Add partial emoji and custom reaction types, refine SQLx mapping

This commit is contained in:
Quat3rnion 2024-06-17 15:22:58 -04:00
parent 590a6d6828
commit bec0269e70
10 changed files with 122 additions and 48 deletions

View File

@ -3,6 +3,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_number_from_string;
use crate::types::{config::types::subconfigs::register::{ use crate::types::{config::types::subconfigs::register::{
DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration, DateOfBirthConfiguration, PasswordConfiguration, RegistrationEmailConfiguration,
@ -22,6 +23,7 @@ pub struct RegisterConfiguration {
pub allow_multiple_accounts: bool, pub allow_multiple_accounts: bool,
pub block_proxies: bool, pub block_proxies: bool,
pub incrementing_discriminators: bool, pub incrementing_discriminators: bool,
#[serde(deserialize_with = "deserialize_number_from_string")]
pub default_rights: Rights, pub default_rights: Rights,
} }

View File

@ -64,7 +64,9 @@ pub struct Channel {
pub managed: Option<bool>, pub managed: Option<bool>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member: Option<ThreadMember>, pub member: Option<ThreadMember>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub member_count: Option<i32>, pub member_count: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub message_count: Option<i32>, pub message_count: Option<i32>,
pub name: Option<String>, pub name: Option<String>,
pub nsfw: Option<bool>, pub nsfw: Option<bool>,
@ -75,6 +77,7 @@ pub struct Channel {
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_option_vec)]
pub permission_overwrites: Option<Vec<Shared<PermissionOverwrite>>>, pub permission_overwrites: Option<Vec<Shared<PermissionOverwrite>>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub permissions: Option<String>, pub permissions: Option<String>,
pub position: Option<i32>, pub position: Option<i32>,
pub rate_limit_per_user: Option<i32>, pub rate_limit_per_user: Option<i32>,
@ -85,6 +88,7 @@ pub struct Channel {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread_metadata: Option<ThreadMetadata>, pub thread_metadata: Option<ThreadMetadata>,
pub topic: Option<String>, pub topic: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub total_message_sent: Option<i32>, pub total_message_sent: Option<i32>,
pub user_limit: Option<i32>, pub user_limit: Option<i32>,
pub video_quality_mode: Option<i32>, pub video_quality_mode: Option<i32>,

View File

@ -6,7 +6,7 @@ use std::fmt::Debug;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::types::Shared; use crate::types::{PartialEmoji, Shared};
use crate::types::entities::User; use crate::types::entities::User;
use crate::types::Snowflake; use crate::types::Snowflake;
@ -66,3 +66,18 @@ impl PartialEq for Emoji {
|| self.available != other.available) || self.available != other.available)
} }
} }
impl From<PartialEmoji> for Emoji {
fn from(value: PartialEmoji) -> Self {
Self {
id: value.id.unwrap_or_default(), // TODO: this should be handled differently
name: Some(value.name),
roles: None,
user: None,
require_colons: Some(value.animated),
managed: None,
animated: Some(value.animated),
available: None,
}
}
}

View File

