mod emoji;
mod guild_id;
mod integration;
mod member;
mod partial_guild;
mod role;
mod audit_log;
mod premium_tier;
#[cfg(feature = "http")]
use crate::http::CacheHttp;
pub use self::emoji::*;
pub use self::guild_id::*;
pub use self::integration::*;
pub use self::member::*;
pub use self::partial_guild::*;
pub use self::role::*;
pub use self::audit_log::*;
pub use self::premium_tier::*;
use chrono::{DateTime, FixedOffset};
use crate::model::prelude::*;
use serde::de::Error as DeError;
use super::utils::*;
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::CacheRwLock;
#[cfg(all(feature = "cache", feature = "model"))]
use parking_lot::RwLock;
#[cfg(all(feature = "http", feature = "model"))]
use serde_json::json;
#[cfg(all(feature = "cache", feature = "model"))]
use std::sync::Arc;
#[cfg(feature = "model")]
use crate::builder::{CreateChannel, EditGuild, EditMember, EditRole};
#[cfg(feature = "model")]
use crate::constants::LARGE_THRESHOLD;
#[cfg(feature = "model")]
use log::{error, warn};
#[cfg(feature = "model")]
use std::borrow::Cow;
#[cfg(feature = "http")]
use crate::http::Http;
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
pub struct Ban {
pub reason: Option<String>,
pub user: User,
}
#[derive(Clone, Debug, Serialize)]
pub struct Guild {
pub afk_channel_id: Option<ChannelId>,
pub afk_timeout: u64,
pub application_id: Option<ApplicationId>,
#[serde(serialize_with = "serialize_gen_locked_map")]
pub channels: HashMap<ChannelId, Arc<RwLock<GuildChannel>>>,
pub default_message_notifications: DefaultMessageNotificationLevel,
#[serde(serialize_with = "serialize_gen_map")]
pub emojis: HashMap<EmojiId, Emoji>,
pub explicit_content_filter: ExplicitContentFilter,
pub features: Vec<String>,
pub icon: Option<String>,
pub id: GuildId,
pub joined_at: DateTime<FixedOffset>,
pub large: bool,
pub member_count: u64,
#[serde(serialize_with = "serialize_gen_map")]
pub members: HashMap<UserId, Member>,
pub mfa_level: MfaLevel,
pub name: String,
pub owner_id: UserId,
#[serde(serialize_with = "serialize_gen_map")]
pub presences: HashMap<UserId, Presence>,
pub region: String,
#[serde(serialize_with = "serialize_gen_map")]
pub roles: HashMap<RoleId, Role>,
pub splash: Option<String>,
pub system_channel_id: Option<ChannelId>,
pub verification_level: VerificationLevel,
#[serde(serialize_with = "serialize_gen_map")]
pub voice_states: HashMap<UserId, VoiceState>,
pub description: Option<String>,
#[serde(default)]
pub premium_tier: PremiumTier,
#[serde(default)]
pub premium_subscription_count: u64,
pub banner: Option<String>,
pub vanity_url_code: Option<String>,
pub preferred_locale: String,
#[serde(skip)]
pub(crate) _nonexhaustive: (),
}
#[cfg(feature = "model")]
impl Guild {
#[cfg(feature = "cache")]
fn check_hierarchy(&self, cache: impl AsRef<CacheRwLock>, other_user: UserId) -> Result<()> {
let current_id = cache.as_ref().read().user.id;
if let Some(higher) = self.greater_member_hierarchy(&cache, other_user, current_id) {
if higher != current_id {
return Err(Error::Model(ModelError::Hierarchy));
}
}
Ok(())
}
#[cfg(feature = "http")]
pub fn default_channel(&self, uid: UserId) -> Option<Arc<RwLock<GuildChannel>>> {
for (cid, channel) in &self.channels {
if self.user_permissions_in(*cid, uid).read_messages() {
return Some(Arc::clone(channel));
}
}
None
}
pub fn default_channel_guaranteed(&self) -> Option<Arc<RwLock<GuildChannel>>> {
for (cid, channel) in &self.channels {
for memid in self.members.keys() {
if self.user_permissions_in(*cid, *memid).read_messages() {
return Some(Arc::clone(channel));
}
}
}
None
}
#[cfg(feature = "cache")]
fn has_perms(&self, cache: impl AsRef<CacheRwLock>, mut permissions: Permissions) -> bool {
let user_id = cache.as_ref().read().user.id;
let perms = self.member_permissions(user_id);
permissions.remove(perms);
permissions.is_empty()
}
#[cfg(feature = "cache")]
pub fn channel_id_from_name(&self, cache: impl AsRef<CacheRwLock>, name: impl AsRef<str>) -> Option<ChannelId> {
let name = name.as_ref();
let cache = cache.as_ref().read();
let guild = cache.guilds.get(&self.id)?.read();
guild.channels
.iter()
.find_map(|(id, c)| {
if c.read().name == name {
Some(*id)
} else {
None
}
})
}
#[cfg(feature = "client")]
#[inline]
pub fn ban<U: Into<UserId>, BO: BanOptions>(&self, cache_http: impl CacheHttp, user: U, options: &BO) -> Result<()> {
self._ban(cache_http, user.into(), options)
}
#[cfg(feature = "client")]
fn _ban<BO: BanOptions>(&self, cache_http: impl CacheHttp, user: UserId, options: &BO) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::BAN_MEMBERS;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
self.check_hierarchy(cache, user)?;
}
}
self.id.ban(cache_http.http(), user, options)
}
#[cfg(feature = "http")]
pub fn bans(&self, cache_http: impl CacheHttp) -> Result<Vec<Ban>> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::BAN_MEMBERS;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.bans(cache_http.http())
}
#[cfg(feature = "http")]
#[inline]
pub fn audit_logs(&self, http: impl AsRef<Http>,
action_type: Option<u8>,
user_id: Option<UserId>,
before: Option<AuditLogEntryId>,
limit: Option<u8>) -> Result<AuditLogs> {
self.id.audit_logs(&http, action_type, user_id, before, limit)
}
#[cfg(feature = "http")]
#[inline]
pub fn channels(&self, http: impl AsRef<Http>) -> Result<HashMap<ChannelId, GuildChannel>> { self.id.channels(&http) }
#[cfg(feature = "http")]
pub fn create(http: impl AsRef<Http>, name: &str, region: Region, icon: Option<&str>) -> Result<PartialGuild> {
let map = json!({
"icon": icon,
"name": name,
"region": region.name(),
});
http.as_ref().create_guild(&map)
}
#[cfg(feature = "client")]
pub fn create_channel(&self, cache_http: impl CacheHttp, f: impl FnOnce(&mut CreateChannel) -> &mut CreateChannel) -> Result<GuildChannel> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::MANAGE_CHANNELS;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.create_channel(cache_http.http(), f)
}
#[cfg(feature = "http")]
#[inline]
pub fn create_emoji(&self, http: impl AsRef<Http>, name: &str, image: &str) -> Result<Emoji> {
self.id.create_emoji(&http, name, image)
}
#[cfg(feature = "http")]
#[inline]
pub fn create_integration<I>(&self, http: impl AsRef<Http>, integration_id: I, kind: &str) -> Result<()>
where I: Into<IntegrationId> {
self.id.create_integration(&http, integration_id, kind)
}
#[cfg(feature = "client")]
pub fn create_role<F>(&self, cache_http: impl CacheHttp, f: F) -> Result<Role>
where F: FnOnce(&mut EditRole) -> &mut EditRole {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::MANAGE_ROLES;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.create_role(cache_http.http(), f)
}
#[cfg(feature = "http")]
pub fn delete(&self, cache_http: impl CacheHttp) -> Result<PartialGuild> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.owner_id != cache.read().user.id {
let req = Permissions::MANAGE_GUILD;
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.delete(cache_http.http())
}
#[cfg(feature = "http")]
#[inline]
pub fn delete_emoji<E: Into<EmojiId>>(&self, http: impl AsRef<Http>, emoji_id: E) -> Result<()> {
self.id.delete_emoji(&http, emoji_id)
}
#[cfg(feature = "http")]
#[inline]
pub fn delete_integration<I: Into<IntegrationId>>(&self, http: impl AsRef<Http>, integration_id: I) -> Result<()> {
self.id.delete_integration(&http, integration_id)
}
#[cfg(feature = "http")]
#[inline]
pub fn delete_role<R: Into<RoleId>>(&self, http: impl AsRef<Http>, role_id: R) -> Result<()> {
self.id.delete_role(&http, role_id)
}
#[cfg(feature = "client")]
pub fn edit<F>(&mut self, cache_http: impl CacheHttp, f: F) -> Result<()>
where F: FnOnce(&mut EditGuild) -> &mut EditGuild {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::MANAGE_GUILD;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
match self.id.edit(cache_http.http(), f) {
Ok(guild) => {
self.afk_channel_id = guild.afk_channel_id;
self.afk_timeout = guild.afk_timeout;
self.default_message_notifications = guild.default_message_notifications;
self.emojis = guild.emojis;
self.features = guild.features;
self.icon = guild.icon;
self.mfa_level = guild.mfa_level;
self.name = guild.name;
self.owner_id = guild.owner_id;
self.region = guild.region;
self.roles = guild.roles;
self.splash = guild.splash;
self.verification_level = guild.verification_level;
Ok(())
},
Err(why) => Err(why),
}
}
#[cfg(feature = "http")]
#[inline]
pub fn edit_emoji<E: Into<EmojiId>>(&self, http: impl AsRef<Http>, emoji_id: E, name: &str) -> Result<Emoji> {
self.id.edit_emoji(&http, emoji_id, name)
}
#[cfg(feature = "http")]
#[inline]
pub fn edit_member<F, U>(&self, http: impl AsRef<Http>, user_id: U, f: F) -> Result<()>
where F: FnOnce(&mut EditMember) -> &mut EditMember, U: Into<UserId> {
self.id.edit_member(&http, user_id, f)
}
#[cfg(feature = "client")]
pub fn edit_nickname(&self, cache_http: impl CacheHttp, new_nickname: Option<&str>) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::CHANGE_NICKNAME;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.edit_nickname(cache_http.http(), new_nickname)
}
#[cfg(feature = "http")]
#[inline]
pub fn edit_role<F, R>(&self, http: impl AsRef<Http>, role_id: R, f: F) -> Result<Role>
where F: FnOnce(&mut EditRole) -> &mut EditRole, R: Into<RoleId> {
self.id.edit_role(&http, role_id, f)
}
#[cfg(feature = "http")]
#[inline]
pub fn edit_role_position<R>(&self, http: impl AsRef<Http>, role_id: R, position: u64) -> Result<Vec<Role>>
where R: Into<RoleId> {
self.id.edit_role_position(&http, role_id, position)
}
#[cfg(feature = "http")]
#[inline]
pub fn get<G: Into<GuildId>>(http: impl AsRef<Http>, guild_id: G) -> Result<PartialGuild> { guild_id.into().to_partial_guild(&http) }
#[cfg(feature = "cache")]
#[inline]
pub fn greater_member_hierarchy<T, U>(&self, cache: impl AsRef<CacheRwLock>, lhs_id: T, rhs_id: U)
-> Option<UserId> where T: Into<UserId>, U: Into<UserId> {
self._greater_member_hierarchy(&cache, lhs_id.into(), rhs_id.into())
}
#[cfg(feature = "cache")]
fn _greater_member_hierarchy(
&self,
cache: impl AsRef<CacheRwLock>,
lhs_id: UserId,
rhs_id: UserId,
) -> Option<UserId> {
if lhs_id == rhs_id {
return None;
}
if lhs_id == self.owner_id {
return Some(lhs_id);
} else if rhs_id == self.owner_id {
return Some(rhs_id);
}
let lhs = self.members.get(&lhs_id)?
.highest_role_info(&cache)
.unwrap_or((RoleId(0), 0));
let rhs = self.members.get(&rhs_id)?
.highest_role_info(&cache)
.unwrap_or((RoleId(0), 0));
if (lhs.1 == 0 && rhs.1 == 0) || (lhs.0 == rhs.0) {
return None;
}
if lhs.1 > rhs.1 {
return Some(lhs_id)
}
if rhs.1 > lhs.1 {
return Some(rhs_id);
}
if lhs.1 == rhs.1 && lhs.0 < rhs.0 {
Some(lhs_id)
} else {
Some(rhs_id)
}
}
pub fn icon_url(&self) -> Option<String> {
self.icon
.as_ref()
.map(|icon| format!(cdn!("/icons/{}/{}.webp"), self.id, icon))
}
#[cfg(feature = "http")]
#[inline]
pub fn integrations(&self, http: impl AsRef<Http>) -> Result<Vec<Integration>> { self.id.integrations(&http) }
#[cfg(feature = "http")]
pub fn invites(&self, cache_http: impl CacheHttp) -> Result<Vec<RichInvite>> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::MANAGE_GUILD;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.invites(cache_http.http())
}
#[inline]
pub fn is_large(&self) -> bool { self.members.len() > LARGE_THRESHOLD as usize }
#[cfg(feature = "http")]
#[inline]
pub fn kick<U: Into<UserId>>(&self, http: impl AsRef<Http>, user_id: U) -> Result<()> { self.id.kick(&http, user_id) }
#[inline]
pub fn leave(&self, http: impl AsRef<Http>) -> Result<()> { self.id.leave(&http) }
#[inline]
#[cfg(feature = "client")]
pub fn member<U: Into<UserId>>(&self, cache_http: impl CacheHttp, user_id: U) -> Result<Member> {
self.id.member(cache_http, user_id)
}
#[cfg(feature = "http")]
#[inline]
pub fn members<U>(&self, http: impl AsRef<Http>, limit: Option<u64>, after: U) -> Result<Vec<Member>>
where U: Into<Option<UserId>> {
self.id.members(&http, limit, after)
}
pub fn members_with_status(&self, status: OnlineStatus) -> Vec<&Member> {
let mut members = vec![];
for (&id, member) in &self.members {
match self.presences.get(&id) {
Some(presence) => if status == presence.status {
members.push(member);
},
None => continue,
}
}
members
}
pub fn member_named(&self, name: &str) -> Option<&Member> {
let (name, discrim) = if let Some(pos) = name.rfind('#') {
let split = name.split_at(pos + 1);
let split2 = (
match split.0.get(0..split.0.len() - 1) {
Some(s) => s,
None => "",
},
split.1,
);
match split2.1.parse::<u16>() {
Ok(discrim_int) => (split2.0, Some(discrim_int)),
Err(_) => (name, None),
}
} else {
(&name[..], None)
};
self.members
.values()
.find(|member| {
let name_matches = member.user.read().name == name;
let discrim_matches = match discrim {
Some(discrim) => member.user.read().discriminator == discrim,
None => true,
};
name_matches && discrim_matches
})
.or_else(|| {
self.members
.values()
.find(|member| member.nick.as_ref().map_or(false, |nick| nick == name))
})
}
pub fn members_starting_with(&self, prefix: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> {
let mut members: Vec<&Member> = self.members
.values()
.filter(|member|
if case_sensitive {
member.user.read().name.starts_with(prefix)
} else {
starts_with_case_insensitive(&member.user.read().name, prefix)
}
|| member.nick.as_ref()
.map_or(false, |nick|
if case_sensitive {
nick.starts_with(prefix)
} else {
starts_with_case_insensitive(nick, prefix)
})).collect();
if sorted {
members
.sort_by(|a, b| {
let name_a = match a.nick {
Some(ref nick) => {
if contains_case_insensitive(&a.user.read().name[..], prefix) {
Cow::Owned(a.user.read().name.clone())
} else {
Cow::Borrowed(nick)
}
},
None => Cow::Owned(a.user.read().name.clone()),
};
let name_b = match b.nick {
Some(ref nick) => {
if contains_case_insensitive(&b.user.read().name[..], prefix) {
Cow::Owned(b.user.read().name.clone())
} else {
Cow::Borrowed(nick)
}
},
None => Cow::Owned(b.user.read().name.clone()),
};
closest_to_origin(prefix, &name_a[..], &name_b[..])
});
members
} else {
members
}
}
pub fn members_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> {
let mut members: Vec<&Member> = self.members
.values()
.filter(|member|
if case_sensitive {
member.user.read().name.contains(substring)
} else {
contains_case_insensitive(&member.user.read().name, substring)
}
|| member.nick.as_ref()
.map_or(false, |nick| {
if case_sensitive {
nick.contains(substring)
} else {
contains_case_insensitive(nick, substring)
}
})).collect();
if sorted {
members
.sort_by(|a, b| {
let name_a = match a.nick {
Some(ref nick) => {
if contains_case_insensitive(&a.user.read().name[..], substring) {
Cow::Owned(a.user.read().name.clone())
} else {
Cow::Borrowed(nick)
}
},
None => Cow::Owned(a.user.read().name.clone()),
};
let name_b = match b.nick {
Some(ref nick) => {
if contains_case_insensitive(&b.user.read().name[..], substring) {
Cow::Owned(b.user.read().name.clone())
} else {
Cow::Borrowed(nick)
}
},
None => Cow::Owned(b.user.read().name.clone()),
};
closest_to_origin(substring, &name_a[..], &name_b[..])
});
members
} else {
members
}
}
pub fn members_username_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> {
let mut members: Vec<&Member> = self.members
.values()
.filter(|member| {
if case_sensitive {
member.user.read().name.contains(substring)
} else {
contains_case_insensitive(&member.user.read().name, substring)
}
}).collect();
if sorted {
members
.sort_by(|a, b| {
let name_a = &a.user.read().name;
let name_b = &b.user.read().name;
closest_to_origin(substring, &name_a[..], &name_b[..])
});
members
} else {
members
}
}
pub fn members_nick_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<&Member> {
let mut members: Vec<&Member> = self.members
.values()
.filter(|member|
member.nick.as_ref()
.map_or(false, |nick| {
if case_sensitive {
nick.contains(substring)
} else {
contains_case_insensitive(nick, substring)
}
})).collect();
if sorted {
members
.sort_by(|a, b| {
let name_a = match a.nick {
Some(ref nick) => {
Cow::Borrowed(nick)
},
None => Cow::Owned(a.user.read().name.clone()),
};
let name_b = match b.nick {
Some(ref nick) => {
Cow::Borrowed(nick)
},
None => Cow::Owned(b.user.read().name.clone()),
};
closest_to_origin(substring, &name_a[..], &name_b[..])
});
members
} else {
members
}
}
#[inline]
pub fn member_permissions<U>(&self, user_id: U) -> Permissions
where U: Into<UserId> {
self._member_permissions(user_id.into())
}
fn _member_permissions(&self, user_id: UserId) -> Permissions {
if user_id == self.owner_id {
return Permissions::all();
}
let everyone = match self.roles.get(&RoleId(self.id.0)) {
Some(everyone) => everyone,
None => {
error!(
"(╯°□°)╯︵ ┻━┻ @everyone role ({}) missing in '{}'",
self.id,
self.name,
);
return Permissions::empty();
},
};
let member = match self.members.get(&user_id) {
Some(member) => member,
None => return everyone.permissions,
};
let mut permissions = everyone.permissions;
for role in &member.roles {
if let Some(role) = self.roles.get(role) {
if role.permissions.contains(Permissions::ADMINISTRATOR) {
return Permissions::all();
}
permissions |= role.permissions;
} else {
warn!(
"(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}",
member.user.read().id,
self.id,
role,
);
}
}
permissions
}
#[cfg(feature = "http")]
#[inline]
pub fn move_member<C, U>(&self, http: impl AsRef<Http>, user_id: U, channel_id: C) -> Result<()>
where C: Into<ChannelId>, U: Into<UserId> {
self.id.move_member(&http, user_id, channel_id)
}
#[inline]
#[deprecated(since="0.6.4", note="Please use `user_permissions_in` instead.")]
pub fn permissions_in<C, U>(&self, channel_id: C, user_id: U) -> Permissions
where C: Into<ChannelId>, U: Into<UserId> {
self.user_permissions_in(channel_id.into(), user_id.into())
}
#[inline]
pub fn user_permissions_in<C, U>(&self, channel_id: C, user_id: U) -> Permissions
where C: Into<ChannelId>, U: Into<UserId> {
self._user_permissions_in(channel_id.into(), user_id.into())
}
fn _user_permissions_in(
&self,
channel_id: ChannelId,
user_id: UserId,
) -> Permissions {
if user_id == self.owner_id {
return Permissions::all();
}
let everyone = match self.roles.get(&RoleId(self.id.0)) {
Some(everyone) => everyone,
None => {
error!(
"(╯°□°)╯︵ ┻━┻ @everyone role ({}) missing in '{}'",
self.id,
self.name
);
return Permissions::empty();
},
};
let mut permissions = everyone.permissions;
let member = match self.members.get(&user_id) {
Some(member) => member,
None => return everyone.permissions,
};
for &role in &member.roles {
if let Some(role) = self.roles.get(&role) {
permissions |= role.permissions;
} else {
warn!(
"(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}",
member.user.read().id,
self.id,
role
);
}
}
if permissions.contains(Permissions::ADMINISTRATOR) {
return Permissions::all();
}
if let Some(channel) = self.channels.get(&channel_id) {
let channel = channel.read();
if channel.kind == ChannelType::Text {
permissions &= !(Permissions::CONNECT
| Permissions::SPEAK
| Permissions::MUTE_MEMBERS
| Permissions::DEAFEN_MEMBERS
| Permissions::MOVE_MEMBERS
| Permissions::USE_VAD);
}
let mut data = Vec::with_capacity(member.roles.len());
for overwrite in &channel.permission_overwrites {
if let PermissionOverwriteType::Role(role) = overwrite.kind {
if role.0 != self.id.0 && !member.roles.contains(&role) {
continue;
}
if let Some(role) = self.roles.get(&role) {
data.push((role.position, overwrite.deny, overwrite.allow));
}
}
}
data.sort_by(|a, b| a.0.cmp(&b.0));
for overwrite in data {
permissions = (permissions & !overwrite.1) | overwrite.2;
}
for overwrite in &channel.permission_overwrites {
if PermissionOverwriteType::Member(user_id) != overwrite.kind {
continue;
}
permissions = (permissions & !overwrite.deny) | overwrite.allow;
}
} else {
warn!(
"(╯°□°)╯︵ ┻━┻ Guild {} does not contain channel {}",
self.id,
channel_id
);
}
if channel_id.0 == self.id.0 {
permissions |= Permissions::READ_MESSAGES;
}
self.remove_unusable_permissions(&mut permissions);
permissions
}
#[inline]
pub fn role_permissions_in<C, R>(&self, channel_id: C, role_id: R) -> Option<Permissions>
where C: Into<ChannelId>, R: Into<RoleId> {
self._role_permissions_in(channel_id.into(), role_id.into())
}
fn _role_permissions_in(
&self,
channel_id: ChannelId,
role_id: RoleId,
) -> Option<Permissions> {
let mut permissions = match self.roles.get(&role_id) {
Some(role) => role.permissions,
None => return None,
};
if permissions.contains(Permissions::ADMINISTRATOR) {
return Some(Permissions::all());
}
if let Some(channel) = self.channels.get(&channel_id) {
let channel = channel.read();
for overwrite in &channel.permission_overwrites {
if let PermissionOverwriteType::Role(permissions_role_id) = overwrite.kind {
if permissions_role_id == role_id {
permissions = (permissions & !overwrite.deny) | overwrite.allow;
break;
}
}
}
} else {
warn!(
"(╯°□°)╯︵ ┻━┻ Guild {} does not contain channel {}",
self.id,
channel_id
);
return None;
}
self.remove_unusable_permissions(&mut permissions);
Some(permissions)
}
#[cfg(feature = "client")]
pub fn prune_count(&self, cache_http: impl CacheHttp, days: u16) -> Result<GuildPrune> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::KICK_MEMBERS;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.prune_count(cache_http.http(), days)
}
fn remove_unusable_permissions(&self, permissions: &mut Permissions) {
if !permissions.contains(Permissions::SEND_MESSAGES) {
*permissions &= !(Permissions::SEND_TTS_MESSAGES
| Permissions::MENTION_EVERYONE
| Permissions::EMBED_LINKS
| Permissions::ATTACH_FILES);
}
if !permissions.contains(Permissions::READ_MESSAGES) {
*permissions &= Permissions::KICK_MEMBERS
| Permissions::BAN_MEMBERS
| Permissions::ADMINISTRATOR
| Permissions::MANAGE_GUILD
| Permissions::CHANGE_NICKNAME
| Permissions::MANAGE_NICKNAMES;
}
}
#[cfg(feature = "http")]
pub fn reorder_channels<It>(&self, http: impl AsRef<Http>, channels: It) -> Result<()>
where It: IntoIterator<Item = (ChannelId, u64)> {
self.id.reorder_channels(&http, channels)
}
#[cfg(all(feature = "cache", feature = "utils"))]
#[inline]
pub fn shard_id(&self, cache: impl AsRef<CacheRwLock>) -> u64 { self.id.shard_id(&cache) }
#[cfg(all(feature = "utils", not(feature = "cache")))]
#[inline]
pub fn shard_id(&self, shard_count: u64) -> u64 { self.id.shard_id(shard_count) }
pub fn splash_url(&self) -> Option<String> {
self.icon
.as_ref()
.map(|icon| format!(cdn!("/splashes/{}/{}.webp"), self.id, icon))
}
#[cfg(feature = "http")]
#[inline]
pub fn start_integration_sync<I: Into<IntegrationId>>(&self, http: impl AsRef<Http>, integration_id: I) -> Result<()> {
self.id.start_integration_sync(&http, integration_id)
}
#[cfg(feature = "client")]
pub fn start_prune(&self, cache_http: impl CacheHttp, days: u16) -> Result<GuildPrune> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::KICK_MEMBERS;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.start_prune(cache_http.http(), days)
}
#[cfg(feature = "client")]
pub fn unban<U: Into<UserId>>(&self, cache_http: impl CacheHttp, user_id: U) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::BAN_MEMBERS;
if !self.has_perms(cache, req) {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.unban(&cache_http.http(), user_id)
}
#[cfg(feature = "http")]
#[inline]
pub fn vanity_url(&self, http: impl AsRef<Http>) -> Result<String> {
self.id.vanity_url(&http)
}
#[cfg(feature = "http")]
#[inline]
pub fn webhooks(&self, http: impl AsRef<Http>) -> Result<Vec<Webhook>> { self.id.webhooks(&http) }
pub fn role_by_name(&self, role_name: &str) -> Option<&Role> {
self.roles.values().find(|role| role_name == role.name)
}
}
impl<'de> Deserialize<'de> for Guild {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
let mut map = JsonMap::deserialize(deserializer)?;
let id = map.get("id")
.and_then(|x| x.as_str())
.and_then(|x| x.parse::<u64>().ok());
if let Some(guild_id) = id {
if let Some(array) = map.get_mut("channels").and_then(|x| x.as_array_mut()) {
for value in array {
if let Some(channel) = value.as_object_mut() {
channel
.insert("guild_id".to_string(), Value::Number(Number::from(guild_id)));
}
}
}
if let Some(array) = map.get_mut("members").and_then(|x| x.as_array_mut()) {
for value in array {
if let Some(member) = value.as_object_mut() {
member
.insert("guild_id".to_string(), Value::Number(Number::from(guild_id)));
}
}
}
}
let afk_channel_id = match map.remove("afk_channel_id") {
Some(v) => serde_json::from_value::<Option<ChannelId>>(v)
.map_err(DeError::custom)?,
None => None,
};
let afk_timeout = map.remove("afk_timeout")
.ok_or_else(|| DeError::custom("expected guild afk_timeout"))
.and_then(u64::deserialize)
.map_err(DeError::custom)?;
let application_id = match map.remove("application_id") {
Some(v) => serde_json::from_value::<Option<ApplicationId>>(v)
.map_err(DeError::custom)?,
None => None,
};
let channels = map.remove("channels")
.ok_or_else(|| DeError::custom("expected guild channels"))
.and_then(deserialize_guild_channels)
.map_err(DeError::custom)?;
let default_message_notifications = map.remove("default_message_notifications")
.ok_or_else(|| {
DeError::custom("expected guild default_message_notifications")
})
.and_then(DefaultMessageNotificationLevel::deserialize)
.map_err(DeError::custom)?;
let emojis = map.remove("emojis")
.ok_or_else(|| DeError::custom("expected guild emojis"))
.and_then(deserialize_emojis)
.map_err(DeError::custom)?;
let explicit_content_filter = map.remove("explicit_content_filter")
.ok_or_else(|| DeError::custom(
"expected guild explicit_content_filter"
))
.and_then(ExplicitContentFilter::deserialize)
.map_err(DeError::custom)?;
let features = map.remove("features")
.ok_or_else(|| DeError::custom("expected guild features"))
.and_then(serde_json::from_value::<Vec<String>>)
.map_err(DeError::custom)?;
let icon = match map.remove("icon") {
Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?,
None => None,
};
let id = map.remove("id")
.ok_or_else(|| DeError::custom("expected guild id"))
.and_then(GuildId::deserialize)
.map_err(DeError::custom)?;
let joined_at = map.remove("joined_at")
.ok_or_else(|| DeError::custom("expected guild joined_at"))
.and_then(DateTime::deserialize)
.map_err(DeError::custom)?;
let large = map.remove("large")
.ok_or_else(|| DeError::custom("expected guild large"))
.and_then(bool::deserialize)
.map_err(DeError::custom)?;
let member_count = map.remove("member_count")
.ok_or_else(|| DeError::custom("expected guild member_count"))
.and_then(u64::deserialize)
.map_err(DeError::custom)?;
let members = map.remove("members")
.ok_or_else(|| DeError::custom("expected guild members"))
.and_then(deserialize_members)
.map_err(DeError::custom)?;
let mfa_level = map.remove("mfa_level")
.ok_or_else(|| DeError::custom("expected guild mfa_level"))
.and_then(MfaLevel::deserialize)
.map_err(DeError::custom)?;
let name = map.remove("name")
.ok_or_else(|| DeError::custom("expected guild name"))
.and_then(String::deserialize)
.map_err(DeError::custom)?;
let owner_id = map.remove("owner_id")
.ok_or_else(|| DeError::custom("expected guild owner_id"))
.and_then(UserId::deserialize)
.map_err(DeError::custom)?;
let presences = map.remove("presences")
.ok_or_else(|| DeError::custom("expected guild presences"))
.and_then(deserialize_presences)
.map_err(DeError::custom)?;
let region = map.remove("region")
.ok_or_else(|| DeError::custom("expected guild region"))
.and_then(String::deserialize)
.map_err(DeError::custom)?;
let roles = map.remove("roles")
.ok_or_else(|| DeError::custom("expected guild roles"))
.and_then(deserialize_roles)
.map_err(DeError::custom)?;
let splash = match map.remove("splash") {
Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?,
None => None,
};
let system_channel_id = match map.remove("system_channel_id") {
Some(v) => Option::<ChannelId>::deserialize(v).map_err(DeError::custom)?,
None => None,
};
let verification_level = map.remove("verification_level")
.ok_or_else(|| DeError::custom("expected guild verification_level"))
.and_then(VerificationLevel::deserialize)
.map_err(DeError::custom)?;
let voice_states = map.remove("voice_states")
.ok_or_else(|| DeError::custom("expected guild voice_states"))
.and_then(deserialize_voice_states)
.map_err(DeError::custom)?;
let description = match map.remove("description") {
Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?,
None => None,
};
let premium_tier = match map.remove("premium_tier") {
Some(v) => PremiumTier::deserialize(v).map_err(DeError::custom)?,
None => PremiumTier::default(),
};
let premium_subscription_count = match map.remove("premium_subscription_count") {
Some(Value::Null) | None => 0,
Some(v) => u64::deserialize(v).map_err(DeError::custom)?,
};
let banner = match map.remove("banner") {
Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?,
None => None,
};
let vanity_url_code = match map.remove("vanity_url_code") {
Some(v) => Option::<String>::deserialize(v).map_err(DeError::custom)?,
None => None,
};
let preferred_locale = map.remove("preferred_locale")
.ok_or_else(|| DeError::custom("expected preferred locale"))
.and_then(String::deserialize)
.map_err(DeError::custom)?;
Ok(Self {
afk_channel_id,
application_id,
afk_timeout,
channels,
default_message_notifications,
emojis,
explicit_content_filter,
features,
icon,
id,
joined_at,
large,
member_count,
members,
mfa_level,
name,
owner_id,
presences,
region,
roles,
splash,
system_channel_id,
verification_level,
voice_states,
description,
premium_tier,
premium_subscription_count,
banner,
vanity_url_code,
preferred_locale,
_nonexhaustive: (),
})
}
}
#[cfg(feature = "model")]
fn contains_case_insensitive(to_look_at: &str, to_find: &str) -> bool {
to_look_at.to_lowercase().contains(&to_find.to_lowercase())
}
#[cfg(feature = "model")]
fn starts_with_case_insensitive(to_look_at: &str, to_find: &str) -> bool {
to_look_at.to_lowercase().starts_with(&to_find.to_lowercase())
}
#[cfg(feature = "model")]
fn closest_to_origin(origin: &str, word_a: &str, word_b: &str) -> std::cmp::Ordering {
let value_a = match word_a.find(origin) {
Some(value) => value + word_a.len(),
None => return std::cmp::Ordering::Greater,
};
let value_b = match word_b.find(origin) {
Some(value) => value + word_b.len(),
None => return std::cmp::Ordering::Less,
};
value_a.cmp(&value_b)
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug)]
pub enum GuildContainer {
Guild(PartialGuild),
Id(GuildId),
#[doc(hidden)]
__Nonexhaustive,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct GuildEmbed {
pub channel_id: ChannelId,
pub enabled: bool,
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct GuildPrune {
pub pruned: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct GuildInfo {
pub id: GuildId,
pub icon: Option<String>,
pub name: String,
pub owner: bool,
pub permissions: Permissions,
}
#[cfg(any(feature = "model", feature = "utils"))]
impl GuildInfo {
pub fn icon_url(&self) -> Option<String> {
self.icon
.as_ref()
.map(|icon| format!(cdn!("/icons/{}/{}.webp"), self.id, icon))
}
}
impl From<PartialGuild> for GuildContainer {
fn from(guild: PartialGuild) -> GuildContainer { GuildContainer::Guild(guild) }
}
impl From<GuildId> for GuildContainer {
fn from(guild_id: GuildId) -> GuildContainer { GuildContainer::Id(guild_id) }
}
impl From<u64> for GuildContainer {
fn from(id: u64) -> GuildContainer { GuildContainer::Id(GuildId(id)) }
}
#[cfg(feature = "model")]
impl InviteGuild {
pub fn splash_url(&self) -> Option<String> {
self.icon
.as_ref()
.map(|icon| format!(cdn!("/splashes/{}/{}.webp"), self.id, icon))
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct GuildUnavailable {
pub id: GuildId,
pub unavailable: bool,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum GuildStatus {
OnlinePartialGuild(PartialGuild),
OnlineGuild(Guild),
Offline(GuildUnavailable),
#[doc(hidden)]
__Nonexhaustive,
}
#[cfg(feature = "model")]
impl GuildStatus {
pub fn id(&self) -> GuildId {
match *self {
GuildStatus::Offline(offline) => offline.id,
GuildStatus::OnlineGuild(ref guild) => guild.id,
GuildStatus::OnlinePartialGuild(ref partial_guild) => partial_guild.id,
GuildStatus::__Nonexhaustive => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum DefaultMessageNotificationLevel {
All = 0,
Mentions = 1,
#[doc(hidden)]
__Nonexhaustive,
}
enum_number!(
DefaultMessageNotificationLevel {
All,
Mentions,
}
);
impl DefaultMessageNotificationLevel {
pub fn num(self) -> u64 {
match self {
DefaultMessageNotificationLevel::All => 0,
DefaultMessageNotificationLevel::Mentions => 1,
DefaultMessageNotificationLevel::__Nonexhaustive => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum ExplicitContentFilter {
None = 0,
WithoutRole = 1,
All = 2,
#[doc(hidden)]
__Nonexhaustive,
}
enum_number!(
ExplicitContentFilter {
None,
WithoutRole,
All,
}
);
impl ExplicitContentFilter {
pub fn num(self) -> u64 {
match self {
ExplicitContentFilter::None => 0,
ExplicitContentFilter::WithoutRole => 1,
ExplicitContentFilter::All => 2,
ExplicitContentFilter::__Nonexhaustive => unreachable!(),
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum MfaLevel {
None = 0,
Elevated = 1,
#[doc(hidden)]
__Nonexhaustive,
}
enum_number!(
MfaLevel {
None,
Elevated,
}
);
impl MfaLevel {
pub fn num(self) -> u64 {
match self {
MfaLevel::None => 0,
MfaLevel::Elevated => 1,
MfaLevel::__Nonexhaustive => unreachable!(),
}
}
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
pub enum Region {
#[serde(rename = "amsterdam")] Amsterdam,
#[serde(rename = "brazil")] Brazil,
#[serde(rename = "eu-central")] EuCentral,
#[serde(rename = "eu-west")] EuWest,
#[serde(rename = "frankfurt")] Frankfurt,
#[serde(rename = "hongkong")] HongKong,
#[serde(rename = "japan")] Japan,
#[serde(rename = "london")] London,
#[serde(rename = "russia")] Russia,
#[serde(rename = "singapore")] Singapore,
#[serde(rename = "sydney")] Sydney,
#[serde(rename = "us-central")] UsCentral,
#[serde(rename = "us-east")] UsEast,
#[serde(rename = "us-south")] UsSouth,
#[serde(rename = "us-west")] UsWest,
#[serde(rename = "vip-amsterdam")] VipAmsterdam,
#[serde(rename = "vip-us-east")] VipUsEast,
#[serde(rename = "vip-us-west")] VipUsWest,
#[doc(hidden)]
__Nonexhaustive,
}
impl Region {
pub fn name(&self) -> &str {
match *self {
Region::Amsterdam => "amsterdam",
Region::Brazil => "brazil",
Region::EuCentral => "eu-central",
Region::EuWest => "eu-west",
Region::Frankfurt => "frankfurt",
Region::HongKong => "hongkong",
Region::Japan => "japan",
Region::London => "london",
Region::Russia => "russia",
Region::Singapore => "singapore",
Region::Sydney => "sydney",
Region::UsCentral => "us-central",
Region::UsEast => "us-east",
Region::UsSouth => "us-south",
Region::UsWest => "us-west",
Region::VipAmsterdam => "vip-amsterdam",
Region::VipUsEast => "vip-us-east",
Region::VipUsWest => "vip-us-west",
Region::__Nonexhaustive => unreachable!(),
}
}
}
#[doc="The level to set as criteria prior to a user being able to send
messages in a [`Guild`].
[`Guild`]: struct.Guild.html"]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum VerificationLevel {
None = 0,
Low = 1,
Medium = 2,
High = 3,
Higher = 4,
#[doc(hidden)]
__Nonexhaustive,
}
enum_number!(
VerificationLevel {
None,
Low,
Medium,
High,
Higher,
}
);
impl VerificationLevel {
pub fn num(self) -> u64 {
match self {
VerificationLevel::None => 0,
VerificationLevel::Low => 1,
VerificationLevel::Medium => 2,
VerificationLevel::High => 3,
VerificationLevel::Higher => 4,
VerificationLevel::__Nonexhaustive => unreachable!(),
}
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "model")]
mod model {
use chrono::prelude::*;
use crate::model::prelude::*;
use std::collections::*;
use std::sync::Arc;
fn gen_user() -> User {
User {
id: UserId(210),
avatar: Some("abc".to_string()),
bot: true,
discriminator: 1432,
name: "test".to_string(),
_nonexhaustive: (),
}
}
fn gen_member() -> Member {
let dt: DateTime<FixedOffset> = FixedOffset::east(5 * 3600)
.ymd(2016, 11, 08)
.and_hms(0, 0, 0);
let vec1 = Vec::new();
let u = Arc::new(RwLock::new(gen_user()));
Member {
deaf: false,
guild_id: GuildId(1),
joined_at: Some(dt),
mute: false,
nick: Some("aaaa".to_string()),
roles: vec1,
user: u,
_nonexhaustive: (),
}
}
fn gen() -> Guild {
let u = gen_user();
let m = gen_member();
let hm1 = HashMap::new();
let hm2 = HashMap::new();
let vec1 = Vec::new();
let dt: DateTime<FixedOffset> = FixedOffset::east(5 * 3600)
.ymd(2016, 11, 08)
.and_hms(0, 0, 0);
let mut hm3 = HashMap::new();
let hm4 = HashMap::new();
let hm5 = HashMap::new();
let hm6 = HashMap::new();
hm3.insert(u.id, m);
let notifications = DefaultMessageNotificationLevel::All;
Guild {
afk_channel_id: Some(ChannelId(0)),
afk_timeout: 0,
channels: hm1,
default_message_notifications: notifications,
emojis: hm2,
features: vec1,
icon: Some("/avatars/210/a_aaa.webp?size=1024".to_string()),
id: GuildId(1),
joined_at: dt,
large: false,
member_count: 1,
members: hm3,
mfa_level: MfaLevel::Elevated,
name: "Spaghetti".to_string(),
owner_id: UserId(210),
presences: hm4,
region: "NA".to_string(),
roles: hm5,
splash: Some("asdf".to_string()),
verification_level: VerificationLevel::None,
voice_states: hm6,
description: None,
premium_tier: PremiumTier::Tier1,
application_id: Some(ApplicationId(0)),
explicit_content_filter: ExplicitContentFilter::None,
system_channel_id: Some(ChannelId(0)),
premium_subscription_count: 12,
banner: None,
vanity_url_code: Some("bruhmoment".to_string()),
preferred_locale: "en-US".to_string(),
_nonexhaustive: (),
}
}
#[test]
fn member_named_username() {
let guild = gen();
let lhs = guild
.member_named("test#1432")
.unwrap()
.display_name();
assert_eq!(lhs, gen_member().display_name());
}
#[test]
fn member_named_nickname() {
let guild = gen();
let lhs = guild.member_named("aaaa").unwrap().display_name();
assert_eq!(lhs, gen_member().display_name());
}
}
}