use chrono::{DateTime, Utc};
use crate::model::prelude::*;
use serde_json::Value;
use std::fmt::Display;
#[cfg(all(feature = "model", feature = "utils"))]
use crate::builder::{CreateEmbed, EditMessage};
#[cfg(all(feature = "cache", feature = "model"))]
use crate::cache::Cache;
#[cfg(all(feature = "cache", feature = "model"))]
use std::fmt::Write;
#[cfg(feature = "model")]
use bitflags::__impl_bitflags;
#[cfg(feature = "model")]
use serde::{
de::{Deserialize, Deserializer},
ser::{Serialize, Serializer},
};
#[cfg(feature = "model")]
use crate::model::utils::U64Visitor;
#[cfg(feature = "model")]
use std::{
result::Result as StdResult,
};
#[cfg(feature = "model")]
use crate::{
constants,
model::id::{
ApplicationId,
MessageId,
GuildId,
ChannelId,
},
};
#[cfg(feature = "model")]
use crate::http::{Http, CacheHttp};
#[cfg(feature = "collector")]
use crate::collector::{
ReactionCollectorBuilder, CollectReaction,
};
#[cfg(feature = "collector")]
use crate::client::bridge::gateway::ShardMessenger;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct Message {
pub id: MessageId,
pub attachments: Vec<Attachment>,
pub author: User,
pub channel_id: ChannelId,
pub content: String,
pub edited_timestamp: Option<DateTime<Utc>>,
pub embeds: Vec<Embed>,
pub guild_id: Option<GuildId>,
#[serde(rename = "type")]
pub kind: MessageType,
pub member: Option<PartialMember>,
pub mention_everyone: bool,
pub mention_roles: Vec<RoleId>,
#[serde(default = "Vec::new")]
pub mention_channels: Vec<ChannelMention>,
pub mentions: Vec<User>,
#[serde(default)]
pub nonce: Value,
pub pinned: bool,
#[serde(default)]
pub reactions: Vec<MessageReaction>,
pub timestamp: DateTime<Utc>,
pub tts: bool,
pub webhook_id: Option<WebhookId>,
pub activity: Option<MessageActivity>,
pub application: Option<MessageApplication>,
pub message_reference: Option<MessageReference>,
pub flags: Option<MessageFlags>,
#[serde(default)]
pub stickers: Vec<Sticker>,
pub referenced_message: Option<Box<Message>>,
}
#[cfg(feature = "model")]
impl Message {
#[cfg(feature = "cache")]
#[inline]
pub async fn channel(&self, cache: impl AsRef<Cache>) -> Option<Channel> {
cache.as_ref().channel(self.channel_id).await
}
#[cfg(all(feature = "cache", feature = "utils"))]
pub async fn is_own(&self, cache: impl AsRef<Cache>) -> bool {
self.author.id == cache.as_ref().current_user().await.id
}
pub async fn delete(&self, cache_http: impl CacheHttp) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::MANAGE_MESSAGES;
let is_author = self.author.id == cache.current_user().await.id;
let has_perms = utils::user_has_perms(&cache, self.channel_id, self.guild_id, req).await?;
if !is_author && !has_perms {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
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() {
let req = Permissions::MANAGE_MESSAGES;
if !utils::user_has_perms(cache, self.channel_id, self.guild_id, req).await? {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
cache_http.http().as_ref().delete_message_reactions(self.channel_id.0, self.id.0).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() {
let req = Permissions::MANAGE_MESSAGES;
if !utils::user_has_perms(cache, self.channel_id, self.guild_id, req).await? {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
cache_http
.http()
.as_ref()
.delete_message_reaction_emoji(self.channel_id.0, self.id.0, &reaction_type.into())
.await
}
#[cfg(feature = "utils")]
pub async fn edit<F>(&mut self, cache_http: impl CacheHttp, f: F) -> Result<()>
where F: FnOnce(&mut EditMessage) -> &mut EditMessage
{
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.author.id != cache.current_user().await.id {
return Err(Error::Model(ModelError::InvalidUser));
}
}
}
let mut builder = EditMessage::default();
if !self.content.is_empty() {
builder.content(&self.content);
}
if let Some(embed) = self.embeds.get(0) {
let embed = CreateEmbed::from(embed.clone());
builder.embed( |e| {
*e = embed;
e
});
}
f(&mut builder);
let map = crate::utils::hashmap_to_json_map(builder.0);
*self = cache_http.http().edit_message(self.channel_id.0, self.id.0, &Value::Object(map)).await?;
Ok(())
}
pub(crate) fn transform_content(&mut self) {
match self.kind {
MessageType::PinsAdd => {
self.content = format!(
"{} pinned a message to this channel. See all the pins.",
self.author
);
},
MessageType::MemberJoin => {
let sec = self.timestamp.timestamp() as usize;
let chosen = constants::JOIN_MESSAGES[sec % constants::JOIN_MESSAGES.len()];
self.content = if chosen.contains("$user") {
chosen.replace("$user", &self.author.mention().to_string())
} else {
chosen.to_string()
};
},
_ => {},
}
}
#[cfg(feature = "cache")]
pub async 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);
at_distinct.push('#');
let _ = write!(at_distinct, "{:04}", u.discriminator);
let mut m = u.mention().to_string();
if !result.contains(&m) {
m.insert(2, '!');
}
result = result.replace(&m, &at_distinct);
}
for id in &self.mention_roles {
let mention = id.mention().to_string();
if let Some(role) = id.to_role_cached(&cache).await {
result = result.replace(&mention, &format!("@{}", role.name));
} else {
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 async fn guild(&self, cache: impl AsRef<Cache>) -> Option<Guild> {
cache.as_ref().guild(self.guild_id?).await
}
#[cfg(feature = "cache")]
pub async fn guild_field<Ret, Fun>(&self, cache: impl AsRef<Cache>, field_accessor: Fun) -> Option<Ret>
where Ret: Clone, Fun: FnOnce(&Guild) -> Ret
{
cache
.as_ref()
.guild_field(self.guild_id?, field_accessor)
.await
}
#[inline]
pub fn is_private(&self) -> bool {
self.guild_id.is_none()
}
pub async fn member(&self, cache_http: impl CacheHttp) -> Result<Member> {
let guild_id = match self.guild_id {
Some(guild_id) => guild_id,
None => return Err(Error::Model(ModelError::ItemMissing)),
};
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if let Some(member) = cache.member(guild_id, self.author.id).await {
return Ok(member);
}
}
}
cache_http.http().get_member(guild_id.0, self.author.id.0).await
}
pub fn overflow_length(content: &str) -> Option<usize> {
let count = content.chars().count();
if count > constants::MESSAGE_CODE_LIMIT {
Some(count - constants::MESSAGE_CODE_LIMIT)
} else {
None
}
}
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() {
let req = Permissions::MANAGE_MESSAGES;
if !utils::user_has_perms(&cache, self.channel_id, self.guild_id, req).await? {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
}
self.channel_id.pin(cache_http.http(), self.id.0).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> {
#[allow(unused_mut)]
let mut user_id = None;
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.guild_id.is_some() {
let req = Permissions::ADD_REACTIONS;
if !utils::user_has_perms(cache, self.channel_id, self.guild_id, req).await? {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
user_id = Some(cache.current_user().await.id);
}
}
cache_http.http().create_reaction(self.channel_id.0, self.id.0, reaction_type).await?;
Ok(Reaction {
channel_id: self.channel_id,
emoji: reaction_type.clone(),
message_id: self.id,
user_id,
guild_id: self.guild_id,
})
}
#[inline]
pub async fn reply(&self, cache_http: impl CacheHttp, content: impl Display) -> Result<Message> {
self._reply(cache_http, content, Some(false)).await
}
#[inline]
pub async fn reply_ping(&self, cache_http: impl CacheHttp, content: impl Display) -> 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!("{} {}", self.author.mention(), content), None).await
}
async fn _reply(&self, cache_http: impl CacheHttp, content: impl Display, inlined: Option<bool>) -> Result<Message> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
if self.guild_id.is_some() {
let req = Permissions::SEND_MESSAGES;
if !super::utils::user_has_perms(cache, self.channel_id, self.guild_id, req).await? {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
}
self.channel_id.send_message(cache_http.http(), |mut builder| {
if let Some(ping_user) = inlined {
builder = builder
.reference_message(self)
.allowed_mentions(|f| f.replied_user(ping_user));
}
builder.content(content)
}).await
}
#[cfg(feature = "utils")]
pub async fn suppress_embeds(&mut self, cache_http: impl CacheHttp) -> Result<()> {
#[cfg(feature = "cache")]
{
if let Some(cache) = cache_http.cache() {
let req = Permissions::MANAGE_MESSAGES;
let is_author = self.author.id == cache.current_user().await.id;
let has_perms = super::utils::user_has_perms(&cache, self.channel_id, self.guild_id, req).await?;
if !is_author && !has_perms {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
let mut suppress = EditMessage::default();
suppress.suppress_embeds(true);
let map = crate::utils::hashmap_to_json_map(suppress.0);
*self = cache_http.http().edit_message(self.channel_id.0, self.id.0, &Value::Object(map)).await?;
Ok(())
}
#[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.0 == id.0)
}
#[inline]
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.user.read().await.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() {
let req = Permissions::MANAGE_MESSAGES;
if !super::utils::user_has_perms(cache, self.channel_id, self.guild_id, req).await? {
return Err(Error::Model(ModelError::InvalidPermissions(req)));
}
}
}
}
cache_http.http().unpin_message(self.channel_id.0, self.id.0).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]
pub fn link(&self) -> String {
match self.guild_id {
Some(guild_id) => format!("https://discord.com/channels/{}/{}/{}", guild_id.0, self.channel_id.0, self.id.0),
None => format!("https://discord.com/channels/@me/{}/{}", self.channel_id.0, self.id.0),
}
}
#[cfg(feature = "collector")]
pub fn await_reaction<'a>(&self, shard_messenger: &'a impl AsRef<ShardMessenger>) -> CollectReaction<'a> {
CollectReaction::new(shard_messenger).message_id(self.id.0)
}
#[cfg(feature = "collector")]
pub fn await_reactions<'a>(&self, shard_messenger: &'a impl AsRef<ShardMessenger>) -> ReactionCollectorBuilder<'a> {
ReactionCollectorBuilder::new(shard_messenger).message_id(self.id.0)
}
#[cfg(feature = "cache")]
pub async fn category_id(&self, cache: impl AsRef<Cache>) -> Option<ChannelId> {
cache.as_ref().channel_category_id(self.channel_id).await
}
pub(crate) fn check_content_length(map: &JsonMap) -> Result<()> {
if let Some(content) = map.get("content") {
if let Value::String(ref content) = *content {
if let Some(length_over) = Message::overflow_length(content) {
return Err(Error::Model(ModelError::MessageTooLong(length_over)));
}
}
}
Ok(())
}
pub(crate) fn check_embed_length(map: &JsonMap) -> Result<()> {
let embed = match map.get("embed") {
Some(&Value::Object(ref value)) => value,
_ => return Ok(()),
};
let mut total: usize = 0;
if let Some(&Value::Object(ref author)) = embed.get("author") {
if let Some(&Value::Object(ref name)) = author.get("name") {
total += name.len();
}
}
if let Some(&Value::String(ref description)) = embed.get("description") {
total += description.len();
}
if let Some(&Value::Array(ref fields)) = embed.get("fields") {
for field_as_value in fields {
if let Value::Object(ref field) = *field_as_value {
if let Some(&Value::String(ref field_name)) = field.get("name") {
total += field_name.len();
}
if let Some(&Value::String(ref field_value)) = field.get("value") {
total += field_value.len();
}
}
}
}
if let Some(&Value::Object(ref footer)) = embed.get("footer") {
if let Some(&Value::String(ref text)) = footer.get("text") {
total += text.len();
}
}
if let Some(&Value::String(ref title)) = embed.get("title") {
total += title.len();
}
if total <= constants::EMBED_MAX_LENGTH {
Ok(())
} else {
let overflow = total - constants::EMBED_MAX_LENGTH;
Err(Error::Model(ModelError::EmbedTooLarge(overflow)))
}
}
}
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<'a> From<&'a Message> for MessageId {
fn from(message: &Message) -> MessageId { message.id }
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageReaction {
pub count: u64,
pub me: bool,
#[serde(rename = "emoji")]
pub reaction_type: ReactionType,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum MessageType {
Regular = 0,
GroupRecipientAddition = 1,
GroupRecipientRemoval = 2,
GroupCallCreation = 3,
GroupNameUpdate = 4,
GroupIconUpdate = 5,
PinsAdd = 6,
MemberJoin = 7,
NitroBoost = 8,
NitroTier1 = 9,
NitroTier2 = 10,
NitroTier3 = 11,
InlineReply = 19,
ApplicationCommand = 20,
}
enum_number!(
MessageType {
Regular,
GroupRecipientAddition,
GroupRecipientRemoval,
GroupCallCreation,
GroupNameUpdate,
GroupIconUpdate,
PinsAdd,
MemberJoin,
NitroBoost,
NitroTier1,
NitroTier2,
NitroTier3,
InlineReply,
ApplicationCommand,
}
);
impl MessageType {
pub fn num(self) -> u64 {
use self::MessageType::*;
match self {
Regular => 0,
GroupRecipientAddition => 1,
GroupRecipientRemoval => 2,
GroupCallCreation => 3,
GroupNameUpdate => 4,
GroupIconUpdate => 5,
PinsAdd => 6,
MemberJoin => 7,
NitroBoost => 8,
NitroTier1 => 9,
NitroTier2 => 10,
NitroTier3 => 11,
InlineReply => 19,
ApplicationCommand => 20,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
#[non_exhaustive]
pub enum MessageActivityKind {
JOIN = 1,
SPECTATE = 2,
LISTEN = 3,
#[allow(non_camel_case_types)]
JOIN_REQUEST = 5,
}
enum_number!(
MessageActivityKind {
JOIN,
SPECTATE,
LISTEN,
JOIN_REQUEST,
}
);
impl MessageActivityKind {
pub fn num(self) -> u64 {
use self::MessageActivityKind::*;
match self {
JOIN => 1,
SPECTATE => 2,
LISTEN => 3,
JOIN_REQUEST => 5,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageApplication {
pub id: ApplicationId,
pub cover_image: Option<String>,
pub description: String,
pub icon: Option<String>,
pub name: String,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageActivity {
#[serde(rename = "type")]
pub kind: MessageActivityKind,
pub party_id: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[non_exhaustive]
pub struct MessageReference {
pub message_id: Option<MessageId>,
pub channel_id: ChannelId,
pub guild_id: Option<GuildId>,
}
impl From<&Message> for MessageReference {
fn from(m: &Message) -> Self {
Self {
message_id: Some(m.id),
channel_id: m.channel_id,
guild_id: m.guild_id,
}
}
}
impl From<(ChannelId, MessageId)> for MessageReference {
fn from(pair: (ChannelId, MessageId)) -> Self {
Self {
message_id: Some(pair.1),
channel_id: pair.0,
guild_id: None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct ChannelMention {
pub id: ChannelId,
pub guild_id: GuildId,
#[serde(rename = "type")]
pub kind: ChannelType,
pub name: String,
}
#[derive(Copy, PartialEq, Eq, Clone, PartialOrd, Ord, Hash)]
#[cfg_attr(not(feature = "model"), derive(Debug, Deserialize, Serialize))]
pub struct MessageFlags {
pub bits: u64,
}
#[cfg(feature = "model")]
__impl_bitflags! {
MessageFlags: u64 {
CROSSPOSTED = 0b0000_0000_0000_0000_0000_0000_0000_0001;
IS_CROSSPOST = 0b0000_0000_0000_0000_0000_0000_0000_0010;
SUPPRESS_EMBEDS = 0b0000_0000_0000_0000_0000_0000_0000_0100;
}
}
#[cfg(feature = "model")]
impl<'de> Deserialize<'de> for MessageFlags {
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
where D: Deserializer<'de>
{
Ok(MessageFlags::from_bits_truncate(
deserializer.deserialize_u64(U64Visitor)?,
))
}
}
#[cfg(feature = "model")]
impl Serialize for MessageFlags {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where S: Serializer
{
serializer.serialize_u64(self.bits())
}
}