Skip to content

Commit 2b098af

Browse files
Add support for guild tags, avatar decorations, and nameplates (#3427)
The remainder of #3382, rebased and cleaned up. These features all have to do with user customization, so I think they can be merged together. --------- Co-authored-by: jamesbt365 <[email protected]>
1 parent 78d738a commit 2b098af

File tree

7 files changed

+173
-13
lines changed

7 files changed

+173
-13
lines changed

src/cache/event.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,9 @@ impl CacheUpdate for GuildMemberUpdateEvent {
229229
avatar: self.avatar,
230230
banner: self.banner,
231231
communication_disabled_until: self.communication_disabled_until,
232-
flags: GuildMemberFlags::default(),
232+
flags: self.flags.unwrap_or_default(),
233233
unusual_dm_activity_until: self.unusual_dm_activity_until,
234+
avatar_decoration_data: self.avatar_decoration_data,
234235
});
235236
}
236237

@@ -466,6 +467,7 @@ impl CacheUpdate for PresenceUpdateEvent {
466467
communication_disabled_until: None,
467468
flags: GuildMemberFlags::default(),
468469
unusual_dm_activity_until: None,
470+
avatar_decoration_data: None,
469471
});
470472
}
471473
}

src/gateway/bridge/shard_queuer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl ShardQueuer {
200200
};
201201

202202
spawn_named("shard_queuer::stop", async move {
203-
drop(runner.run().await);
203+
drop(Box::pin(runner.run()).await);
204204
debug!("[ShardRunner {:?}] Stopping", runner.shard.shard_info());
205205
});
206206

src/model/application/interaction.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ pub struct MessageModalSubmitInteractionMetadata {
481481

482482
/// Metadata about the interaction, including the source of the interaction relevant server and
483483
/// user IDs.
484+
#[allow(clippy::large_enum_variant)]
484485
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
485486
#[derive(Clone, Debug)]
486487
#[non_exhaustive]

src/model/event.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,10 @@ pub struct GuildMemberUpdateEvent {
262262
pub avatar: Option<ImageHash>,
263263
pub banner: Option<ImageHash>,
264264
pub communication_disabled_until: Option<Timestamp>,
265+
// This is not documented but present on the event?
265266
pub unusual_dm_activity_until: Option<Timestamp>,
267+
pub flags: Option<GuildMemberFlags>,
268+
pub avatar_decoration_data: Option<AvatarDecorationData>,
266269
}
267270

268271
/// Requires no gateway intents.

src/model/gateway.rs

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -244,16 +244,34 @@ pub struct ClientStatus {
244244
#[non_exhaustive]
245245
pub struct PresenceUser {
246246
pub id: UserId,
247-
pub avatar: Option<ImageHash>,
248-
pub bot: Option<bool>,
247+
#[serde(rename = "username")]
248+
pub name: Option<String>,
249249
#[serde(default, skip_serializing_if = "Option::is_none", with = "discriminator")]
250250
pub discriminator: Option<NonZeroU16>,
251-
pub email: Option<String>,
251+
pub global_name: Option<String>,
252+
pub avatar: Option<ImageHash>,
253+
#[serde(default)]
254+
pub bot: Option<bool>,
255+
#[serde(default)]
256+
pub system: Option<bool>,
257+
#[serde(default)]
252258
pub mfa_enabled: Option<bool>,
253-
#[serde(rename = "username")]
254-
pub name: Option<String>,
259+
pub banner: Option<ImageHash>,
260+
#[serde(rename = "accent_color")]
261+
pub accent_colour: Option<Colour>,
262+
pub locale: Option<String>,
255263
pub verified: Option<bool>,
264+
pub email: Option<String>,
265+
#[serde(default)]
266+
pub flags: Option<UserPublicFlags>,
267+
#[serde(default)]
268+
pub premium_type: Option<PremiumType>,
256269
pub public_flags: Option<UserPublicFlags>,
270+
pub member: Option<Box<PartialMember>>,
271+
pub primary_guild: Option<PrimaryGuild>,
272+
pub avatar_decoration_data: Option<AvatarDecorationData>,
273+
pub collectibles: Option<Collectibles>,
274+
// TODO: should really go over these fields at some point and check them.
257275
}
258276

259277
impl PresenceUser {
@@ -266,20 +284,23 @@ impl PresenceUser {
266284
avatar: self.avatar,
267285
bot: self.bot?,
268286
discriminator: self.discriminator,
269-
global_name: None,
287+
global_name: self.global_name,
270288
id: self.id,
271289
name: self.name?,
272290
public_flags: self.public_flags,
273-
banner: None,
274-
accent_colour: None,
275-
member: None,
291+
banner: self.banner,
292+
accent_colour: self.accent_colour,
293+
member: self.member,
276294
system: false,
277295
mfa_enabled: self.mfa_enabled.unwrap_or_default(),
278-
locale: None,
296+
locale: self.locale,
279297
verified: self.verified,
280298
email: self.email,
281299
flags: self.public_flags.unwrap_or_default(),
282-
premium_type: PremiumType::None,
300+
premium_type: self.premium_type.unwrap_or_default(),
301+
primary_guild: self.primary_guild,
302+
avatar_decoration_data: self.avatar_decoration_data,
303+
collectibles: self.collectibles,
283304
})
284305
}
285306

src/model/guild/member.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ pub struct Member {
6666
///
6767
/// Will be None or a time in the past if the user is not flagged.
6868
pub unusual_dm_activity_until: Option<Timestamp>,
69+
/// Information about this member's guild specific avatar decoration.
70+
pub avatar_decoration_data: Option<AvatarDecorationData>,
6971
}
7072

7173
bitflags! {
@@ -623,6 +625,8 @@ pub struct PartialMember {
623625
pub avatar: Option<ImageHash>,
624626
/// The member's guild banner hash
625627
pub banner: Option<ImageHash>,
628+
/// Information about this member's avatar decoration.
629+
pub avatar_decoration_data: Option<AvatarDecorationData>,
626630
}
627631

628632
impl From<PartialMember> for Member {
@@ -643,6 +647,7 @@ impl From<PartialMember> for Member {
643647
communication_disabled_until: None,
644648
guild_id: partial.guild_id.unwrap_or_default(),
645649
unusual_dm_activity_until: partial.unusual_dm_activity_until,
650+
avatar_decoration_data: partial.avatar_decoration_data,
646651
}
647652
}
648653
}
@@ -663,6 +668,7 @@ impl From<Member> for PartialMember {
663668
unusual_dm_activity_until: member.unusual_dm_activity_until,
664669
avatar: member.avatar,
665670
banner: member.banner,
671+
avatar_decoration_data: member.avatar_decoration_data,
666672
}
667673
}
668674
}

