feat: ensure bot invites work correctly

This commit is contained in:
Paul Makles
2023-08-27 12:21:48 +01:00
parent c2412190a8
commit 73f7b8f007
13 changed files with 168 additions and 44 deletions

View File

@@ -43,7 +43,7 @@ macro_rules! auto_derived {
macro_rules! auto_derived_partial { macro_rules! auto_derived_partial {
( $item:item, $name:expr ) => { ( $item:item, $name:expr ) => {
#[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)] #[derive(OptionalStruct, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
#[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)] #[optional_derive(Serialize, Deserialize, Debug, Clone, Default, Eq, PartialEq)]
#[optional_name = $name] #[optional_name = $name]
#[opt_skip_serializing_none] #[opt_skip_serializing_none]

View File

@@ -50,6 +50,24 @@ auto_derived!(
} }
); );
#[allow(clippy::derivable_impls)]
impl Default for Bot {
fn default() -> Self {
Self {
id: Default::default(),
owner: Default::default(),
token: Default::default(),
public: Default::default(),
analytics: Default::default(),
discoverable: Default::default(),
interactions_url: Default::default(),
terms_of_service_url: Default::default(),
privacy_policy_url: Default::default(),
flags: Default::default(),
}
}
}
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
impl Bot { impl Bot {
/// Create a new bot /// Create a new bot

View File

@@ -36,6 +36,20 @@ auto_derived!(
} }
); );
#[allow(clippy::derivable_impls)]
impl Default for Webhook {
fn default() -> Self {
Self {
id: Default::default(),
name: Default::default(),
avatar: None,
channel_id: Default::default(),
permissions: Default::default(),
token: Default::default(),
}
}
}
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
impl Webhook { impl Webhook {
pub async fn create(&self, db: &Database) -> Result<()> { pub async fn create(&self, db: &Database) -> Result<()> {

View File

@@ -1,12 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use revolt_models::v0::MessageAuthor;
use revolt_permissions::OverrideField; use revolt_permissions::OverrideField;
use revolt_result::Result; use revolt_result::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{events::client::EventV1, Database, File, IntoDocumentPath}; use crate::{events::client::EventV1, Database, File, IntoDocumentPath, SystemMessage, User};
auto_derived!( auto_derived!(
#[serde(tag = "channel_type")]
pub enum Channel { pub enum Channel {
/// Personal "Saved Notes" channel which allows users to save messages /// Personal "Saved Notes" channel which allows users to save messages
SavedMessages { SavedMessages {
@@ -190,40 +192,48 @@ impl Channel {
pub async fn add_user_to_group( pub async fn add_user_to_group(
&mut self, &mut self,
db: &Database, db: &Database,
user_id: &str, user: &User,
_by_id: &str, by_id: &str,
) -> Result<()> { ) -> Result<()> {
if let Channel::Group { recipients, .. } = self { if let Channel::Group { recipients, .. } = self {
if recipients.contains(&String::from(user_id)) { if recipients.contains(&String::from(&user.id)) {
return Err(create_error!(AlreadyInGroup)); return Err(create_error!(AlreadyInGroup));
} }
recipients.push(String::from(user_id)); recipients.push(String::from(&user.id));
} }
match &self { match &self {
Channel::Group { id, .. } => { Channel::Group { id, .. } => {
db.add_user_to_group(id, user_id).await?; db.add_user_to_group(id, &user.id).await?;
EventV1::ChannelGroupJoin { EventV1::ChannelGroupJoin {
id: id.to_string(), id: id.to_string(),
user: user_id.to_string(), user: user.id.to_string(),
} }
.p(id.to_string()) .p(id.to_string())
.await; .await;
EventV1::ChannelCreate(self.clone().into()) EventV1::ChannelCreate(self.clone().into())
.private(user_id.to_string()) .private(user.id.to_string())
.await; .await;
/* TODO: SystemMessage::UserAdded { SystemMessage::UserAdded {
id: user.to_string(), id: user.id.to_string(),
by: by.to_string(), by: by_id.to_string(),
} }
.into_message(id.to_string()) .into_message(id.to_string())
.create(db, self, None) .send(
db,
MessageAuthor::System {
username: &user.username,
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
},
self,
false,
)
.await .await
.ok(); */ .ok();
Ok(()) Ok(())
} }
@@ -469,19 +479,20 @@ impl Channel {
pub async fn remove_user_from_group( pub async fn remove_user_from_group(
&self, &self,
db: &Database, db: &Database,
user_id: &str, user: &User,
_by_id: Option<&str>, by_id: Option<&str>,
silent: bool, silent: bool,
) -> Result<()> { ) -> Result<()> {
match &self { match &self {
Channel::Group { Channel::Group {
id, id,
name,
owner, owner,
recipients, recipients,
.. ..
} => { } => {
if user_id == owner { if &user.id == owner {
if let Some(new_owner) = recipients.iter().find(|x| *x != user_id) { if let Some(new_owner) = recipients.iter().find(|x| *x != &user.id) {
db.update_channel( db.update_channel(
id, id,
&PartialChannel { &PartialChannel {
@@ -492,14 +503,22 @@ impl Channel {
) )
.await?; .await?;
/* TODO: SystemMessage::ChannelOwnershipChanged { SystemMessage::ChannelOwnershipChanged {
from: owner.to_string(), from: owner.to_string(),
to: new_owner.into(), to: new_owner.to_string(),
} }
.into_message(id.to_string()) .into_message(id.to_string())
.create(db, self, None) .send(
db,
MessageAuthor::System {
username: name,
avatar: None,
},
self,
false,
)
.await .await
.ok(); */ .ok();
} else { } else {
db.delete_channel(self).await?; db.delete_channel(self).await?;
return Ok(()); return Ok(());
@@ -508,26 +527,34 @@ impl Channel {
EventV1::ChannelGroupLeave { EventV1::ChannelGroupLeave {
id: id.to_string(), id: id.to_string(),
user: user_id.to_string(), user: user.id.to_string(),
} }
.p(id.to_string()) .p(id.to_string())
.await; .await;
if !silent { if !silent {
/* TODO: if let Some(_by) = by_id { if let Some(by) = by_id {
SystemMessage::UserRemove { SystemMessage::UserRemove {
id: user_id.to_string(), id: user.id.to_string(),
by: by.to_string(), by: by.to_string(),
} }
} else { } else {
SystemMessage::UserLeft { SystemMessage::UserLeft {
id: user_id.to_string(), id: user.id.to_string(),
} }
} }
.into_message(id.to_string()) .into_message(id.to_string())
.create(db, self, None) .send(
db,
MessageAuthor::System {
username: &user.username,
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
},
self,
false,
)
.await .await
.ok(); */ .ok();
} }
Ok(()) Ok(())

View File

@@ -172,6 +172,29 @@ auto_derived!(
} }
); );
#[allow(clippy::derivable_impls)]
impl Default for Message {
fn default() -> Self {
Self {
id: Default::default(),
nonce: None,
channel: Default::default(),
author: Default::default(),
webhook: None,
content: None,
system: None,
attachments: None,
edited: None,
embeds: None,
mentions: None,
replies: None,
reactions: Default::default(),
interactions: Default::default(),
masquerade: None,
}
}
}
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
impl Message { impl Message {
/// Send a message without any notifications /// Send a message without any notifications

View File

@@ -15,8 +15,7 @@ auto_derived_partial!(
pub id: MemberCompositeKey, pub id: MemberCompositeKey,
/// Time at which this user joined the server /// Time at which this user joined the server
#[serde(skip_serializing_if = "Option::is_none")] pub joined_at: Timestamp,
pub joined_at: Option<Timestamp>,
/// Member's nickname /// Member's nickname
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
@@ -61,6 +60,19 @@ auto_derived!(
} }
); );
impl Default for Member {
fn default() -> Self {
Self {
id: Default::default(),
joined_at: Timestamp::now_utc(),
nickname: None,
avatar: None,
roles: vec![],
timeout: None,
}
}
}
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
impl Member { impl Member {
/// Create a new member in a server /// Create a new member in a server
@@ -75,6 +87,10 @@ impl Member {
return Err(create_error!(Banned)); return Err(create_error!(Banned));
} }
if db.fetch_member(&server.id, &user.id).await.is_ok() {
return Err(create_error!(AlreadyInServer));
}
let member = Member { let member = Member {
id: MemberCompositeKey { id: MemberCompositeKey {
server: server.id.to_string(), server: server.id.to_string(),
@@ -155,13 +171,13 @@ impl Member {
db.update_member(&self.id, &partial, remove.clone()).await?; db.update_member(&self.id, &partial, remove.clone()).await?;
/* // TODO: EventV1::ServerMemberUpdate { EventV1::ServerMemberUpdate {
id: self.id.clone(), id: self.id.clone().into(),
data: partial, data: partial.into(),
clear: remove, clear: remove.into_iter().map(|field| field.into()).collect(),
} }
.p(self.id.server.clone()) .p(self.id.server.clone())
.await; */ .await;
Ok(()) Ok(())
} }

View File

@@ -133,6 +133,26 @@ pub static DISCRIMINATOR_SEARCH_SPACE: Lazy<HashSet<String>> = Lazy::new(|| {
set.into_iter().collect() set.into_iter().collect()
}); });
#[allow(clippy::derivable_impls)]
impl Default for User {
fn default() -> Self {
Self {
id: Default::default(),
username: Default::default(),
discriminator: Default::default(),
display_name: Default::default(),
avatar: Default::default(),
relations: Default::default(),
badges: Default::default(),
status: Default::default(),
profile: Default::default(),
flags: Default::default(),
privileged: Default::default(),
bot: Default::default(),
}
}
}
#[allow(clippy::disallowed_methods)] #[allow(clippy::disallowed_methods)]
impl User { impl User {
/// Create a new user /// Create a new user

View File

@@ -158,6 +158,10 @@ auto_derived!(
pub enum MessageAuthor<'a> { pub enum MessageAuthor<'a> {
User(&'a User), User(&'a User),
Webhook(&'a Webhook), Webhook(&'a Webhook),
System {
username: &'a str,
avatar: Option<&'a str>,
},
} }
impl Interactions { impl Interactions {
@@ -172,6 +176,7 @@ impl<'a> MessageAuthor<'a> {
match self { match self {
MessageAuthor::User(user) => &user.id, MessageAuthor::User(user) => &user.id,
MessageAuthor::Webhook(webhook) => &webhook.id, MessageAuthor::Webhook(webhook) => &webhook.id,
MessageAuthor::System { .. } => "00000000000000000000000000",
} }
} }
@@ -179,6 +184,7 @@ impl<'a> MessageAuthor<'a> {
match self { match self {
MessageAuthor::User(user) => user.avatar.as_ref().map(|file| file.id.as_str()), MessageAuthor::User(user) => user.avatar.as_ref().map(|file| file.id.as_str()),
MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref().map(|file| file.id.as_str()), MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref().map(|file| file.id.as_str()),
MessageAuthor::System { avatar, .. } => *avatar,
} }
} }
@@ -186,6 +192,7 @@ impl<'a> MessageAuthor<'a> {
match self { match self {
MessageAuthor::User(user) => &user.username, MessageAuthor::User(user) => &user.username,
MessageAuthor::Webhook(webhook) => &webhook.name, MessageAuthor::Webhook(webhook) => &webhook.name,
MessageAuthor::System { username, .. } => username,
} }
} }
} }

View File

@@ -10,8 +10,7 @@ auto_derived_partial!(
pub id: MemberCompositeKey, pub id: MemberCompositeKey,
/// Time at which this user joined the server /// Time at which this user joined the server
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] pub joined_at: Timestamp,
pub joined_at: Option<Timestamp>,
/// Member's nickname /// Member's nickname
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))] #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]

View File

@@ -88,6 +88,7 @@ pub enum ErrorType {
TooManyRoles { TooManyRoles {
max: usize, max: usize,
}, },
AlreadyInServer,
// ? Bot related errors // ? Bot related errors
ReachedMaximumBots, ReachedMaximumBots,

View File

@@ -43,6 +43,7 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::UnknownServer => Status::NotFound, ErrorType::UnknownServer => Status::NotFound,
ErrorType::InvalidRole => Status::NotFound, ErrorType::InvalidRole => Status::NotFound,
ErrorType::Banned => Status::Forbidden, ErrorType::Banned => Status::Forbidden,
ErrorType::AlreadyInServer => Status::Conflict,
ErrorType::TooManyServers { .. } => Status::BadRequest, ErrorType::TooManyServers { .. } => Status::BadRequest,
ErrorType::TooManyEmoji { .. } => Status::BadRequest, ErrorType::TooManyEmoji { .. } => Status::BadRequest,

View File

@@ -45,6 +45,8 @@ pub async fn invite_bot(
return Err(create_error!(BotIsPrivate)); return Err(create_error!(BotIsPrivate));
} }
let bot_user = db.fetch_user(&bot.id).await?;
match dest.into_inner() { match dest.into_inner() {
InviteBotDestination::Server { server } => { InviteBotDestination::Server { server } => {
let server = db.fetch_server(&server).await?; let server = db.fetch_server(&server).await?;
@@ -54,8 +56,7 @@ pub async fn invite_bot(
.await .await
.throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?; .throw_if_lacking_channel_permission(ChannelPermission::ManageServer)?;
let user = db.fetch_user(&bot.id).await?; Member::create(db, &server, &bot_user)
Member::create(db, &server, &user)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }
@@ -68,7 +69,7 @@ pub async fn invite_bot(
.throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?; .throw_if_lacking_channel_permission(ChannelPermission::InviteOthers)?;
channel channel
.add_user_to_group(db, &bot.id, &user.id) .add_user_to_group(db, &bot_user, &user.id)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }

View File

@@ -1,4 +1,4 @@
use crate::models::{channel::{Channel, FieldsChannel, PartialChannel}}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
use crate::{OverrideField, Result}; use crate::{OverrideField, Result};
#[async_trait] #[async_trait]
@@ -13,9 +13,6 @@ pub trait AbstractChannel: Sync + Send {
async fn insert_channel(&self, channel: &Channel) -> Result<()>; async fn insert_channel(&self, channel: &Channel) -> Result<()>;
/// Update an existing channel using some data /// Update an existing channel using some data
/// ! TODO: we need separate Channel::update which also sends out the relevant events
/// ! also applies to other methods I guess, try to restrict event bound methods to
/// ! the models themselves instead of the abstract database
async fn update_channel( async fn update_channel(
&self, &self,
id: &str, id: &str,