@ -59,7 +59,7 @@ pub struct Guild {
pub emojis: Vec<Shared<Emoji>>, pub emojis: Vec<Shared<Emoji>>,
pub explicit_content_filter: Option<ExplicitContentFilterLevel>, pub explicit_content_filter: Option<ExplicitContentFilterLevel>,
//#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))] //#[cfg_attr(feature = "sqlx", sqlx(try_from = "String"))]
pub features: Option<GuildFeaturesList>, pub features: GuildFeaturesList,
pub icon: Option<String>, pub icon: Option<String>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub icon_hash: Option<String>, pub icon_hash: Option<String>,
@ -111,7 +111,7 @@ pub struct Guild {
#[cfg_attr(feature = "client", observe_option_vec)] #[cfg_attr(feature = "client", observe_option_vec)]
pub webhooks: Option<Vec<Shared<Webhook>>>, pub webhooks: Option<Vec<Shared<Webhook>>>,
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
pub welcome_screen: Option<sqlx::types::Json<WelcomeScreenObject>>, pub welcome_screen: sqlx::types::Json<Option<WelcomeScreenObject>>,
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
pub welcome_screen: Option<WelcomeScreenObject>, pub welcome_screen: Option<WelcomeScreenObject>,
pub widget_channel_id: Option<Snowflake>, pub widget_channel_id: Option<Snowflake>,

View File

@ -16,7 +16,9 @@ use super::{Application, Channel, GuildMember, NSFWLevel, User};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))] #[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
pub struct Invite { pub struct Invite {
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub approximate_member_count: Option<i32>, pub approximate_member_count: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))]
pub approximate_presence_count: Option<i32>, pub approximate_presence_count: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub channel: Option<Channel>, pub channel: Option<Channel>,
@ -45,7 +47,7 @@ pub struct Invite {
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub target_user: Option<User>, pub target_user: Option<User>,
pub temporary: Option<bool>, pub temporary: Option<bool>,
pub uses: Option<i32>, pub uses: Option<u32>,
} }
/// The guild an invite is for. /// The guild an invite is for.
@ -77,16 +79,16 @@ impl From<Guild> for InviteGuild {
icon: value.icon, icon: value.icon,
splash: value.splash, splash: value.splash,
verification_level: value.verification_level.unwrap_or_default(), verification_level: value.verification_level.unwrap_or_default(),
features: value.features.unwrap_or_default(), features: value.features,
vanity_url_code: value.vanity_url_code, vanity_url_code: value.vanity_url_code,
description: value.description, description: value.description,
banner: value.banner, banner: value.banner,
premium_subscription_count: value.premium_subscription_count, premium_subscription_count: value.premium_subscription_count,
nsfw_deprecated: None, nsfw_deprecated: None,
nsfw_level: value.nsfw_level.unwrap_or_default(), nsfw_level: value.nsfw_level.unwrap_or_default(),
welcome_screen: value.welcome_screen.map(|obj| { welcome_screen: value.welcome_screen.0.map(|obj| {
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
let res = obj.0; let res = obj;
#[cfg(not(feature = "sqlx"))] #[cfg(not(feature = "sqlx"))]
let res = obj; let res = obj;
res res

View File

@ -75,13 +75,14 @@ pub struct Message {
pub interaction: Option<MessageInteraction>, pub interaction: Option<MessageInteraction>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub thread: Option<Channel>, pub thread: Option<Channel>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg(feature = "sqlx")]
pub components: Option<sqlx::types::Json<Vec<Component>>>,
#[cfg(not(feature = "sqlx"))]
pub components: Option<Vec<Component>>, pub components: Option<Vec<Component>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub sticker_items: Option<Vec<StickerItem>>, pub sticker_items: Option<Vec<StickerItem>>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub stickers: Option<Vec<Sticker>>, pub stickers: Option<Vec<Sticker>>,
pub position: Option<i32>,
#[cfg_attr(feature = "sqlx", sqlx(skip))] #[cfg_attr(feature = "sqlx", sqlx(skip))]
pub role_subscription_data: Option<RoleSubscriptionData>, pub role_subscription_data: Option<RoleSubscriptionData>,
} }
@ -116,7 +117,7 @@ impl PartialEq for Message {
&& self.thread == other.thread && self.thread == other.thread
&& self.components == other.components && self.components == other.components
&& self.sticker_items == other.sticker_items && self.sticker_items == other.sticker_items
&& self.position == other.position // && self.position == other.position
&& self.role_subscription_data == other.role_subscription_data && self.role_subscription_data == other.role_subscription_data
} }
} }
@ -125,12 +126,22 @@ impl PartialEq for Message {
/// # Reference /// # Reference
/// See <https://discord-userdoccers.vercel.app/resources/message#message-reference-object> /// See <https://discord-userdoccers.vercel.app/resources/message#message-reference-object>
pub struct MessageReference { pub struct MessageReference {
#[serde(rename = "type")]
pub reference_type: MessageReferenceType,
pub message_id: Snowflake, pub message_id: Snowflake,
pub channel_id: Snowflake, pub channel_id: Snowflake,
pub guild_id: Option<Snowflake>, pub guild_id: Option<Snowflake>,
pub fail_if_not_exists: Option<bool>, pub fail_if_not_exists: Option<bool>,
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Eq, Ord, PartialOrd)]
pub enum MessageReferenceType {
/// A standard reference used by replies and system messages
Default = 0,
/// A reference used to point to a message at a point in time
Forward = 1,
}
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct MessageInteraction { pub struct MessageInteraction {
pub id: Snowflake, pub id: Snowflake,
@ -405,3 +416,21 @@ bitflags! {
const HAS_SNAPSHOT = 1 << 14; const HAS_SNAPSHOT = 1 << 14;
} }
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct PartialEmoji {
#[serde(default)]
pub id: Option<Snowflake>,
pub name: String,
#[serde(default)]
pub animated: bool
}
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, PartialOrd)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[cfg_attr(feature = "sqlx", derive(sqlx::Type))]
#[repr(u8)]
pub enum ReactionType {
Normal = 0,
Burst = 1, // The dreaded super reactions
}

View File

@ -59,7 +59,7 @@ pub struct GetChannelMessagesSchema {
/// Between 1 and 100, defaults to 50. /// Between 1 and 100, defaults to 50.
pub limit: Option<i32>, pub limit: Option<i32>,
#[serde(flatten)] #[serde(flatten)]
pub anchor: ChannelMessagesAnchor, pub anchor: Option<ChannelMessagesAnchor>,
} }
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] #[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)]
@ -74,21 +74,21 @@ impl GetChannelMessagesSchema {
pub fn before(anchor: Snowflake) -> Self { pub fn before(anchor: Snowflake) -> Self {
Self { Self {
limit: None, limit: None,
anchor: ChannelMessagesAnchor::Before(anchor), anchor: Some(ChannelMessagesAnchor::Before(anchor)),
} }
} }
pub fn around(anchor: Snowflake) -> Self { pub fn around(anchor: Snowflake) -> Self {
Self { Self {
limit: None, limit: None,
anchor: ChannelMessagesAnchor::Around(anchor), anchor: Some(ChannelMessagesAnchor::Around(anchor)),
} }
} }
pub fn after(anchor: Snowflake) -> Self { pub fn after(anchor: Snowflake) -> Self {
Self { Self {
limit: None, limit: None,
anchor: ChannelMessagesAnchor::After(anchor), anchor: Some(ChannelMessagesAnchor::After(anchor)),
} }
} }

View File

@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use crate::types::entities::{ use crate::types::entities::{
AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment, AllowedMention, Component, Embed, MessageReference, PartialDiscordFileAttachment,
}; };
use crate::types::{Attachment, MessageFlags, MessageType, Snowflake}; use crate::types::{Attachment, MessageFlags, MessageType, ReactionType, Snowflake};
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)] #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -118,13 +118,21 @@ pub struct MessageAck {
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)] #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
pub struct MessageModifySchema { pub struct MessageModifySchema {
content: Option<String>, pub content: Option<String>,
embeds: Option<Vec<Embed>>, pub embeds: Option<Vec<Embed>>,
embed: Option<Embed>, pub embed: Option<Embed>,
allowed_mentions: Option<AllowedMention>, pub allowed_mentions: Option<AllowedMention>,
components: Option<Vec<Component>>, pub components: Option<Vec<Component>>,
flags: Option<MessageFlags>, pub flags: Option<MessageFlags>,
files: Option<Vec<u8>>, pub files: Option<Vec<u8>>,
payload_json: Option<String>, pub payload_json: Option<String>,
attachments: Option<Vec<Attachment>>, pub attachments: Option<Vec<Attachment>>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, PartialOrd)]
pub struct ReactionQuerySchema {
pub after: Option<Snowflake>,
pub limit: Option<u32>,
#[serde(rename = "type")]
pub reaction_type: Option<ReactionType>
} }

