forked from jmug/stoatchat
inital webhook support
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::channel::{FieldsChannel, PartialChannel};
|
||||
use crate::models::webhook::{PartialWebhook, FieldsWebhook};
|
||||
use crate::models::message::{AppendMessage, PartialMessage};
|
||||
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
|
||||
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
|
||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus};
|
||||
use crate::models::{Channel, Emoji, Member, Message, Server, User, UserSettings};
|
||||
use crate::models::{Channel, Emoji, Member, Message, Server, User, UserSettings, Webhook};
|
||||
use crate::Error;
|
||||
|
||||
/// WebSocket Client Errors
|
||||
@@ -200,4 +201,19 @@ pub enum EventV1 {
|
||||
|
||||
/// Delete emoji
|
||||
EmojiDelete { id: String },
|
||||
|
||||
/// New webhook
|
||||
WebhookCreate(Webhook),
|
||||
|
||||
/// Update existing webhook
|
||||
WebhookUpdate {
|
||||
id: String,
|
||||
data: PartialWebhook,
|
||||
remove: Vec<FieldsWebhook>
|
||||
},
|
||||
|
||||
/// Delete webhook
|
||||
WebhookDelete {
|
||||
id: String
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||
use crate::models::{channel::{Channel, FieldsChannel, PartialChannel}};
|
||||
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
@@ -9,7 +9,7 @@ impl AbstractMessage for DummyDb {
|
||||
Ok(Message {
|
||||
id: id.into(),
|
||||
channel: "channel".into(),
|
||||
author: "author".into(),
|
||||
author: Some("author".into()),
|
||||
content: Some("message content".into()),
|
||||
|
||||
..Default::default()
|
||||
|
||||
40
crates/quark/src/impl/dummy/channels/webhook.rs
Normal file
40
crates/quark/src/impl/dummy/channels/webhook.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use crate::models::webhook::{Webhook, FieldsWebhook, PartialWebhook};
|
||||
use crate::{AbstractWebhook, Result};
|
||||
|
||||
use super::super::DummyDb;
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractWebhook for DummyDb {
|
||||
async fn insert_webhook(&self, webhook: &Webhook) -> Result<()> {
|
||||
info!("Created webhook {} in {}", webhook.name, webhook.channel);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_webhook(&self, webhook_id: &str) -> Result<()> {
|
||||
info!("deleting webhook {webhook_id}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_webhook(&self, webhook_id: &str) -> Result<Webhook> {
|
||||
Ok(Webhook {
|
||||
id: webhook_id.to_string(),
|
||||
name: "".to_string(),
|
||||
avatar: None,
|
||||
channel: "0".to_string(),
|
||||
token: "".to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn update_webook(&self, webhook_id: &str, partial_webhook: &PartialWebhook, remove: &[FieldsWebhook]) -> Result<()> {
|
||||
info!("updating webhook {webhook_id}, {partial_webhook:?}, removing {remove:?}");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_webhooks_for_channel(&self, channel: &str) -> Result<Vec<Webhook>> {
|
||||
info!("fetching webhooks for channel {channel}");
|
||||
|
||||
Ok(vec![self.fetch_webhook("0").await?])
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ pub mod channels {
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
pub mod webhook;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
|
||||
@@ -18,7 +18,7 @@ use crate::{
|
||||
tasks::ack::AckEvent,
|
||||
types::{
|
||||
january::{Embed, Text},
|
||||
push::PushNotification,
|
||||
push::{PushNotification, MessageAuthor},
|
||||
},
|
||||
Database, Error, Permission, Result,
|
||||
};
|
||||
@@ -66,7 +66,7 @@ impl Message {
|
||||
&mut self,
|
||||
db: &Database,
|
||||
channel: &Channel,
|
||||
sender: Option<&User>,
|
||||
sender: Option<MessageAuthor<'_>>,
|
||||
) -> Result<()> {
|
||||
self.create_no_web_push(db, channel.id(), channel.is_direct_dm())
|
||||
.await?;
|
||||
@@ -271,6 +271,14 @@ impl Message {
|
||||
// Write to database
|
||||
db.clear_reaction(&self.id, emoji).await
|
||||
}
|
||||
|
||||
/// Gets either the user or the webhook id which sent this message
|
||||
pub fn author_id(&self) -> &str {
|
||||
match &self.author {
|
||||
Some(id) => id,
|
||||
None => self.webhook.as_deref().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoUsers {
|
||||
@@ -279,7 +287,11 @@ pub trait IntoUsers {
|
||||
|
||||
impl IntoUsers for Message {
|
||||
fn get_user_ids(&self) -> Vec<String> {
|
||||
let mut ids = vec![self.author.clone()];
|
||||
let mut ids = Vec::new();
|
||||
|
||||
if let Some(author) = &self.author {
|
||||
ids.push(author.clone());
|
||||
};
|
||||
|
||||
if let Some(msg) = &self.system {
|
||||
match msg {
|
||||
@@ -319,7 +331,7 @@ impl SystemMessage {
|
||||
Message {
|
||||
id: Ulid::new().to_string(),
|
||||
channel,
|
||||
author: "00000000000000000000000000".to_string(),
|
||||
author: Some("00000000000000000000000000".to_string()),
|
||||
system: Some(self),
|
||||
|
||||
..Default::default()
|
||||
|
||||
58
crates/quark/src/impl/generic/channels/webhook.rs
Normal file
58
crates/quark/src/impl/generic/channels/webhook.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use crate::{
|
||||
events::client::EventV1,
|
||||
models::{
|
||||
webhook::{Webhook, PartialWebhook, FieldsWebhook}
|
||||
},
|
||||
Database, Result
|
||||
};
|
||||
|
||||
impl Webhook {
|
||||
pub async fn create(&self, db: &Database) -> Result<()> {
|
||||
db.insert_webhook(self).await?;
|
||||
|
||||
EventV1::WebhookCreate(self.clone())
|
||||
.p(self.channel.clone()).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete(&self, db: &Database) -> Result<()> {
|
||||
db.delete_webhook(&self.id).await?;
|
||||
|
||||
EventV1::WebhookDelete { id: self.id.clone() }
|
||||
.p(self.channel.clone()).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
&mut self,
|
||||
db: &Database,
|
||||
partial: PartialWebhook,
|
||||
remove: Vec<FieldsWebhook>
|
||||
) -> Result<()> {
|
||||
for field in &remove {
|
||||
self.remove(field)
|
||||
};
|
||||
|
||||
self.apply_options(partial.clone());
|
||||
|
||||
db.update_webook(&self.id, &partial, &remove).await?;
|
||||
|
||||
EventV1::WebhookUpdate {
|
||||
id: self.id.clone(),
|
||||
data: partial,
|
||||
remove
|
||||
}
|
||||
.p(self.channel.clone())
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove(&mut self, field: &FieldsWebhook) {
|
||||
match field {
|
||||
FieldsWebhook::Avatar => self.avatar = None
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ pub mod channels {
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
pub mod webhook;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
|
||||
@@ -37,9 +37,24 @@ impl MongoDb {
|
||||
operation: "delete_many",
|
||||
with: "channel_unreads",
|
||||
})
|
||||
.map(|_| ())
|
||||
.map(|_| ())?;
|
||||
|
||||
// update many attachments with parent id
|
||||
|
||||
// Delete all webhooks on this channel.
|
||||
self.col::<Document>("webhooks")
|
||||
.delete_many(
|
||||
doc! {
|
||||
"channel": &id
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map_err(|_| Error::DatabaseError {
|
||||
operation: "delete_many",
|
||||
with: "webhooks",
|
||||
})
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
57
crates/quark/src/impl/mongo/channels/webhook.rs
Normal file
57
crates/quark/src/impl/mongo/channels/webhook.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::models::{webhook::{Webhook, PartialWebhook, FieldsWebhook}};
|
||||
use crate::r#impl::mongo::IntoDocumentPath;
|
||||
use crate::{Result, AbstractWebhook};
|
||||
|
||||
use super::super::MongoDb;
|
||||
|
||||
static COL: &str = "webhooks";
|
||||
|
||||
#[async_trait]
|
||||
impl AbstractWebhook for MongoDb {
|
||||
async fn insert_webhook(&self, webhook: &Webhook) -> Result<()> {
|
||||
self.insert_one(COL, webhook).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_webhook(&self, webhook_id: &str) -> Result<Webhook> {
|
||||
info!("{COL} {webhook_id}");
|
||||
|
||||
self.find_one_by_id(COL, webhook_id).await
|
||||
}
|
||||
|
||||
async fn delete_webhook(&self, webhook_id: &str) -> Result<()> {
|
||||
self.delete_one_by_id(COL, webhook_id).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_webook(&self, webhook_id: &str, partial_webhook: &PartialWebhook, remove: &[FieldsWebhook]) -> Result<()> {
|
||||
self.update_one_by_id(
|
||||
COL,
|
||||
webhook_id,
|
||||
partial_webhook,
|
||||
remove.iter().map(|x| x as &dyn IntoDocumentPath).collect(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
|
||||
async fn fetch_webhooks_for_channel(&self, channel: &str) -> Result<Vec<Webhook>> {
|
||||
self.find(
|
||||
COL,
|
||||
doc! {
|
||||
"channel": channel
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDocumentPath for FieldsWebhook {
|
||||
fn as_path(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
FieldsWebhook::Avatar => Some("avatar"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ pub mod channels {
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
pub mod webhook;
|
||||
}
|
||||
|
||||
pub mod servers {
|
||||
|
||||
@@ -117,9 +117,12 @@ pub struct Message {
|
||||
pub nonce: Option<String>,
|
||||
/// Id of the channel this message was sent in
|
||||
pub channel: String,
|
||||
/// Id of the user that sent this message
|
||||
pub author: String,
|
||||
|
||||
/// Id of the user that sent this message, will be none when a webhook sent the message
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub author: Option<String>,
|
||||
/// Id of the webhook that sent this message, mutually exclusive with `author`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub webhook: Option<String>,
|
||||
/// Message content
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub content: Option<String>,
|
||||
|
||||
33
crates/quark/src/models/channels/webhook.rs
Normal file
33
crates/quark/src/models/channels/webhook.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use crate::models::File;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Respresents a webhook
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, OptionalStruct)]
|
||||
#[optional_derive(Serialize, Deserialize, JsonSchema, Debug, Default, Clone)]
|
||||
#[optional_name = "PartialWebhook"]
|
||||
#[opt_skip_serializing_none]
|
||||
#[opt_some_priority]
|
||||
|
||||
pub struct Webhook {
|
||||
/// Unique Id
|
||||
#[serde(rename = "_id")]
|
||||
pub id: String,
|
||||
|
||||
/// The name of the webhook
|
||||
pub name: String,
|
||||
|
||||
/// The avatar of the webhook
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avatar: Option<File>,
|
||||
|
||||
/// The channel this webhook belongs to
|
||||
pub channel: String,
|
||||
|
||||
/// The private token for the webhook
|
||||
pub token: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
|
||||
pub enum FieldsWebhook {
|
||||
Avatar
|
||||
}
|
||||
@@ -13,6 +13,7 @@ mod channels {
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
pub mod webhook;
|
||||
}
|
||||
|
||||
mod servers {
|
||||
@@ -47,3 +48,4 @@ pub use server_member::Member;
|
||||
pub use simple::SimpleModel;
|
||||
pub use user::User;
|
||||
pub use user_settings::UserSettings;
|
||||
pub use webhook::Webhook;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
|
||||
use crate::models::{channel::{Channel, FieldsChannel, PartialChannel}};
|
||||
use crate::{OverrideField, Result};
|
||||
|
||||
#[async_trait]
|
||||
|
||||
11
crates/quark/src/traits/channels/webhook.rs
Normal file
11
crates/quark/src/traits/channels/webhook.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use crate::models::webhook::{Webhook, PartialWebhook, FieldsWebhook};
|
||||
use crate::Result;
|
||||
|
||||
#[async_trait]
|
||||
pub trait AbstractWebhook: Sync + Send {
|
||||
async fn insert_webhook(&self, webhook: &Webhook) -> Result<()>;
|
||||
async fn fetch_webhook(&self, webhook_id: &str) -> Result<Webhook>;
|
||||
async fn delete_webhook(&self, webhook_id: &str) -> Result<()>;
|
||||
async fn update_webook(&self, webhook_id: &str, partial_webhook: &PartialWebhook, remove: &[FieldsWebhook]) -> Result<()>;
|
||||
async fn fetch_webhooks_for_channel(&self, channel: &str) -> Result<Vec<Webhook>>;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ mod channels {
|
||||
pub mod channel_invite;
|
||||
pub mod channel_unread;
|
||||
pub mod message;
|
||||
pub mod webhook;
|
||||
}
|
||||
|
||||
mod servers {
|
||||
@@ -35,6 +36,7 @@ pub use channels::channel::AbstractChannel;
|
||||
pub use channels::channel_invite::AbstractChannelInvite;
|
||||
pub use channels::channel_unread::AbstractChannelUnread;
|
||||
pub use channels::message::AbstractMessage;
|
||||
pub use channels::webhook::AbstractWebhook;
|
||||
|
||||
pub use servers::server::AbstractServer;
|
||||
pub use servers::server_ban::AbstractServerBan;
|
||||
@@ -60,5 +62,6 @@ pub trait AbstractDatabase:
|
||||
+ AbstractBot
|
||||
+ AbstractUser
|
||||
+ AbstractUserSettings
|
||||
+ AbstractWebhook
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::time::SystemTime;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::{Message, User};
|
||||
use crate::models::{Message, User, Webhook, File};
|
||||
use crate::variables::delta::{APP_URL, AUTUMN_URL, PUBLIC_URL};
|
||||
|
||||
/// Push Notification
|
||||
@@ -25,14 +25,42 @@ pub struct PushNotification {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
pub enum MessageAuthor<'a> {
|
||||
User(&'a User),
|
||||
Webhook(&'a Webhook)
|
||||
}
|
||||
|
||||
impl<'a> MessageAuthor<'a> {
|
||||
pub fn id(&self) -> &str {
|
||||
match self {
|
||||
MessageAuthor::User(user) => &user.id,
|
||||
MessageAuthor::Webhook(webhook) => &webhook.id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn avatar(&self) -> Option<&File> {
|
||||
match self {
|
||||
MessageAuthor::User(user) => user.avatar.as_ref(),
|
||||
MessageAuthor::Webhook(webhook) => webhook.avatar.as_ref(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn username(&self) -> &str {
|
||||
match self {
|
||||
MessageAuthor::User(user) => &user.username,
|
||||
MessageAuthor::Webhook(webhook) => &webhook.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PushNotification {
|
||||
/// Create a new notification from a given message, author and channel ID
|
||||
pub fn new(msg: Message, author: Option<&User>, channel_id: &str) -> Self {
|
||||
let icon = if let Some(author) = author {
|
||||
if let Some(avatar) = &author.avatar {
|
||||
pub fn new(msg: Message, author: Option<MessageAuthor<'_>>, channel_id: &str) -> Self {
|
||||
let icon = if let Some(author) = &author {
|
||||
if let Some(avatar) = author.avatar() {
|
||||
format!("{}/avatars/{}", &*AUTUMN_URL, avatar.id)
|
||||
} else {
|
||||
format!("{}/users/{}/default_avatar", &*PUBLIC_URL, msg.author)
|
||||
format!("{}/users/{}/default_avatar", &*PUBLIC_URL, author.id())
|
||||
}
|
||||
} else {
|
||||
format!("{}/assets/logo.png", &*APP_URL)
|
||||
@@ -59,7 +87,7 @@ impl PushNotification {
|
||||
|
||||
Self {
|
||||
author: author
|
||||
.map(|x| x.username.to_string())
|
||||
.map(|x| x.username().to_string())
|
||||
.unwrap_or_else(|| "Revolt".to_string()),
|
||||
icon,
|
||||
image,
|
||||
|
||||
@@ -4,7 +4,7 @@ use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::models::{Bot, Channel, Emoji, Invite, Member, Message, Server, ServerBan, User};
|
||||
use crate::models::{Bot, Channel, Emoji, Invite, Member, Message, Server, ServerBan, User, Webhook};
|
||||
use crate::presence::presence_is_online;
|
||||
use crate::{Database, Error, Result};
|
||||
|
||||
@@ -78,6 +78,11 @@ impl Ref {
|
||||
pub async fn as_emoji(&self, db: &Database) -> Result<Emoji> {
|
||||
db.fetch_emoji(&self.id).await
|
||||
}
|
||||
|
||||
/// Fetches webhook from Ref
|
||||
pub async fn as_webhook(&self, db: &Database) -> Result<Webhook> {
|
||||
db.fetch_webhook(&self.id).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> FromParam<'r> for Ref {
|
||||
|
||||
Reference in New Issue
Block a user