forked from jmug/stoatchat
feat: ensure bot invites work correctly
This commit is contained in:
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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<()> {
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"))]
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ pub enum ErrorType {
|
|||||||
TooManyRoles {
|
TooManyRoles {
|
||||||
max: usize,
|
max: usize,
|
||||||
},
|
},
|
||||||
|
AlreadyInServer,
|
||||||
|
|
||||||
// ? Bot related errors
|
// ? Bot related errors
|
||||||
ReachedMaximumBots,
|
ReachedMaximumBots,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user