mod emoji;
mod guild_id;
mod integration;
mod member;
mod partial_guild;
mod role;
mod audit_log;
mod premium_tier;
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, Utc};
use crate::model::prelude::*;
use serde::de::Error as DeError;
use super::utils::*;
use futures::stream::StreamExt;
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::Cache;
#[cfg(all(feature = "http", feature = "model"))]
use serde_json::json;
#[cfg(feature = "model")]
use crate::builder::{CreateChannel, EditGuild, EditMember, EditRole};
#[cfg(feature = "model")]
use crate::constants::LARGE_THRESHOLD;
#[cfg(feature = "model")]
use tracing::{error, warn};
#[cfg(feature = "model")]
use crate::http::{Http, CacheHttp};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)]
pub struct Ban {
pub reason: Option<String>,
pub user: User,
}
#[derive(Clone, Debug, Serialize)]
#[non_exhaustive]
pub struct Guild {
pub afk_channel_id: Option<ChannelId>,
pub afk_timeout: u64,
pub application_id: Option<ApplicationId>,
#[serde(serialize_with = "serialize_gen_map")]
pub channels: HashMap<ChannelId, 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<Utc>,
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,
}
#[cfg(feature = "model")]
impl Guild {
#[cfg(feature = "cache")]
async fn check_hierarchy(&self, cache: impl AsRef<Cache>, other_user: UserId) -> Result<()> {
let current_id = cache.as_ref().current_user().await.id;
if let Some(higher) = self.greater_member_hierarchy(&cache, other_user, current_id).await {
if higher != current_id {
return Err(Error::Model(ModelError::Hierarchy));
}
}
Ok(())
}
pub async fn default_channel(&self, uid: UserId) -> Option<&GuildChannel> {
let member = self.members.get(&uid)?;
for channel in self.channels.values() {
if self.user_permissions_in(channel, member).ok()?.read_messages() {
return Some(channel);
}
}
None
}
pub async fn default_channel_guaranteed(&self) -> Option<&GuildChannel> {
for channel in self.channels.values() {
for member in self.members.values() {
if self.user_permissions_in(channel, member).ok()?.read_messages() {
return Some(channel);
}
}
}
None
}
#[cfg(feature = "cache")]
async fn has_perms(&self, cache_http: impl CacheHttp, mut permissions: Permissions) -> bool {
if let Some(cache) = cache_http.cache() {
let user_id = cache.current_user().await.id;
if let Ok(perms) = self.member_permissions(&cache_http, user_id).await {
permissions.remove(perms);
permissions.is_empty()
} else {
false
}
} else {
false
}
}
#[cfg(feature = "cache")]
pub async fn channel_id_from_name(&self, cache: impl AsRef<Cache>, name: impl AsRef<str>) -> Option<ChannelId> {
let name = name.as_ref();
let guild_channels = cache
.as_ref()
.guild_channels(&self.id)
.await?;
for (id, channel) in guild_channels.iter() {
if channel.name == name {
return Some(*id)
}
}
None
}
#[inline]
pub async fn ban(&self, cache_http: impl CacheHttp, user: impl Into<UserId>, dmd: u8) -> Result<()> {
self._ban_with_reason(cache_http, user.into(), dmd, "").await
}
#[inline]
pub async fn ban_with_reason(
&self,
cache_http: impl CacheHttp,
user: impl Into<UserId>,
dmd: u8,
reason: impl AsRef<str>) -> Result<()> {
self._ban_with_reason(cache_http, user.into(), dmd, reason.as_ref()).await
}
async fn _ban_with_reason(&self, cache_http: impl CacheHttp, user: UserId, dmd: u8, reason: &str) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::BAN_MEMBERS;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
self.check_hierarchy(cache, user).await?;
}
}
self.id.ban_with_reason(cache_http.http(), user, dmd, reason).await
}
pub fn banner_url(&self) -> Option<String> {
self.banner
.as_ref()
.map(|banner| format!(cdn!("/banners/{}/{}.webp?size=1024"), self.id, banner))
}
pub async fn bans(&self, cache_http: impl CacheHttp) -> Result<Vec<Ban>> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::BAN_MEMBERS;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.bans(cache_http.http()).await
}
#[inline]
pub async 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).await
}
#[inline]
pub async fn channels(&self, http: impl AsRef<Http>) -> Result<HashMap<ChannelId, GuildChannel>> {
self.id.channels(&http).await
}
pub async 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).await
}
pub async fn create_channel(&self, cache_http: impl CacheHttp, f: impl FnOnce(&mut CreateChannel) -> &mut CreateChannel) -> Result<GuildChannel> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::MANAGE_CHANNELS;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.create_channel(cache_http.http(), f).await
}
#[inline]
pub async fn create_emoji(&self, http: impl AsRef<Http>, name: &str, image: &str) -> Result<Emoji> {
self.id.create_emoji(&http, name, image).await
}
#[inline]
pub async fn create_integration<I>(&self, http: impl AsRef<Http>, integration_id: impl Into<IntegrationId>, kind: &str) -> Result<()> {
self.id.create_integration(&http, integration_id, kind).await
}
pub async fn create_role<F>(&self, cache_http: impl CacheHttp, f: F) -> Result<Role>
where F: FnOnce(&mut EditRole) -> &mut EditRole
{
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::MANAGE_ROLES;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.create_role(cache_http.http(), f).await
}
pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<PartialGuild> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.owner_id != cache.current_user().await.id {
let req = Permissions::MANAGE_GUILD;
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.delete(cache_http.http()).await
}
#[inline]
pub async fn delete_emoji(&self, http: impl AsRef<Http>, emoji_id: impl Into<EmojiId>) -> Result<()> {
self.id.delete_emoji(&http, emoji_id).await
}
#[inline]
pub async fn delete_integration(&self, http: impl AsRef<Http>, integration_id: impl Into<IntegrationId>) -> Result<()> {
self.id.delete_integration(&http, integration_id).await
}
#[inline]
pub async fn delete_role(&self, http: impl AsRef<Http>, role_id: impl Into<RoleId>) -> Result<()> {
self.id.delete_role(&http, role_id).await
}
pub async fn edit<F>(&mut self, cache_http: impl CacheHttp, f: F) -> Result<()>
where F: FnOnce(&mut EditGuild) -> &mut EditGuild
{
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::MANAGE_GUILD;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
match self.id.edit(cache_http.http(), f).await {
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),
}
}
#[inline]
pub async fn edit_emoji(&self, http: impl AsRef<Http>, emoji_id: impl Into<EmojiId>, name: &str) -> Result<Emoji> {
self.id.edit_emoji(&http, emoji_id, name).await
}
#[inline]
pub async fn edit_member<F>(&self, http: impl AsRef<Http>, user_id: impl Into<UserId>, f: F) -> Result<Member>
where F: FnOnce(&mut EditMember) -> &mut EditMember
{
self.id.edit_member(&http, user_id, f).await
}
pub async fn edit_nickname(&self, cache_http: impl CacheHttp, new_nickname: Option<&str>) -> Result<()> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::CHANGE_NICKNAME;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.edit_nickname(cache_http.http(), new_nickname).await
}
#[inline]
pub async fn edit_role<F>(&self, http: impl AsRef<Http>, role_id: impl Into<RoleId>, f: F) -> Result<Role>
where F: FnOnce(&mut EditRole) -> &mut EditRole
{
self.id.edit_role(&http, role_id, f).await
}
#[inline]
pub async fn edit_role_position(
&self,
http: impl AsRef<Http>,
role_id: impl Into<RoleId>,
position: u64
) -> Result<Vec<Role>> {
self.id.edit_role_position(&http, role_id, position).await
}
#[inline]
pub async fn get(http: impl AsRef<Http>, guild_id: impl Into<GuildId>) -> Result<PartialGuild> {
guild_id.into().to_partial_guild(&http).await
}
#[cfg(feature = "cache")]
#[inline]
pub async fn greater_member_hierarchy(
&self,
cache: impl AsRef<Cache>,
lhs_id: impl Into<UserId>,
rhs_id: impl Into<UserId>
) -> Option<UserId> {
self._greater_member_hierarchy(&cache, lhs_id.into(), rhs_id.into()).await
}
#[cfg(feature = "cache")]
async fn _greater_member_hierarchy(
&self,
cache: impl AsRef<Cache>,
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)
.await
.unwrap_or((RoleId(0), 0));
let rhs = self.members.get(&rhs_id)?
.highest_role_info(&cache)
.await
.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| {
let ext = if icon.starts_with("a_") {
"gif"
} else {
"webp"
};
format!(cdn!("/icons/{}/{}.{}"), self.id, icon, ext)
})
}
#[inline]
pub async fn emojis(&self, http: impl AsRef<Http>) -> Result<Vec<Emoji>> {
self.id.emojis(http).await
}
#[inline]
pub async fn emoji(&self, http: impl AsRef<Http>, emoji_id: EmojiId) -> Result<Emoji> {
self.id.emoji(http, emoji_id).await
}
#[inline]
pub async fn integrations(&self, http: impl AsRef<Http>) -> Result<Vec<Integration>> {
self.id.integrations(&http).await
}
pub async fn invites(&self, cache_http: impl CacheHttp) -> Result<Vec<RichInvite>> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::MANAGE_GUILD;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.invites(cache_http.http()).await
}
#[inline]
pub fn is_large(&self) -> bool { self.members.len() > LARGE_THRESHOLD as usize }
#[inline]
pub async fn kick(&self, http: impl AsRef<Http>, user_id: impl Into<UserId>) -> Result<()> {
self.id.kick(&http, user_id).await
}
#[inline]
pub async fn kick_with_reason(
&self,
http: impl AsRef<Http>,
user_id: impl Into<UserId>,
reason: &str
) -> Result<()> {
self.id.kick_with_reason(&http, user_id, reason).await
}
#[inline]
pub async fn leave(&self, http: impl AsRef<Http>) -> Result<()> {
self.id.leave(&http).await
}
#[inline]
pub async fn member(&self, cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> Result<Member> {
self.id.member(cache_http, user_id).await
}
#[inline]
pub async fn members(
&self,
http: impl AsRef<Http>,
limit: Option<u64>,
after: impl Into<Option<UserId>>
) -> Result<Vec<Member>> {
self.id.members(&http, limit, after).await
}
pub fn members_with_status(&self, status: OnlineStatus) -> Vec<&Member> {
let mut members = vec![];
for (&id, member) in &self.members {
if let Some(presence) = self.presences.get(&id) {
if status == presence.status {
members.push(member);
}
}
}
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 = (
split.0.get(0..split.0.len() - 1).unwrap_or(""),
split.1,
);
match split2.1.parse::<u16>() {
Ok(discrim_int) => (split2.0, Some(discrim_int)),
Err(_) => (name, None),
}
} else {
(&name[..], None)
};
for member in self.members.values() {
let name_matches = member.user.name == name;
let discrim_matches = match discrim {
Some(discrim) => member.user.discriminator == discrim,
None => true,
};
if name_matches && discrim_matches {
return Some(member);
}
}
self.members
.values()
.find(|member| member.nick.as_ref().map_or(false, |nick| nick == name))
}
pub async fn members_starting_with(&self, prefix: &str, case_sensitive: bool, sorted: bool) -> Vec<(&Member, String)> {
fn starts_with(prefix: &str, case_sensitive: bool, name: &str) -> bool {
case_sensitive && name.starts_with(prefix)
|| !case_sensitive && starts_with_case_insensitive(name, prefix)
}
let mut members = futures::stream::iter(self.members.values())
.filter_map(|member| async move {
let username = &member.user.name;
if starts_with(prefix, case_sensitive, username) {
Some((member, username.to_string()))
} else {
match member.nick {
Some(ref nick) => {
if starts_with(prefix, case_sensitive, nick) {
Some((member, nick.to_string()))
} else {
None
}
},
None => None,
}
}
}).collect::<Vec<(&Member, String)>>()
.await;
if sorted {
members
.sort_by(|a, b| {
closest_to_origin(prefix, &a.1[..], &b.1[..])
});
members
} else {
members
}
}
pub async fn members_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<(&Member, String)> {
fn contains(substring: &str, case_sensitive: bool, name: &str) -> bool {
case_sensitive && name.contains(substring)
|| !case_sensitive && contains_case_insensitive(name, substring)
}
let mut members = futures::stream::iter(self.members
.values())
.filter_map(|member| async move {
let username = &member.user.name;
if contains(substring, case_sensitive, username) {
Some((member, username.to_string()))
} else {
match member.nick {
Some(ref nick) => {
if contains(substring, case_sensitive, nick) {
Some((member, nick.to_string()))
} else {
None
}
},
None => None
}
}
}).collect::<Vec<(&Member, String)>>()
.await;
if sorted {
members
.sort_by(|a, b| {
closest_to_origin(substring, &a.1[..], &b.1[..])
});
members
} else {
members
}
}
pub async fn members_username_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<(&Member, String)> {
let mut members = futures::stream::iter(self.members
.values())
.filter_map(|member| async move {
if case_sensitive {
let name = &member.user.name;
if name.contains(substring) {
Some((member, name.to_string()))
} else {
None
}
} else {
let name = &member.user.name;
if contains_case_insensitive(name, substring) {
Some((member, name.to_string()))
} else {
None
}
}
}).collect::<Vec<(&Member, String)>>()
.await;
if sorted {
members
.sort_by(|a, b| {
closest_to_origin(substring, &a.1[..], &b.1[..])
});
members
} else {
members
}
}
pub async fn members_nick_containing(&self, substring: &str, case_sensitive: bool, sorted: bool) -> Vec<(&Member, String)> {
let mut members = futures::stream::iter(self.members
.values())
.filter_map(|member| async move {
let nick = match member.nick {
Some(ref nick) => nick.to_string(),
None => member.user.name.to_string(),
};
if case_sensitive && nick.contains(substring)
|| !case_sensitive && contains_case_insensitive(&nick, substring) {
Some((member, nick))
} else {
None
}
}).collect::<Vec<(&Member, String)>>()
.await;
if sorted {
members
.sort_by(|a, b| {
closest_to_origin(substring, &a.1[..], &b.1[..])
});
members
} else {
members
}
}
#[inline]
#[cfg(feature = "cache")]
pub async fn member_permissions(&self, cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> Result<Permissions> {
self._member_permissions(cache_http, user_id.into()).await
}
#[cfg(feature = "cache")]
async fn _member_permissions(&self, cache_http: impl CacheHttp, user_id: UserId) -> Result<Permissions> {
if user_id == self.owner_id {
return Ok(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 Ok(Permissions::empty());
},
};
let member = self.member(cache_http, &user_id).await?;
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 Ok(Permissions::all());
}
permissions |= role.permissions;
} else {
warn!(
"(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}",
member.user.id,
self.id,
role,
);
}
}
Ok(permissions)
}
#[inline]
pub async fn move_member(&self, http: impl AsRef<Http>, user_id: impl Into<UserId>, channel_id: impl Into<ChannelId>) -> Result<Member> {
self.id.move_member(&http, user_id, channel_id).await
}
#[inline]
pub fn user_permissions_in(&self, channel: &GuildChannel, member: &Member) -> Result<Permissions> {
Self::_user_permissions_in(
channel,
member,
&self.roles,
self.owner_id,
self.id
)
}
pub(crate) fn _user_permissions_in(
channel: &GuildChannel,
member: &Member,
roles: &HashMap<RoleId, Role>,
owner_id: UserId,
guild_id: GuildId
) -> Result<Permissions> {
if member.user.id == owner_id {
return Ok(Self::remove_unnecessary_voice_permissions(channel, Permissions::all()));
}
let everyone = match roles.get(&RoleId(guild_id.0)) {
Some(everyone) => everyone,
None => {
error!(
"(╯°□°)╯︵ ┻━┻ @everyone role missing in {}",
guild_id,
);
return Err(Error::Model(ModelError::RoleNotFound));
},
};
let mut permissions = everyone.permissions;
for &role in &member.roles {
if let Some(role) = roles.get(&role) {
permissions |= role.permissions;
} else {
error!(
"(╯°□°)╯︵ ┻━┻ {} on {} has non-existent role {:?}",
member.user.id,
guild_id,
role
);
return Err(Error::Model(ModelError::RoleNotFound));
}
}
if permissions.contains(Permissions::ADMINISTRATOR) {
return Ok(Self::remove_unnecessary_voice_permissions(channel, Permissions::all()));
}
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 != guild_id.0 && !member.roles.contains(&role) {
continue;
}
if let Some(role) = 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(member.user.id) != overwrite.kind {
continue;
}
permissions = (permissions & !overwrite.deny) | overwrite.allow;
}
if channel.id.0 == guild_id.0 {
permissions |= Permissions::READ_MESSAGES;
}
Self::remove_unusable_permissions(&mut permissions);
Ok(permissions)
}
#[inline]
pub fn role_permissions_in(&self, channel: &GuildChannel, role: &Role) -> Result<Permissions> {
Self::_role_permissions_in(channel, role, self.id)
}
pub(crate) fn _role_permissions_in(
channel: &GuildChannel,
role: &Role,
guild_id: GuildId
) -> Result<Permissions> {
if role.guild_id != guild_id || channel.guild_id != guild_id {
return Err(Error::Model(ModelError::WrongGuild));
}
let mut permissions = role.permissions;
if permissions.contains(Permissions::ADMINISTRATOR) {
return Ok(Self::remove_unnecessary_voice_permissions(channel, Permissions::all()));
}
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;
}
}
}
Self::remove_unusable_permissions(&mut permissions);
Ok(permissions)
}
pub async fn prune_count(&self, cache_http: impl CacheHttp, days: u16) -> Result<GuildPrune> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::KICK_MEMBERS;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.prune_count(cache_http.http(), days).await
}
pub(crate) fn remove_unusable_permissions(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);
}
}
pub(crate) fn remove_unnecessary_voice_permissions(channel: &GuildChannel, mut permissions: Permissions) -> Permissions {
if channel.kind == ChannelType::Text {
permissions &= !(Permissions::CONNECT
| Permissions::SPEAK
| Permissions::MUTE_MEMBERS
| Permissions::DEAFEN_MEMBERS
| Permissions::MOVE_MEMBERS
| Permissions::USE_VAD
| Permissions::STREAM);
}
permissions
}
#[inline]
pub async fn reorder_channels<It>(&self, http: impl AsRef<Http>, channels: It) -> Result<()>
where It: IntoIterator<Item = (ChannelId, u64)>
{
self.id.reorder_channels(&http, channels).await
}
#[cfg(all(feature = "cache", feature = "utils"))]
#[inline]
pub async fn shard_id(&self, cache: impl AsRef<Cache>) -> u64 {
self.id.shard_id(&cache).await
}
#[cfg(all(feature = "utils", not(feature = "cache")))]
#[inline]
pub async fn shard_id(&self, shard_count: u64) -> u64 { self.id.shard_id(shard_count).await }
pub fn splash_url(&self) -> Option<String> {
self.splash
.as_ref()
.map(|splash| format!(cdn!("/splashes/{}/{}.webp?size=4096"), self.id, splash))
}
#[inline]
pub async fn start_integration_sync(&self, http: impl AsRef<Http>, integration_id: impl Into<IntegrationId>) -> Result<()> {
self.id.start_integration_sync(&http, integration_id).await
}
pub async fn start_prune(&self, cache_http: impl CacheHttp, days: u16) -> Result<GuildPrune> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::KICK_MEMBERS;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.start_prune(cache_http.http(), days).await
}
pub async fn unban(&self, cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> Result<()> {
#[cfg(feature = "cache")]
{
if cache_http.cache().is_some() {
let req = Permissions::BAN_MEMBERS;
if !self.has_perms(&cache_http, req).await {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
self.id.unban(&cache_http.http(), user_id).await
}
#[inline]
pub async fn vanity_url(&self, http: impl AsRef<Http>) -> Result<String> {
self.id.vanity_url(&http).await
}
#[inline]
pub async fn webhooks(&self, http: impl AsRef<Http>) -> Result<Vec<Webhook>> {
self.id.webhooks(&http).await
}
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)));
}
}
}
if let Some(array) = map.get_mut("roles").and_then(|x| x.as_array_mut()) {
for value in array {
if let Some(role) = value.as_object_mut() {
role
.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,
})
}
}
#[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)]
#[non_exhaustive]
pub enum GuildContainer {
Guild(PartialGuild),
Id(GuildId),
}
#[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| {
let ext = if icon.starts_with("a_") {
"gif"
} else {
"webp"
};
format!(cdn!("/icons/{}/{}.{}"), self.id, icon, ext)
})
}
}
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.splash
.as_ref()
.map(|splash| format!(cdn!("/splashes/{}/{}.webp?size=4096"), self.id, splash))
}
}
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
pub struct GuildUnavailable {
pub id: GuildId,
#[serde(default)]
pub unavailable: bool,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
#[serde(untagged)]
pub enum GuildStatus {
OnlinePartialGuild(PartialGuild),
OnlineGuild(Guild),
Offline(GuildUnavailable),
}
#[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,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum DefaultMessageNotificationLevel {
All = 0,
Mentions = 1,
}
enum_number!(
DefaultMessageNotificationLevel {
All,
Mentions,
}
);
impl DefaultMessageNotificationLevel {
pub fn num(self) -> u64 {
match self {
DefaultMessageNotificationLevel::All => 0,
DefaultMessageNotificationLevel::Mentions => 1,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum ExplicitContentFilter {
None = 0,
WithoutRole = 1,
All = 2,
}
enum_number!(
ExplicitContentFilter {
None,
WithoutRole,
All,
}
);
impl ExplicitContentFilter {
pub fn num(self) -> u64 {
match self {
ExplicitContentFilter::None => 0,
ExplicitContentFilter::WithoutRole => 1,
ExplicitContentFilter::All => 2,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum MfaLevel {
None = 0,
Elevated = 1,
}
enum_number!(
MfaLevel {
None,
Elevated,
}
);
impl MfaLevel {
pub fn num(self) -> u64 {
match self {
MfaLevel::None => 0,
MfaLevel::Elevated => 1,
}
}
}
#[derive(Copy, Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)]
#[non_exhaustive]
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,
}
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",
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum VerificationLevel {
None = 0,
Low = 1,
Medium = 2,
High = 3,
Higher = 4,
}
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,
}
}
}
#[cfg(test)]
mod test {
#[cfg(feature = "model")]
mod model {
use chrono::prelude::*;
use crate::model::prelude::*;
use std::collections::*;
fn gen_user() -> User {
User {
id: UserId(210),
avatar: Some("abc".to_string()),
bot: true,
discriminator: 1432,
name: "test".to_string(),
}
}
fn gen_member() -> Member {
let dt: DateTime<Utc> = FixedOffset::east(5 * 3600)
.ymd(2016, 11, 08)
.and_hms(0, 0, 0)
.with_timezone(&Utc);
let vec1 = Vec::new();
let u = gen_user();
Member {
deaf: false,
guild_id: GuildId(1),
joined_at: Some(dt),
mute: false,
nick: Some("aaaa".to_string()),
roles: vec1,
user: u,
}
}
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<Utc> = FixedOffset::east(5 * 3600)
.ymd(2016, 11, 08)
.and_hms(0, 0, 0)
.with_timezone(&Utc);
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(),
}
}
#[tokio::test]
async fn member_named_username() {
let guild = gen();
let lhs = guild
.member_named("test#1432")
.unwrap()
.display_name();
assert_eq!(lhs, gen_member().display_name());
}
#[tokio::test]
async fn member_named_nickname() {
let guild = gen();
let lhs = guild.member_named("aaaa").unwrap().display_name();
assert_eq!(lhs, gen_member().display_name());
}
}
}