src/model/user.rs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,15 @@ pub struct User {
289289
/// [Discord docs](https://discord.com/developers/docs/topics/gateway-events#message-create-message-create-extra-fields).
290290
// Box required to avoid infinitely recursive types
291291
pub member: Option<Box<PartialMember>>,
292+
/// The primary guild and tag the user has active.
293+
///
294+
/// Note: just because this guild is populated does not mean the tag is visible.
295+
pub primary_guild: Option<PrimaryGuild>,
296+
/// Information about this user's avatar decoration.
297+
pub avatar_decoration_data: Option<AvatarDecorationData>,
298+
/// The collectibles the user currently has active, excluding avatar decorations and profile
299+
/// effects.
300+
pub collectibles: Option<Collectibles>,
292301
}
293302

294303
enum_number! {
@@ -355,6 +364,99 @@ bitflags! {
355364
}
356365
}
357366

367+
/// User's Primary Guild object
368+
///
369+
/// [Discord docs](https://discord.com/developers/docs/resources/user#user-object-user-primary-guild)
370+
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
371+
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
372+
#[non_exhaustive]
373+
pub struct PrimaryGuild {
374+
/// The id of the user's primary guild.
375+
pub identity_guild_id: Option<GuildId>,
376+
/// Whether the user is displaying the primary guild's server tag. This can be null if the
377+
/// system clears the identity, e.g. because the server no longer supports tags.
378+
pub identity_enabled: Option<bool>,
379+
/// The text of the [`User`]'s server tag.
380+
pub tag: Option<String>,
381+
/// The hash of the server badge.
382+
pub badge: Option<ImageHash>,
383+
}
384+
385+
#[cfg(feature = "model")]
386+
impl PrimaryGuild {
387+
#[must_use]
388+
/// Returns the formatted URL of the badge's icon, if one exists.
389+
pub fn badge_url(&self) -> Option<String> {
390+
primary_guild_badge_url(self.identity_guild_id, self.badge.as_ref())
391+
}
392+
}
393+
394+
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
395+
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
396+
#[non_exhaustive]
397+
/// The data for a [`User`]'s avatar decoration.
398+
///
399+
/// [Discord docs](https://discord.com/developers/docs/resources/user#avatar-decoration-data-object).
400+
pub struct AvatarDecorationData {
401+
/// The avatar decoration hash
402+
pub asset: ImageHash,
403+
/// id of the avatar decoration's SKU
404+
pub sku_id: SkuId,
405+
}
406+
407+
#[cfg(feature = "model")]
408+
impl AvatarDecorationData {
409+
#[must_use]
410+
/// Returns the formatted URL of the decoration.
411+
pub fn decoration_url(&self) -> String {
412+
avatar_decoration_url(&self.asset)
413+
}
414+
}
415+
416+
/// The collectibles the user has, excluding Avatar Decorations and Profile Effects.
417+
///
418+
/// [Discord docs](https://discord.com/developers/docs/resources/user#collectibles).
419+
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
420+
#[derive(Clone, Debug, Deserialize, Serialize)]
421+
#[non_exhaustive]
422+
pub struct Collectibles {
423+
/// The [`User`]'s nameplate, if they have one.
424+
pub nameplate: Option<Nameplate>,
425+
}
426+
427+
/// A nameplate, shown on the member list on official clients.
428+
///
429+
/// [Discord docs](https://discord.com/developers/docs/resources/user#nameplate-nameplate-structure).
430+
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
431+
#[derive(Clone, Debug, Deserialize, Serialize)]
432+
#[non_exhaustive]
433+
pub struct Nameplate {
434+
/// Id of the nameplate SKU
435+
pub sku_id: SkuId,
436+
/// Path to the nameplate asset.
437+
pub asset: String,
438+
/// The label of this nameplate.
439+
pub label: String,
440+
/// Background color of the nameplate, one of: `crimson`, `berry`, `sky`, `teal`, `forest`,
441+
/// `bubble_gum`, `violet`, `cobalt`, `clover`, `lemon`, `white`
442+
pub palette: String,
443+
}
444+
445+
#[cfg(all(feature = "unstable_discord_api", feature = "model"))]
446+
impl Nameplate {
447+
/// Gets the static version of the nameplate's url.
448+
#[must_use]
449+
pub fn static_url(&self) -> String {
450+
static_nameplate_url(&self.asset)
451+
}
452+
453+
/// Gets the animated version of the nameplate's url.
454+
#[must_use]
455+
pub fn url(&self) -> String {
456+
nameplate_url(&self.asset)
457+
}
458+
}
459+
358460
use std::hash::{Hash, Hasher};
359461

