diff options
| author | Austin Hellyer <[email protected]> | 2016-09-19 09:00:03 -0700 |
|---|---|---|
| committer | Austin Hellyer <[email protected]> | 2016-10-18 11:14:27 -0700 |
| commit | 8fc8c81403c3daa187ba96a7d488a64db21463bf (patch) | |
| tree | 81bc4890c28b08ce806f69084617066bce863c2d /src/ext | |
| download | serenity-8fc8c81403c3daa187ba96a7d488a64db21463bf.tar.xz serenity-8fc8c81403c3daa187ba96a7d488a64db21463bf.zip | |
Initial commit
Diffstat (limited to 'src/ext')
| -rw-r--r-- | src/ext/framework/command.rs | 7 | ||||
| -rw-r--r-- | src/ext/framework/configuration.rs | 48 | ||||
| -rw-r--r-- | src/ext/framework/mod.rs | 122 | ||||
| -rw-r--r-- | src/ext/mod.rs | 13 | ||||
| -rw-r--r-- | src/ext/state/mod.rs | 729 | ||||
| -rw-r--r-- | src/ext/voice/mod.rs | 0 |
6 files changed, 919 insertions, 0 deletions
diff --git a/src/ext/framework/command.rs b/src/ext/framework/command.rs new file mode 100644 index 0000000..31b2520 --- /dev/null +++ b/src/ext/framework/command.rs @@ -0,0 +1,7 @@ +use std::sync::Arc; +use ::client::Context; +use ::model::Message; + +pub type Command = Fn(Context, Message) + Send + Sync; +#[doc(hidden)] +pub type InternalCommand = Arc<Command>; diff --git a/src/ext/framework/configuration.rs b/src/ext/framework/configuration.rs new file mode 100644 index 0000000..cd68c69 --- /dev/null +++ b/src/ext/framework/configuration.rs @@ -0,0 +1,48 @@ +use std::default::Default; +use ::client::http; + +pub struct Configuration { + pub depth: usize, + pub on_mention: Option<Vec<String>>, + pub prefix: Option<String>, +} + +impl Configuration { + /// The default depth of the message to check for commands. Defaults to 5. + pub fn depth(mut self, depth: u8) -> Self { + self.depth = depth as usize; + + self + } + + pub fn on_mention(mut self, on_mention: bool) -> Self { + if !on_mention { + return self; + } + + if let Ok(current_user) = http::get_current_user() { + self.on_mention = Some(vec![ + format!("<@{}>", current_user.id), // Regular mention + format!("<@!{}>", current_user.id), // Nickname mention + ]); + } + + self + } + + pub fn prefix<S: Into<String>>(mut self, prefix: S) -> Self { + self.prefix = Some(prefix.into()); + + self + } +} + +impl Default for Configuration { + fn default() -> Configuration { + Configuration { + depth: 5, + on_mention: None, + prefix: None, + } + } +} diff --git a/src/ext/framework/mod.rs b/src/ext/framework/mod.rs new file mode 100644 index 0000000..3021d5b --- /dev/null +++ b/src/ext/framework/mod.rs @@ -0,0 +1,122 @@ +mod command; +mod configuration; + +pub use self::command::Command; +pub use self::configuration::Configuration; + +use self::command::InternalCommand; +use std::collections::HashMap; +use std::sync::Arc; +use std::thread; +use ::client::Context; +use ::model::Message; + +#[allow(type_complexity)] +#[derive(Default)] +pub struct Framework { + configuration: Configuration, + commands: HashMap<String, InternalCommand>, + checks: HashMap<String, Arc<Fn(&Context, &Message) -> bool + Send + Sync + 'static>>, + pub initialized: bool, +} + +impl Framework { + pub fn configure<F>(mut self, f: F) -> Self + where F: FnOnce(Configuration) -> Configuration { + self.configuration = f(self.configuration); + self.initialized = true; + + self + } + + #[doc(hidden)] + pub fn dispatch(&mut self, context: Context, message: Message) { + // Determine the point at which the prefix ends, and the command starts. + let pos = if let Some(ref prefix) = self.configuration.prefix { + let mut mention_ends = None; + + if let Some(ref mentions) = self.configuration.on_mention { + for mention in mentions { + if !message.content.starts_with(&mention[..]) { + continue; + } + + mention_ends = Some(mention.len() + 1); + + break; + } + } + + if let Some(mention_ends) = mention_ends { + mention_ends + } else if !message.content.starts_with(prefix) { + return; + } else { + prefix.len() + } + } else { + 0 + }; + + // Ensure that the message length is at least longer than the prefix + // length. There's no point in checking further ahead if there's nothing + // to check. + if message.content.len() <= pos { + return; + } + + let mut built = String::new(); + + for i in 0..self.configuration.depth { + if i > 0 { + built.push(' '); + } + + built.push_str(match { + message.content + .split_at(pos) + .1 + .split_whitespace() + .collect::<Vec<&str>>() + .get(i) + } { + Some(piece) => piece, + None => return, + }); + + if let Some(command) = self.commands.get(&built) { + if let Some(check) = self.checks.get(&built) { + if !(check)(&context, &message) { + return; + } + } + + let command = command.clone(); + + thread::spawn(move || { + (command)(context, message) + }); + + return; + } + } + } + + pub fn on<F, S>(mut self, command_name: S, f: F) -> Self + where F: Fn(Context, Message) + Send + Sync + 'static, + S: Into<String> { + self.commands.insert(command_name.into(), Arc::new(f)); + self.initialized = true; + + self + } + + pub fn set_check<F, S>(mut self, command: S, check: F) -> Self + where F: Fn(&Context, &Message) -> bool + Send + Sync + 'static, + S: Into<String> { + self.checks.insert(command.into(), Arc::new(check)); + self.initialized = true; + + self + } +} diff --git a/src/ext/mod.rs b/src/ext/mod.rs new file mode 100644 index 0000000..92fda62 --- /dev/null +++ b/src/ext/mod.rs @@ -0,0 +1,13 @@ +//! The set of extensions is functionality that is not required for a +//! [`Client`] and/or [`Connection`] to properly function. +//! +//! These are flagged behind feature-gates and can be enabled and disabled. +//! +//! See each extension's module-level documentation for more information. +//! +//! [`Client`]: ../client/struct.Client.html +//! [`Connection`]: ../client/struct.Connection.html + +pub mod framework; +pub mod state; +pub mod voice; diff --git a/src/ext/state/mod.rs b/src/ext/state/mod.rs new file mode 100644 index 0000000..d4c250d --- /dev/null +++ b/src/ext/state/mod.rs @@ -0,0 +1,729 @@ +use std::collections::hash_map::Entry; +use std::collections::HashMap; +use std::default::Default; +use ::model::*; + +/// Known state composed from received events. +#[derive(Debug, Clone)] +pub struct State { + pub calls: HashMap<ChannelId, Call>, + pub groups: HashMap<ChannelId, Group>, + /// Settings specific to a guild. + /// + /// This will always be empty for bot accounts. + pub guild_settings: HashMap<Option<GuildId>, UserGuildSettings>, + pub guilds: HashMap<GuildId, LiveGuild>, + /// A list of notes that a user has made for individual users. + /// + /// This will always be empty for bot accounts. + pub notes: HashMap<UserId, String>, + pub presences: HashMap<UserId, Presence>, + pub private_channels: HashMap<ChannelId, PrivateChannel>, + pub relationships: HashMap<UserId, Relationship>, + /// Account-specific settings for a user account. + pub settings: Option<UserSettings>, + pub unavailable_guilds: Vec<GuildId>, + pub user: CurrentUser, +} + +impl State { + pub fn unknown_members(&self) -> u64 { + let mut total = 0; + + for guild in self.guilds.values() { + let members = guild.members.len() as u64; + + if guild.member_count > members { + total += guild.member_count - members; + } else if guild.member_count < members { + warn!("Inconsistent member count for {:?}: {} < {}", + guild.id, + guild.member_count, + members); + } + } + + total + } + + pub fn all_private_channels(&self) -> Vec<ChannelId> { + self.groups + .keys() + .cloned() + .chain(self.private_channels.keys().cloned()) + .collect() + } + + pub fn all_guilds(&self) -> Vec<GuildId> { + self.guilds + .values() + .map(|s| s.id) + .chain(self.unavailable_guilds.iter().cloned()) + .collect() + } + + #[doc(hidden)] + pub fn __download_members(&mut self) -> Vec<GuildId> { + self.guilds + .values_mut() + .filter(|guild| guild.large) + .map(|ref mut guild| { + guild.members.clear(); + + guild.id + }) + .collect::<Vec<_>>() + } + + pub fn find_call<C: Into<ChannelId>>(&self, group_id: C) -> Option<&Call> { + self.calls.get(&group_id.into()) + } + + pub fn find_channel<C: Into<ChannelId>>(&self, id: C) -> Option<Channel> { + let id = id.into(); + + for guild in self.guilds.values() { + for channel in guild.channels.values() { + if channel.id == id { + return Some(Channel::Public(channel.clone())); + } + } + } + + None + } + + pub fn find_guild<G: Into<GuildId>>(&self, id: G) -> Option<&LiveGuild> { + self.guilds.get(&id.into()) + } + + pub fn find_group<C: Into<ChannelId>>(&self, id: C) -> Option<&Group> { + self.groups.get(&id.into()) + } + + pub fn find_member<G, U>(&self, guild_id: G, user_id: U) + -> Option<&Member> where G: Into<GuildId>, U: Into<UserId> { + self.guilds + .get(&guild_id.into()) + .map(|guild| { + guild.members.get(&user_id.into()) + }).and_then(|x| match x { + Some(x) => Some(x), + _ => None, + }) + } + + pub fn find_role<G, R>(&self, guild_id: G, role_id: R) -> Option<&Role> + where G: Into<GuildId>, R: Into<RoleId> { + if let Some(guild) = self.guilds.get(&guild_id.into()) { + guild.roles.get(&role_id.into()) + } else { + None + } + } + + /// Update the state according to the changes described in the given event. + #[allow(cyclomatic_complexity)] + #[allow(unneeded_field_pattern)] + pub fn update(&mut self, event: &Event) { + match *event { + Event::CallCreate(ref event) => { + self.update_with_call_create(event); + }, + Event::CallUpdate(ref event) => { + self.update_with_call_update(event); + }, + Event::CallDelete(ref event) => { + self.update_with_call_delete(event); + }, + Event::ChannelCreate(ref event) => { + self.update_with_channel_create(event); + }, + Event::ChannelDelete(ref event) => { + self.update_with_channel_delete(event); + }, + Event::ChannelPinsUpdate(ref event) => { + self.update_with_channel_pins_update(event); + }, + Event::ChannelRecipientAdd(ref event) => { + self.update_with_channel_recipient_add(event); + }, + Event::ChannelRecipientRemove(ref event) => { + self.update_with_channel_recipient_remove(event); + }, + Event::ChannelUpdate(ref event) => { + self.update_with_channel_update(event); + }, + Event::GuildCreate(ref event) => { + self.update_with_guild_create(event); + }, + Event::GuildDelete(ref event) => { + self.update_with_guild_delete(event); + }, + Event::GuildEmojisUpdate(ref event) => { + self.update_with_guild_emojis_update(event); + }, + Event::GuildMemberAdd(ref event) => { + self.update_with_guild_member_add(event); + }, + Event::GuildMemberRemove(ref event) => { + self.update_with_guild_member_remove(event); + }, + Event::GuildMemberUpdate(ref event) => { + self.update_with_guild_member_update(event); + }, + Event::GuildMembersChunk(ref event) => { + self.update_with_guild_members_chunk(event); + }, + Event::GuildRoleCreate(ref event) => { + self.update_with_guild_role_create(event); + }, + Event::GuildRoleDelete(ref event) => { + self.update_with_guild_role_delete(event); + }, + Event::GuildRoleUpdate(ref event) => { + self.update_with_guild_role_update(event); + }, + Event::GuildSync(ref event) => { + self.update_with_guild_sync(event); + }, + Event::GuildUnavailable(ref event) => { + self.update_with_guild_unavailable(event); + }, + Event::GuildUpdate(ref event) => { + self.update_with_guild_update(event); + }, + Event::PresencesReplace(ref event) => { + self.update_with_presences_replace(event); + }, + Event::PresenceUpdate(ref event) => { + self.update_with_presence_update(event); + }, + Event::Ready(ref event) => { + self.update_with_ready(event); + }, + Event::RelationshipAdd(ref event) => { + self.update_with_relationship_add(event); + }, + Event::RelationshipRemove(ref event) => { + self.update_with_relationship_remove(event); + }, + Event::UserGuildSettingsUpdate(ref event) => { + self.update_with_user_guild_settings_update(event); + }, + Event::UserNoteUpdate(ref event) => { + self.update_with_user_note_update(event); + }, + Event::UserSettingsUpdate(ref event) => { + self.update_with_user_settings_update(event); + }, + Event::UserUpdate(ref event) => { + self.update_with_user_update(event); + }, + Event::VoiceStateUpdate(ref event) => { + self.update_with_voice_state_update(event); + }, + _ => {}, + } + } + + pub fn update_with_call_create(&mut self, event: &CallCreateEvent) { + match self.calls.entry(event.call.channel_id) { + Entry::Vacant(e) => { + e.insert(event.call.clone()); + }, + Entry::Occupied(mut e) => { + e.get_mut().clone_from(&event.call); + }, + } + } + + pub fn update_with_call_delete(&mut self, event: &CallDeleteEvent) { + self.calls.remove(&event.channel_id); + } + + pub fn update_with_call_update(&mut self, event: &CallUpdateEvent) { + self.calls + .get_mut(&event.channel_id) + .map(|call| { + call.region.clone_from(&event.region); + call.ringing.clone_from(&event.ringing); + }); + } + + pub fn update_with_channel_create(&mut self, event: &ChannelCreateEvent) { + match event.channel { + Channel::Group(ref group) => { + self.groups.insert(group.channel_id, group.clone()); + }, + Channel::Private(ref channel) => { + self.private_channels.insert(channel.id, channel.clone()); + }, + Channel::Public(ref channel) => { + self.guilds + .get_mut(&channel.guild_id) + .map(|guild| guild.channels.insert(channel.id, + channel.clone())); + }, + } + } + + pub fn update_with_channel_delete(&mut self, event: &ChannelDeleteEvent) { + match event.channel { + Channel::Group(ref group) => { + self.groups.remove(&group.channel_id); + }, + Channel::Private(ref channel) => { + self.private_channels.remove(&channel.id); + }, + Channel::Public(ref channel) => { + self.guilds + .get_mut(&channel.guild_id) + .map(|guild| guild.channels.remove(&channel.id)); + }, + } + } + + pub fn update_with_channel_pins_update(&mut self, + event: &ChannelPinsUpdateEvent) { + if let Some(channel) = self.private_channels.get_mut(&event.channel_id) { + channel.last_pin_timestamp = event.last_pin_timestamp.clone(); + + return; + } + + if let Some(group) = self.groups.get_mut(&event.channel_id) { + group.last_pin_timestamp = event.last_pin_timestamp.clone(); + + return; + } + + // Guild searching is last because it is expensive + // in comparison to private channel and group searching. + for guild in self.guilds.values_mut() { + for channel in guild.channels.values_mut() { + if channel.id == event.channel_id { + channel.last_pin_timestamp = event.last_pin_timestamp.clone(); + + return; + } + } + } + } + + pub fn update_with_channel_recipient_add(&mut self, + event: &ChannelRecipientAddEvent) { + self.groups + .get_mut(&event.channel_id) + .map(|group| group.recipients.insert(event.user.id, + event.user.clone())); + } + + pub fn update_with_channel_recipient_remove(&mut self, + event: &ChannelRecipientRemoveEvent) { + self.groups + .get_mut(&event.channel_id) + .map(|group| group.recipients.remove(&event.user.id)); + } + + pub fn update_with_channel_update(&mut self, event: &ChannelUpdateEvent) { + match event.channel { + Channel::Group(ref group) => { + match self.groups.entry(group.channel_id) { + Entry::Vacant(e) => { + e.insert(group.clone()); + }, + Entry::Occupied(mut e) => { + let dest = e.get_mut(); + if group.recipients.is_empty() { + // if the update omits the recipient list, preserve it + let recipients = ::std::mem::replace(&mut dest.recipients, HashMap::new()); + dest.clone_from(group); + dest.recipients = recipients; + } else { + dest.clone_from(group); + } + }, + } + }, + Channel::Private(ref channel) => { + self.private_channels + .get_mut(&channel.id) + .map(|chan| chan.clone_from(channel)); + }, + Channel::Public(ref channel) => { + self.guilds + .get_mut(&channel.guild_id) + .map(|guild| guild.channels + .insert(channel.id, channel.clone())); + }, + } + } + + pub fn update_with_guild_create(&mut self, event: &GuildCreateEvent) { + self.guilds.insert(event.guild.id, event.guild.clone()); + } + + pub fn update_with_guild_delete(&mut self, event: &GuildDeleteEvent) { + self.guilds.remove(&event.guild.id); + } + + pub fn update_with_guild_emojis_update(&mut self, + event: &GuildEmojisUpdateEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| guild.emojis.extend(event.emojis.clone())); + } + + pub fn update_with_guild_member_add(&mut self, + event: &GuildMemberAddEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| { + guild.member_count += 1; + guild.members.insert(event.member.user.id, + event.member.clone()); + }); + } + + pub fn update_with_guild_member_remove(&mut self, + event: &GuildMemberRemoveEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| { + guild.member_count -= 1; + guild.members.remove(&event.user.id); + }); + } + + pub fn update_with_guild_member_update(&mut self, + event: &GuildMemberUpdateEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| { + let mut found = false; + + if let Some(member) = guild.members.get_mut(&event.user.id) { + member.nick.clone_from(&event.nick); + member.roles.clone_from(&event.roles); + member.user.clone_from(&event.user); + + found = true; + } + + if !found { + guild.members.insert(event.user.id, Member { + deaf: false, + joined_at: String::default(), + mute: false, + nick: event.nick.clone(), + roles: event.roles.clone(), + user: event.user.clone(), + }); + } + }); + } + + pub fn update_with_guild_members_chunk(&mut self, + event: &GuildMembersChunkEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| guild.members.extend(event.members.clone())); + } + + pub fn update_with_guild_role_create(&mut self, + event: &GuildRoleCreateEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| guild.roles.insert(event.role.id, event.role.clone())); + } + + pub fn update_with_guild_role_delete(&mut self, + event: &GuildRoleDeleteEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| guild.roles.remove(&event.role_id)); + } + + pub fn update_with_guild_role_update(&mut self, + event: &GuildRoleUpdateEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| guild.roles + .get_mut(&event.role.id) + .map(|role| role.clone_from(&event.role))); + } + + pub fn update_with_guild_sync(&mut self, event: &GuildSyncEvent) { + self.guilds + .get_mut(&event.guild_id) + .map(|guild| { + guild.large = event.large; + guild.members.clone_from(&event.members); + guild.presences.clone_from(&event.presences); + }); + } + + pub fn update_with_guild_unavailable(&mut self, + event: &GuildUnavailableEvent) { + if !self.unavailable_guilds.contains(&event.guild_id) { + self.unavailable_guilds.push(event.guild_id); + } + } + + pub fn update_with_guild_update(&mut self, event: &GuildUpdateEvent) { + self.guilds + .get_mut(&event.guild.id) + .map(|guild| { + // todo: embed + guild.afk_timeout = event.guild.afk_timeout; + guild.afk_channel_id.clone_from(&event.guild.afk_channel_id); + guild.icon.clone_from(&event.guild.icon); + guild.name.clone_from(&event.guild.name); + guild.owner_id.clone_from(&event.guild.owner_id); + guild.region.clone_from(&event.guild.region); + guild.roles.clone_from(&event.guild.roles); + guild.verification_level = event.guild.verification_level; + }); + } + + pub fn update_with_presences_replace(&mut self, event: &PresencesReplaceEvent) { + self.presences.clone_from(&{ + let mut p = HashMap::default(); + + for presence in &event.presences { + p.insert(presence.user_id, presence.clone()); + } + + p + }); + } + + pub fn update_with_presence_update(&mut self, event: &PresenceUpdateEvent) { + if let Some(guild_id) = event.guild_id { + if let Some(guild) = self.guilds.get_mut(&guild_id) { + // If the user was modified, update the member list + if let Some(user) = event.presence.user.as_ref() { + guild.members + .get_mut(&user.id) + .map(|member| member.user.clone_from(user)); + } + + update_presence(&mut guild.presences, &event.presence); + } + } + } + + pub fn update_with_ready(&mut self, ready: &ReadyEvent) { + let ready = ready.ready.clone(); + + for guild in ready.guilds { + match guild { + PossibleGuild::Offline(guild_id) => { + self.unavailable_guilds.push(guild_id); + } + PossibleGuild::Online(guild) => { + self.guilds.insert(guild.id, guild); + }, + } + } + + self.unavailable_guilds.sort(); + self.unavailable_guilds.dedup(); + + // The private channels sent in the READY contains both the actual + // private channels, and the groups. + for (channel_id, channel) in ready.private_channels { + match channel { + Channel::Group(group) => { + self.groups.insert(channel_id, group); + }, + Channel::Private(channel) => { + self.private_channels.insert(channel_id, channel); + }, + Channel::Public(_) => {}, + } + } + + for guild in ready.user_guild_settings.unwrap_or_default() { + self.guild_settings.insert(guild.guild_id, guild); + } + + for (user_id, presence) in ready.presences { + self.presences.insert(user_id, presence); + } + + for (user_id, relationship) in ready.relationships { + self.relationships.insert(user_id, relationship); + } + + self.notes.extend(ready.notes); + + self.settings = ready.user_settings; + self.user = ready.user; + } + + pub fn update_with_relationship_add(&mut self, event: &RelationshipAddEvent) { + self.relationships.insert(event.relationship.id, + event.relationship.clone()); + } + + pub fn update_with_relationship_remove(&mut self, + event: &RelationshipRemoveEvent) { + self.relationships.remove(&event.user_id); + } + + pub fn update_with_user_guild_settings_update(&mut self, + event: &UserGuildSettingsUpdateEvent) { + self.guild_settings + .get_mut(&event.settings.guild_id) + .map(|guild_setting| guild_setting.clone_from(&event.settings)); + } + + pub fn update_with_user_note_update(&mut self, + event: &UserNoteUpdateEvent) { + if event.note.is_empty() { + self.notes.remove(&event.user_id); + } else { + self.notes.insert(event.user_id, event.note.clone()); + } + } + + pub fn update_with_user_settings_update(&mut self, + event: &UserSettingsUpdateEvent) { + self.settings + .as_mut() + .map(|settings| { + opt_modify(&mut settings.enable_tts_command, &event.enable_tts_command); + opt_modify(&mut settings.inline_attachment_media, &event.inline_attachment_media); + opt_modify(&mut settings.inline_embed_media, &event.inline_embed_media); + opt_modify(&mut settings.locale, &event.locale); + opt_modify(&mut settings.message_display_compact, &event.message_display_compact); + opt_modify(&mut settings.render_embeds, &event.render_embeds); + opt_modify(&mut settings.show_current_game, &event.show_current_game); + opt_modify(&mut settings.theme, &event.theme); + opt_modify(&mut settings.convert_emoticons, &event.convert_emoticons); + opt_modify(&mut settings.friend_source_flags, &event.friend_source_flags); + }); + } + + pub fn update_with_user_update(&mut self, event: &UserUpdateEvent) { + self.user = event.current_user.clone(); + } + + pub fn update_with_voice_state_update(&mut self, + event: &VoiceStateUpdateEvent) { + if let Some(guild_id) = event.guild_id { + if let Some(guild) = self.guilds.get_mut(&guild_id) { + if !event.voice_state.channel_id.is_some() { + // Remove the user from the voice state list + guild.voice_states.remove(&event.voice_state.user_id); + } else { + // Update or add to the voice state list + { + let finding = guild.voice_states + .get_mut(&event.voice_state.user_id); + + if let Some(srv_state) = finding { + srv_state.clone_from(&event.voice_state); + + return; + } + } + + guild.voice_states.insert(event.voice_state.user_id, + event.voice_state.clone()); + } + } + + return; + } + + if let Some(channel) = event.voice_state.channel_id { + // channel id available, insert voice state + if let Some(call) = self.calls.get_mut(&channel) { + { + let finding = call.voice_states + .get_mut(&event.voice_state.user_id); + + if let Some(grp_state) = finding { + grp_state.clone_from(&event.voice_state); + + return; + } + } + + call.voice_states.insert(event.voice_state.user_id, + event.voice_state.clone()); + } + } else { + // delete this user from any group call containing them + for call in self.calls.values_mut() { + call.voice_states.remove(&event.voice_state.user_id); + } + } + } +} + +impl Default for State { + fn default() -> State { + State { + calls: HashMap::default(), + groups: HashMap::default(), + guild_settings: HashMap::default(), + guilds: HashMap::default(), + notes: HashMap::default(), + presences: HashMap::default(), + private_channels: HashMap::default(), + relationships: HashMap::default(), + settings: None, + unavailable_guilds: Vec::default(), + user: CurrentUser { + avatar: None, + bot: false, + discriminator: 0, + email: None, + id: UserId(0), + mfa_enabled: false, + mobile: None, + name: String::default(), + verified: false, + } + } + } +} + +fn update_presence(presences: &mut HashMap<UserId, Presence>, + presence: &Presence) { + if presence.status == OnlineStatus::Offline { + // Remove the user from the presence list + presences.remove(&presence.user_id); + } else { + // Update or add to the presence list + if let Some(ref mut guild_presence) = presences.get(&presence.user_id) { + if presence.user.is_none() { + guild_presence.clone_from(&presence); + } + + return; + } + presences.insert(presence.user_id, presence.clone()); + } +} + +/// A reference to a private channel, public channel, or group. +#[derive(Debug, Clone, Copy)] +pub enum ChannelRef<'a> { + /// A private channel + Private(&'a PrivateChannel), + /// A group channel + Group(&'a Group), + /// A public channel and its guild + Public(&'a LiveGuild, &'a PublicChannel), +} + +fn opt_modify<T: Clone>(dest: &mut T, src: &Option<T>) { + if let Some(val) = src.as_ref() { + dest.clone_from(val); + } +} diff --git a/src/ext/voice/mod.rs b/src/ext/voice/mod.rs new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/ext/voice/mod.rs |