use std::ops::Deref;
use crate::CacheableMember;
use serde::Serialize;
use twilight_model::user::AvatarDecorationData;
use twilight_model::{
application::interaction::InteractionMember,
gateway::payload::incoming::MemberUpdate,
guild::{Member, MemberFlags, PartialMember},
id::{
Id,
marker::{RoleMarker, UserMarker},
},
util::{ImageHash, Timestamp},
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ComputedInteractionMember {
pub avatar: Option<ImageHash>,
pub deaf: Option<bool>,
pub interaction_member: InteractionMember,
pub mute: Option<bool>,
pub user_id: Id<UserMarker>,
}
impl Deref for ComputedInteractionMember {
type Target = InteractionMember;
fn deref(&self) -> &Self::Target {
&self.interaction_member
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct CachedMember {
pub(crate) avatar: Option<ImageHash>,
pub(crate) avatar_decoration_data: Option<AvatarDecorationData>,
pub(crate) banner: Option<ImageHash>,
pub(crate) communication_disabled_until: Option<Timestamp>,
pub(crate) deaf: Option<bool>,
pub(crate) flags: MemberFlags,
pub(crate) joined_at: Option<Timestamp>,
pub(crate) mute: Option<bool>,
pub(crate) nick: Option<String>,
pub(crate) pending: bool,
pub(crate) premium_since: Option<Timestamp>,
pub(crate) roles: Vec<Id<RoleMarker>>,
pub(crate) user_id: Id<UserMarker>,
}
impl CachedMember {
pub const fn avatar(&self) -> Option<ImageHash> {
self.avatar
}
pub const fn communication_disabled_until(&self) -> Option<Timestamp> {
self.communication_disabled_until
}
pub const fn deaf(&self) -> Option<bool> {
self.deaf
}
pub const fn flags(&self) -> MemberFlags {
self.flags
}
pub const fn joined_at(&self) -> Option<Timestamp> {
self.joined_at
}
pub const fn mute(&self) -> Option<bool> {
self.mute
}
pub fn nick(&self) -> Option<&str> {
self.nick.as_deref()
}
pub const fn pending(&self) -> bool {
self.pending
}
pub const fn premium_since(&self) -> Option<Timestamp> {
self.premium_since
}
#[allow(clippy::missing_const_for_fn)]
pub fn roles(&self) -> &[Id<RoleMarker>] {
&self.roles
}
pub const fn user_id(&self) -> Id<UserMarker> {
self.user_id
}
}
impl From<Member> for CachedMember {
fn from(member: Member) -> Self {
let Member {
avatar,
avatar_decoration_data,
banner,
communication_disabled_until,
deaf,
flags,
joined_at,
mute,
nick,
pending,
premium_since,
roles,
user,
} = member;
Self {
avatar,
avatar_decoration_data,
banner,
communication_disabled_until,
deaf: Some(deaf),
flags,
joined_at,
mute: Some(mute),
nick,
pending,
premium_since,
roles,
user_id: user.id,
}
}
}
impl From<ComputedInteractionMember> for CachedMember {
fn from(member: ComputedInteractionMember) -> Self {
let ComputedInteractionMember {
avatar,
deaf,
mute,
user_id,
interaction_member,
} = member;
let InteractionMember {
avatar: _,
avatar_decoration_data,
banner,
communication_disabled_until,
flags,
joined_at,
nick,
pending,
permissions: _,
premium_since,
roles,
} = interaction_member;
Self {
avatar,
avatar_decoration_data,
banner,
communication_disabled_until,
deaf,
flags,
joined_at,
mute,
nick,
pending,
premium_since,
roles,
user_id,
}
}
}
impl From<(Id<UserMarker>, PartialMember)> for CachedMember {
fn from((user_id, member): (Id<UserMarker>, PartialMember)) -> Self {
let PartialMember {
avatar,
avatar_decoration_data,
banner,
communication_disabled_until,
deaf,
flags,
joined_at,
mute,
nick,
permissions: _,
premium_since,
roles,
user,
} = member;
Self {
avatar,
avatar_decoration_data,
banner,
communication_disabled_until,
deaf: Some(deaf),
flags,
joined_at,
mute: Some(mute),
nick,
pending: false,
premium_since,
roles,
user_id: user.map_or(user_id, |user| user.id),
}
}
}
impl PartialEq<Member> for CachedMember {
fn eq(&self, other: &Member) -> bool {
self.avatar == other.avatar
&& self.communication_disabled_until == other.communication_disabled_until
&& self.deaf == Some(other.deaf)
&& self.joined_at == other.joined_at
&& self.mute == Some(other.mute)
&& self.nick == other.nick
&& self.pending == other.pending
&& self.premium_since == other.premium_since
&& self.roles == other.roles
&& self.user_id == other.user.id
}
}
impl PartialEq<PartialMember> for CachedMember {
fn eq(&self, other: &PartialMember) -> bool {
self.communication_disabled_until == other.communication_disabled_until
&& self.deaf == Some(other.deaf)
&& self.joined_at == other.joined_at
&& self.mute == Some(other.mute)
&& self.nick == other.nick
&& self.premium_since == other.premium_since
&& self.roles == other.roles
}
}
impl PartialEq<InteractionMember> for CachedMember {
fn eq(&self, other: &InteractionMember) -> bool {
self.joined_at == other.joined_at
&& self.nick == other.nick
&& self.premium_since == other.premium_since
&& self.roles == other.roles
}
}
impl CacheableMember for CachedMember {
fn roles(&self) -> &[Id<RoleMarker>] {
&self.roles
}
#[cfg(feature = "permission-calculator")]
fn communication_disabled_until(&self) -> Option<Timestamp> {
self.communication_disabled_until
}
fn avatar(&self) -> Option<ImageHash> {
self.avatar
}
fn deaf(&self) -> Option<bool> {
self.deaf
}
fn mute(&self) -> Option<bool> {
self.mute
}
fn update_with_member_update(&mut self, member_update: &MemberUpdate) {
self.avatar = member_update.avatar;
self.deaf = member_update.deaf.or_else(|| self.deaf());
self.mute = member_update.mute.or_else(|| self.mute());
self.nick.clone_from(&member_update.nick);
self.roles.clone_from(&member_update.roles);
self.joined_at = member_update.joined_at;
self.pending = member_update.pending;
self.communication_disabled_until = member_update.communication_disabled_until;
}
}
#[cfg(test)]
mod tests {
use super::CachedMember;
use static_assertions::assert_fields;
use twilight_model::{
guild::{Member, MemberFlags, PartialMember},
id::Id,
user::User,
util::Timestamp,
};
assert_fields!(
CachedMember: deaf,
joined_at,
mute,
nick,
pending,
premium_since,
roles,
user_id
);
fn cached_member() -> CachedMember {
let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
CachedMember {
avatar: None,
avatar_decoration_data: None,
banner: None,
communication_disabled_until: None,
deaf: Some(false),
flags,
joined_at,
mute: Some(true),
nick: Some("member nick".to_owned()),
pending: false,
premium_since: None,
roles: Vec::new(),
user_id: user().id,
}
}
fn user() -> User {
User {
accent_color: None,
avatar: None,
avatar_decoration: None,
avatar_decoration_data: None,
banner: None,
bot: false,
discriminator: 1,
email: None,
flags: None,
global_name: Some("test".to_owned()),
id: Id::new(1),
locale: None,
mfa_enabled: None,
name: "bar".to_owned(),
premium_type: None,
primary_guild: None,
public_flags: None,
system: None,
verified: None,
}
}
#[test]
fn eq_member() {
let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
let member = Member {
avatar: None,
avatar_decoration_data: None,
banner: None,
communication_disabled_until: None,
deaf: false,
flags,
joined_at,
mute: true,
nick: Some("member nick".to_owned()),
pending: false,
premium_since: None,
roles: Vec::new(),
user: user(),
};
assert_eq!(cached_member(), member);
}
#[test]
fn eq_partial_member() {
let joined_at = Some(Timestamp::from_secs(1_632_072_645).expect("non zero"));
let flags = MemberFlags::BYPASSES_VERIFICATION | MemberFlags::DID_REJOIN;
let member = PartialMember {
avatar: None,
avatar_decoration_data: None,
banner: None,
communication_disabled_until: None,
deaf: false,
flags,
joined_at,
mute: true,
nick: Some("member nick".to_owned()),
permissions: None,
premium_since: None,
roles: Vec::new(),
user: None,
};
assert_eq!(cached_member(), member);
}
}