#[cfg(feature = "model")]
use std::fmt::Display;
#[cfg(all(feature = "cache", feature = "model"))]
use std::fmt::Write;
#[cfg(all(feature = "model", feature = "utils"))]
use crate::builder::{Builder, CreateAllowedMentions, CreateMessage, EditMessage};
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::{Cache, GuildRef};
#[cfg(feature = "collector")]
use crate::collector::{
ComponentInteractionCollector,
ModalInteractionCollector,
ReactionCollector,
};
#[cfg(feature = "model")]
use crate::constants;
#[cfg(feature = "collector")]
use crate::gateway::ShardMessenger;
#[cfg(feature = "model")]
use crate::http::{CacheHttp, Http};
use crate::model::prelude::*;
use crate::model::utils::{deserialize_components, discord_colours, StrOrInt};
#[cfg(all(feature = "model", feature = "cache"))]
use crate::utils;
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Message {
pub id: MessageId,
pub channel_id: ChannelId,
pub author: User,
pub content: String,
pub timestamp: Timestamp,
pub edited_timestamp: Option<Timestamp>,
pub tts: bool,
pub mention_everyone: bool,
pub mentions: Vec<User>,
pub mention_roles: Vec<RoleId>,
#[serde(default = "Vec::new")]
pub mention_channels: Vec<ChannelMention>,
pub attachments: Vec<Attachment>,
pub embeds: Vec<Embed>,
#[serde(default)]
pub reactions: Vec<MessageReaction>,
#[serde(default)]
pub nonce: Option<Nonce>,
pub pinned: bool,
pub webhook_id: Option<WebhookId>,
#[serde(rename = "type")]
pub kind: MessageType,
pub activity: Option<MessageActivity>,
pub application: Option<MessageApplication>,
pub application_id: Option<ApplicationId>,
pub message_reference: Option<MessageReference>,
pub flags: Option<MessageFlags>,
pub referenced_message: Option<Box<Message>>, #[serde(default, deserialize_with = "deserialize_snapshots")]
pub message_snapshots: Vec<MessageSnapshot>,
#[cfg_attr(not(ignore_serenity_deprecated), deprecated = "Use interaction_metadata")]
pub interaction: Option<Box<MessageInteraction>>,
pub interaction_metadata: Option<Box<MessageInteractionMetadata>>,
pub thread: Option<GuildChannel>,
#[serde(default, deserialize_with = "deserialize_components")]
pub components: Vec<ActionRow>,
#[serde(default)]
pub sticker_items: Vec<StickerItem>,
pub position: Option<u64>,
pub role_subscription_data: Option<RoleSubscriptionData>,
pub guild_id: Option<GuildId>,
pub member: Option<Box<PartialMember>>,
pub poll: Option<Box<Poll>>,
}
#[cfg(feature = "model")]
impl Message {
pub async fn crosspost(&self, cache_http: impl CacheHttp) -> Result<Message> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.author.id != cache.current_user().id && self.guild_id.is_some() {
utils::user_has_perms_cache(
cache,
self.channel_id,
Permissions::MANAGE_MESSAGES,
)?;
}
}
}
if let Some(flags) = self.flags {
if flags.contains(MessageFlags::CROSSPOSTED) {
return Err(Error::Model(ModelError::MessageAlreadyCrossposted));
} else if flags.contains(MessageFlags::IS_CROSSPOST)
|| self.kind != MessageType::Regular
{
return Err(Error::Model(ModelError::CannotCrosspostMessage));
}
}
self.channel_id.crosspost(cache_http.http(), self.id).await
}
#[inline]
pub async fn channel(&self, cache_http: impl CacheHttp) -> Result<Channel> {
self.channel_id.to_channel(cache_http).await
}
#[cfg(feature = "cache")]
#[deprecated = "Check Message::author is equal to Cache::current_user"]
pub fn is_own(&self, cache: impl AsRef<Cache>) -> bool {
self.author.id == cache.as_ref().current_user().id
}
#[cfg(feature = "cache")]
pub fn author_permissions(&self, cache: impl AsRef<Cache>) -> Option<Permissions> {
let Some(guild_id) = self.guild_id else {
return Some(Permissions::dm_permissions());
};
let guild = cache.as_ref().guild(guild_id)?;
let (channel, is_thread) = if let Some(channel) = guild.channels.get(&self.channel_id) {
(channel, false)
} else if let Some(thread) = guild.threads.iter().find(|th| th.id == self.channel_id) {
(thread, true)
} else {
return None;
};
let mut permissions = if let Some(member) = &self.member {
guild.partial_member_permissions_in(channel, self.author.id, member)
} else {
guild.user_permissions_in(channel, guild.members.get(&self.author.id)?)
};
if is_thread {
permissions.set(Permissions::SEND_MESSAGES, permissions.send_messages_in_threads());
}
Some(permissions)
}
pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.author.id != cache.current_user().id {
utils::user_has_perms_cache(
cache,
self.channel_id,
Permissions::MANAGE_MESSAGES,
)?;
}
}
}
self.channel_id.delete_message(cache_http.http(), self.id).await
}
pub async fn delete_reactions(&self, cache_http: impl CacheHttp) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
utils::user_has_perms_cache(cache, self.channel_id, Permissions::MANAGE_MESSAGES)?;
}
}
self.channel_id.delete_reactions(cache_http.http(), self.id).await
}
#[inline]
pub async fn delete_reaction(
&self,
http: impl AsRef<Http>,
user_id: Option<UserId>,
reaction_type: impl Into<ReactionType>,
) -> Result<()> {
self.channel_id.delete_reaction(http, self.id, user_id, reaction_type).await
}
pub async fn delete_reaction_emoji(
&self,
cache_http: impl CacheHttp,
reaction_type: impl Into<ReactionType>,
) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
utils::user_has_perms_cache(cache, self.channel_id, Permissions::MANAGE_MESSAGES)?;
}
}
cache_http
.http()
.as_ref()
.delete_message_reaction_emoji(self.channel_id, self.id, &reaction_type.into())
.await
}
pub async fn edit(&mut self, cache_http: impl CacheHttp, builder: EditMessage) -> Result<()> {
if let Some(flags) = self.flags {
if flags.contains(MessageFlags::IS_VOICE_MESSAGE) {
return Err(Error::Model(ModelError::CannotEditVoiceMessage));
}
}
*self =
builder.execute(cache_http, (self.channel_id, self.id, Some(self.author.id))).await?;
Ok(())
}
#[cfg(feature = "cache")]
pub fn content_safe(&self, cache: impl AsRef<Cache>) -> String {
let mut result = self.content.clone();
for u in &self.mentions {
let mut at_distinct = String::with_capacity(38);
at_distinct.push('@');
at_distinct.push_str(&u.name);
if let Some(discriminator) = u.discriminator {
at_distinct.push('#');
write!(at_distinct, "{:04}", discriminator.get())
.expect("writing to a string should never fail");
}
let mut m = u.mention().to_string();
if !result.contains(&m) {
m.insert(2, '!');
}
result = result.replace(&m, &at_distinct);
}
if let Some(guild_id) = self.guild_id {
for id in &self.mention_roles {
let mention = id.mention().to_string();
if let Some(guild) = cache.as_ref().guild(guild_id) {
if let Some(role) = guild.roles.get(id) {
result = result.replace(&mention, &format!("@{}", role.name));
continue;
}
}
result = result.replace(&mention, "@deleted-role");
}
}
result.replace("@everyone", "@\u{200B}everyone").replace("@here", "@\u{200B}here")
}
#[inline]
pub async fn reaction_users(
&self,
http: impl AsRef<Http>,
reaction_type: impl Into<ReactionType>,
limit: Option<u8>,
after: impl Into<Option<UserId>>,
) -> Result<Vec<User>> {
self.channel_id.reaction_users(http, self.id, reaction_type, limit, after).await
}
#[cfg(feature = "cache")]
pub fn guild<'a>(&self, cache: &'a Cache) -> Option<GuildRef<'a>> {
cache.guild(self.guild_id?)
}
#[inline]
#[must_use]
#[deprecated = "Check if guild_id is None if the message is received from the gateway."]
pub fn is_private(&self) -> bool {
self.guild_id.is_none()
}
pub async fn member(&self, cache_http: impl CacheHttp) -> Result<Member> {
match self.guild_id {
Some(guild_id) => guild_id.member(cache_http, self.author.id).await,
None => Err(Error::Model(ModelError::ItemMissing)),
}
}
#[must_use]
pub fn overflow_length(content: &str) -> Option<usize> {
crate::builder::check_overflow(content.chars().count(), constants::MESSAGE_CODE_LIMIT).err()
}
pub async fn pin(&self, cache_http: impl CacheHttp) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.guild_id.is_some() {
utils::user_has_perms_cache(
cache,
self.channel_id,
Permissions::MANAGE_MESSAGES,
)?;
}
}
}
self.channel_id.pin(cache_http.http(), self.id).await
}
#[inline]
pub async fn react(
&self,
cache_http: impl CacheHttp,
reaction_type: impl Into<ReactionType>,
) -> Result<Reaction> {
self.react_(cache_http, reaction_type.into()).await
}
async fn react_(
&self,
cache_http: impl CacheHttp,
reaction_type: ReactionType,
) -> Result<Reaction> {
#[cfg_attr(not(feature = "cache"), allow(unused_mut))]
let mut user_id = None;
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.guild_id.is_some() {
utils::user_has_perms_cache(
cache,
self.channel_id,
Permissions::ADD_REACTIONS,
)?;
}
user_id = Some(cache.current_user().id);
}
}
cache_http.http().create_reaction(self.channel_id, self.id, &reaction_type).await?;
Ok(Reaction {
channel_id: self.channel_id,
emoji: reaction_type,
message_id: self.id,
user_id,
guild_id: self.guild_id,
member: self.member.as_deref().map(|member| member.clone().into()),
message_author_id: None,
burst: false,
burst_colours: None,
reaction_type: ReactionTypes::Normal,
})
}
#[inline]
pub async fn reply(
&self,
cache_http: impl CacheHttp,
content: impl Into<String>,
) -> Result<Message> {
self.reply_(cache_http, content, Some(false)).await
}
#[inline]
pub async fn reply_ping(
&self,
cache_http: impl CacheHttp,
content: impl Into<String>,
) -> Result<Message> {
self.reply_(cache_http, content, Some(true)).await
}
#[inline]
pub async fn reply_mention(
&self,
cache_http: impl CacheHttp,
content: impl Display,
) -> Result<Message> {
self.reply_(cache_http, format!("{} {content}", self.author.mention()), None).await
}
async fn reply_(
&self,
cache_http: impl CacheHttp,
content: impl Into<String>,
inlined: Option<bool>,
) -> Result<Message> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.guild_id.is_some() {
utils::user_has_perms_cache(
cache,
self.channel_id,
Permissions::SEND_MESSAGES,
)?;
}
}
}
let mut builder = CreateMessage::new().content(content);
if let Some(ping_user) = inlined {
let allowed_mentions = CreateAllowedMentions::new()
.replied_user(ping_user)
.everyone(true)
.all_users(true)
.all_roles(true);
builder = builder.reference_message(self).allowed_mentions(allowed_mentions);
}
self.channel_id.send_message(cache_http, builder).await
}
#[inline]
pub fn mentions_user_id(&self, id: impl Into<UserId>) -> bool {
let id = id.into();
self.mentions.iter().any(|mentioned_user| mentioned_user.id == id)
}
#[inline]
#[must_use]
pub fn mentions_user(&self, user: &User) -> bool {
self.mentions_user_id(user.id)
}
pub async fn mentions_me(&self, cache_http: impl CacheHttp) -> Result<bool> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
return Ok(self.mentions_user_id(cache.current_user().id));
}
}
let current_user = cache_http.http().get_current_user().await?;
Ok(self.mentions_user_id(current_user.id))
}
pub async fn unpin(&self, cache_http: impl CacheHttp) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.guild_id.is_some() {
utils::user_has_perms_cache(
cache,
self.channel_id,
Permissions::MANAGE_MESSAGES,
)?;
}
}
}
cache_http.http().unpin_message(self.channel_id, self.id, None).await
}
pub async fn end_poll(&self, http: impl AsRef<Http>) -> Result<Self> {
self.channel_id.end_poll(http, self.id).await
}
#[inline]
pub async fn author_nick(&self, cache_http: impl CacheHttp) -> Option<String> {
self.author.nick_in(cache_http, self.guild_id?).await
}
#[inline]
#[must_use]
pub fn link(&self) -> String {
self.id.link(self.channel_id, self.guild_id)
}
#[inline]
#[allow(deprecated)]
#[deprecated = "Use Self::link if Message was recieved via an event, otherwise use MessageId::link to provide the guild_id yourself."]
pub async fn link_ensured(&self, cache_http: impl CacheHttp) -> String {
self.id.link_ensured(cache_http, self.channel_id, self.guild_id).await
}
#[cfg(feature = "collector")]
pub fn await_reaction(&self, shard_messenger: impl AsRef<ShardMessenger>) -> ReactionCollector {
ReactionCollector::new(shard_messenger).message_id(self.id)
}
#[cfg(feature = "collector")]
pub fn await_reactions(
&self,
shard_messenger: impl AsRef<ShardMessenger>,
) -> ReactionCollector {
self.await_reaction(shard_messenger)
}
#[cfg(feature = "collector")]
pub fn await_component_interaction(
&self,
shard_messenger: impl AsRef<ShardMessenger>,
) -> ComponentInteractionCollector {
ComponentInteractionCollector::new(shard_messenger).message_id(self.id)
}
#[cfg(feature = "collector")]
pub fn await_component_interactions(
&self,
shard_messenger: impl AsRef<ShardMessenger>,
) -> ComponentInteractionCollector {
self.await_component_interaction(shard_messenger)
}
#[cfg(feature = "collector")]
pub fn await_modal_interaction(
&self,
shard_messenger: impl AsRef<ShardMessenger>,
) -> ModalInteractionCollector {
ModalInteractionCollector::new(shard_messenger).message_id(self.id)
}
#[cfg(feature = "collector")]
pub fn await_modal_interactions(
&self,
shard_messenger: impl AsRef<ShardMessenger>,
) -> ModalInteractionCollector {
self.await_modal_interaction(shard_messenger)
}
pub async fn category_id(&self, cache_http: impl CacheHttp) -> Option<ChannelId> {
#[cfg(feature = "cache")]
if let Some(cache) = cache_http.cache() {
if let Some(guild) = cache.guild(self.guild_id?) {
let channel = guild.channels.get(&self.channel_id)?;
return if channel.thread_metadata.is_some() {
let thread_parent = guild.channels.get(&channel.parent_id?)?;
thread_parent.parent_id
} else {
channel.parent_id
};
}
}
let channel = self.channel_id.to_channel(&cache_http).await.ok()?.guild()?;
if channel.thread_metadata.is_some() {
let thread_parent = channel.parent_id?.to_channel(cache_http).await.ok()?.guild()?;
thread_parent.parent_id
} else {
channel.parent_id
}
}
}
impl AsRef<MessageId> for Message {
fn as_ref(&self) -> &MessageId {
&self.id
}
}
impl From<Message> for MessageId {
fn from(message: Message) -> MessageId {
message.id
}
}
impl From<&Message> for MessageId {
fn from(message: &Message) -> MessageId {
message.id
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageReaction {
pub count: u64,
pub count_details: CountDetails,
pub me: bool,
pub me_burst: bool,
#[serde(rename = "emoji")]
pub reaction_type: ReactionType,
#[serde(rename = "burst_colors", deserialize_with = "discord_colours")]
pub burst_colours: Vec<Colour>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct CountDetails {
pub burst: u64,
pub normal: u64,
}
enum_number! {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum MessageType {
#[default]
Regular = 0,
GroupRecipientAddition = 1,
GroupRecipientRemoval = 2,
GroupCallCreation = 3,
GroupNameUpdate = 4,
GroupIconUpdate = 5,
PinsAdd = 6,
MemberJoin = 7,
NitroBoost = 8,
NitroTier1 = 9,
NitroTier2 = 10,
NitroTier3 = 11,
ChannelFollowAdd = 12,
GuildDiscoveryDisqualified = 14,
GuildDiscoveryRequalified = 15,
GuildDiscoveryGracePeriodInitialWarning = 16,
GuildDiscoveryGracePeriodFinalWarning = 17,
ThreadCreated = 18,
InlineReply = 19,
ChatInputCommand = 20,
ThreadStarterMessage = 21,
GuildInviteReminder = 22,
ContextMenuCommand = 23,
AutoModAction = 24,
RoleSubscriptionPurchase = 25,
InteractionPremiumUpsell = 26,
StageStart = 27,
StageEnd = 28,
StageSpeaker = 29,
StageTopic = 31,
GuildApplicationPremiumSubscription = 32,
GuildIncidentAlertModeEnabled = 36,
GuildIncidentAlertModeDisabled = 37,
GuildIncidentReportRaid = 38,
GuildIncidentReportFalseAlarm = 39,
PurchaseNotification = 44,
_ => Unknown(u8),
}
}
enum_number! {
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum MessageActivityKind {
Join = 1,
Spectate = 2,
Listen = 3,
JoinRequest = 5,
_ => Unknown(u8),
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageApplication {
pub id: ApplicationId,
pub cover_image: Option<ImageHash>,
pub description: String,
pub icon: Option<ImageHash>,
pub name: String,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageActivity {
#[serde(rename = "type")]
pub kind: MessageActivityKind,
pub party_id: Option<String>,
}
enum_number! {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum MessageReferenceKind {
#[default]
Default = 0,
Forward = 1,
_ => Unknown(u8),
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageReference {
#[serde(rename = "type", default = "MessageReferenceKind::default")]
pub kind: MessageReferenceKind,
pub message_id: Option<MessageId>,
pub channel_id: ChannelId,
pub guild_id: Option<GuildId>,
pub fail_if_not_exists: Option<bool>,
}
impl MessageReference {
#[must_use]
pub fn new(kind: MessageReferenceKind, channel_id: ChannelId) -> Self {
Self {
kind,
channel_id,
message_id: None,
guild_id: None,
fail_if_not_exists: None,
}
}
#[must_use]
pub fn message_id(mut self, message_id: MessageId) -> Self {
self.message_id = Some(message_id);
self
}
#[must_use]
pub fn guild_id(mut self, guild_id: GuildId) -> Self {
self.guild_id = Some(guild_id);
self
}
#[must_use]
pub fn fail_if_not_exists(mut self, fail_if_not_exists: bool) -> Self {
self.fail_if_not_exists = Some(fail_if_not_exists);
self
}
}
impl From<&Message> for MessageReference {
fn from(m: &Message) -> Self {
Self {
kind: MessageReferenceKind::default(),
message_id: Some(m.id),
channel_id: m.channel_id,
guild_id: m.guild_id,
fail_if_not_exists: None,
}
}
}
impl From<(ChannelId, MessageId)> for MessageReference {
fn from(pair: (ChannelId, MessageId)) -> Self {
Self {
kind: MessageReferenceKind::default(),
message_id: Some(pair.1),
channel_id: pair.0,
guild_id: None,
fail_if_not_exists: None,
}
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct ChannelMention {
pub id: ChannelId,
pub guild_id: GuildId,
#[serde(rename = "type")]
pub kind: ChannelType,
pub name: String,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct MessageSnapshot {
pub content: String,
pub timestamp: Timestamp,
pub edited_timestamp: Option<Timestamp>,
pub mentions: Vec<User>,
#[serde(default)]
pub mention_roles: Vec<RoleId>,
pub attachments: Vec<Attachment>,
pub embeds: Vec<Embed>,
#[serde(rename = "type")]
pub kind: MessageType,
pub flags: Option<MessageFlags>,
#[serde(default, deserialize_with = "deserialize_components")]
pub components: Vec<ActionRow>,
#[serde(default)]
pub sticker_items: Vec<StickerItem>,
}
fn deserialize_snapshots<'de, D>(deserializer: D) -> Result<Vec<MessageSnapshot>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
struct MessageSnapshotWrapper {
pub message: MessageSnapshot,
}
let snapshots: Vec<MessageSnapshotWrapper> = Deserialize::deserialize(deserializer)?;
let result = snapshots.into_iter().map(|wrapper| wrapper.message).collect();
Ok(result)
}
bitflags! {
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Copy, Clone, Default, Debug, Eq, Hash, PartialEq)]
pub struct MessageFlags: u64 {
const CROSSPOSTED = 1 << 0;
const IS_CROSSPOST = 1 << 1;
const SUPPRESS_EMBEDS = 1 << 2;
const SOURCE_MESSAGE_DELETED = 1 << 3;
const URGENT = 1 << 4;
const HAS_THREAD = 1 << 5;
const EPHEMERAL = 1 << 6;
const LOADING = 1 << 7;
const FAILED_TO_MENTION_SOME_ROLES_IN_THREAD = 1 << 8;
const SUPPRESS_NOTIFICATIONS = 1 << 12;
const IS_VOICE_MESSAGE = 1 << 13;
}
}
#[cfg(feature = "model")]
impl MessageId {
#[must_use]
pub fn link(&self, channel_id: ChannelId, guild_id: Option<GuildId>) -> String {
if let Some(guild_id) = guild_id {
format!("https://discord.com/channels/{guild_id}/{channel_id}/{self}")
} else {
format!("https://discord.com/channels/@me/{channel_id}/{self}")
}
}
#[deprecated = "Use GuildChannel::guild_id if you have no GuildId"]
pub async fn link_ensured(
&self,
cache_http: impl CacheHttp,
channel_id: ChannelId,
mut guild_id: Option<GuildId>,
) -> String {
if guild_id.is_none() {
let found_channel = channel_id.to_channel(cache_http).await;
if let Ok(channel) = found_channel {
if let Some(c) = channel.guild() {
guild_id = Some(c.guild_id);
}
}
}
self.link(channel_id, guild_id)
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Serialize)]
#[serde(untagged)]
pub enum Nonce {
String(String),
Number(u64),
}
impl<'de> serde::Deserialize<'de> for Nonce {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
Ok(StrOrInt::deserialize(deserializer)?.into_enum(Self::String, Self::Number))
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoleSubscriptionData {
pub role_subscription_listing_id: SkuId,
pub tier_name: String,
pub total_months_subscribed: u16,
pub is_renewal: bool,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Poll {
pub question: PollMedia,
pub answers: Vec<PollAnswer>,
pub expiry: Option<Timestamp>,
pub allow_multiselect: bool,
pub layout_type: PollLayoutType,
pub results: Option<PollResults>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[non_exhaustive]
pub struct PollMedia {
pub text: Option<String>,
pub emoji: Option<PollMediaEmoji>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum PollMediaEmoji {
Name(String),
Id(EmojiId),
}
impl<'de> serde::Deserialize<'de> for PollMediaEmoji {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> StdResult<Self, D::Error> {
#[derive(serde::Deserialize)]
struct RawPollMediaEmoji {
name: Option<String>,
id: Option<EmojiId>,
}
let raw = RawPollMediaEmoji::deserialize(deserializer)?;
if let Some(name) = raw.name {
Ok(PollMediaEmoji::Name(name))
} else if let Some(id) = raw.id {
Ok(PollMediaEmoji::Id(id))
} else {
Err(serde::de::Error::duplicate_field("emoji"))
}
}
}
impl From<String> for PollMediaEmoji {
fn from(value: String) -> Self {
Self::Name(value)
}
}
impl From<EmojiId> for PollMediaEmoji {
fn from(value: EmojiId) -> Self {
Self::Id(value)
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct PollAnswer {
pub answer_id: AnswerId,
pub poll_media: PollMedia,
}
enum_number! {
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Deserialize, Serialize)]
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[serde(from = "u8", into = "u8")]
#[non_exhaustive]
pub enum PollLayoutType {
#[default]
Default = 1,
_ => Unknown(u8),
}
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct PollResults {
pub is_finalized: bool,
pub answer_counts: Vec<PollAnswerCount>,
}
#[cfg_attr(feature = "typesize", derive(typesize::derive::TypeSize))]
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct PollAnswerCount {
pub id: AnswerId,
pub count: u64,
pub me_voted: bool,
}
#[cfg(all(test, feature = "cache"))]
mod tests {
use std::collections::HashMap;
use dashmap::DashMap;
use super::{
Guild,
GuildChannel,
Member,
Message,
PermissionOverwrite,
PermissionOverwriteType,
Permissions,
User,
UserId,
};
use crate::cache::wrappers::MaybeMap;
use crate::cache::Cache;
#[test]
fn author_permissions_respects_overwrites() {
let author = User {
id: UserId::new(50778944701071),
..Default::default()
};
let channel = GuildChannel {
permission_overwrites: vec![PermissionOverwrite {
allow: Permissions::SEND_MESSAGES,
deny: Permissions::default(),
kind: PermissionOverwriteType::Member(author.id),
}],
..Default::default()
};
let channel_id = channel.id;
let guild = Guild {
channels: HashMap::from([(channel.id, channel)]),
members: HashMap::from([(author.id, Member {
user: author.clone(),
..Default::default()
})]),
..Default::default()
};
let message = Message {
author,
channel_id,
guild_id: Some(guild.id),
..Default::default()
};
let mut cache = Cache::new();
cache.guilds = MaybeMap(Some({
let guilds = DashMap::default();
guilds.insert(guild.id, guild);
guilds
}));
assert_eq!(message.author_permissions(&cache), Some(Permissions::SEND_MESSAGES));
}
}