View File

@ -2,11 +2,11 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/. // file, You can obtain one at http://mozilla.org/MPL/2.0/.
use std::num::ParseIntError;
use std::str::FromStr;
use bitflags::bitflags; use bitflags::bitflags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::types::UserFlags;
#[cfg(feature = "sqlx")]
use sqlx::{{Decode, Encode, MySql}, database::{HasArguments, HasValueRef}, encode::IsNull, error::BoxDynError, mysql::MySqlValueRef};
bitflags! { bitflags! {
/// Rights are instance-wide, per-user permissions for everything you may perform on the instance, /// Rights are instance-wide, per-user permissions for everything you may perform on the instance,
@ -18,7 +18,7 @@ bitflags! {
/// ///
/// # Reference /// # Reference
/// See <https://docs.spacebar.chat/setup/server/security/rights/> /// See <https://docs.spacebar.chat/setup/server/security/rights/>
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, Eq, PartialEq, chorus_macros::SqlxBitFlags)]
pub struct Rights: u64 { pub struct Rights: u64 {
/// All rights /// All rights
const OPERATOR = 1 << 0; const OPERATOR = 1 << 0;
@ -132,32 +132,27 @@ bitflags! {
} }
} }
#[cfg(feature = "sqlx")] impl FromStr for Rights {
impl sqlx::Type<MySql> for Rights { type Err = ParseIntError;
fn type_info() -> <sqlx::MySql as sqlx::Database>::TypeInfo {
u64::type_info()
}
fn compatible(ty: &<sqlx::MySql as sqlx::Database>::TypeInfo) -> bool { fn from_str(s: &str) -> Result<Self, Self::Err> {
u64::compatible(ty) s.parse::<u64>().map(Rights::from_bits).map(|f| f.unwrap_or(Rights::empty()))
} }
} }
#[cfg(feature = "sqlx")] impl Serialize for Rights {
impl<'q> Encode<'q, MySql> for Rights { fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
fn encode_by_ref(&self, buf: &mut <MySql as HasArguments<'q>>::ArgumentBuffer) -> IsNull { serializer.serialize_str(&self.bits().to_string())
<u64 as Encode<MySql>>::encode_by_ref(&self.0.0, buf)
} }
} }
#[cfg(feature = "sqlx")] impl<'de> Deserialize<'de> for Rights {
impl<'r> Decode<'r, MySql> for Rights { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de> {
fn decode(value: <MySql as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> { let s = String::deserialize(deserializer)?.parse::<u64>().map_err(serde::de::Error::custom)?;
let raw = <u64 as Decode<MySql>>::decode(value)?;
Ok(Rights::from_bits(raw).unwrap())
}
}
Ok(Rights::from_bits(s).unwrap())
}
}
impl Rights { impl Rights {
pub fn any(&self, permission: Rights, check_operator: bool) -> bool { pub fn any(&self, permission: Rights, check_operator: bool) -> bool {

View File

@ -8,6 +8,9 @@ use std::{
}; };
use chrono::{DateTime, TimeZone, Utc}; use chrono::{DateTime, TimeZone, Utc};
use sqlx::{MySql, TypeInfo};
use sqlx::database::HasArguments;
use sqlx::encode::IsNull;
#[cfg(feature = "sqlx")] #[cfg(feature = "sqlx")]
use sqlx::Type; use sqlx::Type;
@ -19,8 +22,6 @@ const EPOCH: i64 = 1420070400000;
/// # Reference /// # Reference
/// See <https://discord.com/developers/docs/reference#snowflakes> /// See <https://discord.com/developers/docs/reference#snowflakes>
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "sqlx", derive(Type))]
#[cfg_attr(feature = "sqlx", sqlx(transparent))]
pub struct Snowflake(pub u64); pub struct Snowflake(pub u64);
impl Snowflake { impl Snowflake {
@ -102,6 +103,24 @@ impl<'de> serde::Deserialize<'de> for Snowflake {
} }
} }
impl sqlx::Type<sqlx::MySql> for Snowflake {
fn type_info() -> <sqlx::MySql as sqlx::Database>::TypeInfo {
<String as sqlx::Type<sqlx::MySql>>::type_info()
}
}
impl<'q> sqlx::Encode<'q, sqlx::MySql> for Snowflake {
fn encode_by_ref(&self, buf: &mut <sqlx::MySql as sqlx::database::HasArguments<'q>>::ArgumentBuffer) -> sqlx::encode::IsNull {
<String as sqlx::Encode<'q, sqlx::MySql>>::encode_by_ref(&self.0.to_string(), buf)
}
}
impl<'d> sqlx::Decode<'d, sqlx::MySql> for Snowflake {
fn decode(value: <sqlx::MySql as sqlx::database::HasValueRef<'d>>::ValueRef) -> Result<Self, sqlx::error::BoxDynError> {
<String as sqlx::Decode<'d, sqlx::MySql>>::decode(value).map(|s| s.parse::<u64>().map(Snowflake).unwrap())
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};