360462
impl PartialEq for User {
@@ -824,6 +926,31 @@ fn tag(name: &str, discriminator: Option<NonZeroU16>) -> String {
824926
tag
825927
}
826928

929+
#[cfg(feature = "model")]
930+
fn primary_guild_badge_url(guild_id: Option<GuildId>, hash: Option<&ImageHash>) -> Option<String> {
931+
if let Some(guild_id) = guild_id {
932+
return hash.map(|hash| cdn!("/guild-tag-badges/{}/{}.png?size=1024", guild_id, hash));
933+
}
934+
935+
None
936+
}
937+
938+
#[cfg(feature = "model")]
939+
fn avatar_decoration_url(hash: &ImageHash) -> String {
940+
cdn!("/avatar-decoration-presets/{}.png?size=1024", hash)
941+
}
942+
943+
#[cfg(all(feature = "unstable_discord_api", feature = "model"))]
944+
fn nameplate_url(path: &str) -> String {
945+
cdn!("https://cdn.discordapp.com/assets/collectibles/{}/asset.webm", path)
946+
}
947+
948+
#[cfg(all(feature = "unstable_discord_api", feature = "model"))]
949+
#[cfg(feature = "model")]
950+
fn static_nameplate_url(path: &str) -> String {
951+
cdn!("https://cdn.discordapp.com/assets/collectibles/{}/static.png", path)
952+
}
953+
827954
#[cfg(test)]
828955
mod test {
829956
use std::num::NonZeroU16;

0 commit comments

Comments
 (0)