inital webhook support
This commit is contained in:
@@ -11,7 +11,7 @@ pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResp
|
|||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.author != user.id {
|
if message.author.as_deref().unwrap_or_default() != user.id {
|
||||||
perms(&user)
|
perms(&user)
|
||||||
.channel(&target.as_channel(db).await?)
|
.channel(&target.as_channel(db).await?)
|
||||||
.throw_permission(db, Permission::ManageMessages)
|
.throw_permission(db, Permission::ManageMessages)
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ pub async fn req(
|
|||||||
return Err(Error::NotFound);
|
return Err(Error::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
if message.author != user.id {
|
if message.author.as_deref().unwrap_or_default() != user.id {
|
||||||
return Err(Error::CannotEditMessage);
|
return Err(Error::CannotEditMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use revolt_quark::{
|
|||||||
},
|
},
|
||||||
perms,
|
perms,
|
||||||
web::idempotency::IdempotencyKey,
|
web::idempotency::IdempotencyKey,
|
||||||
Db, Error, Permission, Ref, Result,
|
Db, Error, Permission, Ref, Result, types::push::MessageAuthor,
|
||||||
};
|
};
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -22,31 +22,31 @@ pub struct DataMessageSend {
|
|||||||
///
|
///
|
||||||
/// **This is deprecated and replaced by `Idempotency-Key`!**
|
/// **This is deprecated and replaced by `Idempotency-Key`!**
|
||||||
#[validate(length(min = 1, max = 64))]
|
#[validate(length(min = 1, max = 64))]
|
||||||
nonce: Option<String>,
|
pub nonce: Option<String>,
|
||||||
|
|
||||||
/// Message content to send
|
/// Message content to send
|
||||||
#[validate(length(min = 0, max = 2000))]
|
#[validate(length(min = 0, max = 2000))]
|
||||||
content: Option<String>,
|
pub content: Option<String>,
|
||||||
/// Attachments to include in message
|
/// Attachments to include in message
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
attachments: Option<Vec<String>>,
|
pub attachments: Option<Vec<String>>,
|
||||||
/// Messages to reply to
|
/// Messages to reply to
|
||||||
replies: Option<Vec<Reply>>,
|
pub replies: Option<Vec<Reply>>,
|
||||||
/// Embeds to include in message
|
/// Embeds to include in message
|
||||||
///
|
///
|
||||||
/// Text embed content contributes to the content length cap
|
/// Text embed content contributes to the content length cap
|
||||||
#[validate(length(min = 1, max = 10))]
|
#[validate(length(min = 1, max = 10))]
|
||||||
embeds: Option<Vec<SendableEmbed>>,
|
pub embeds: Option<Vec<SendableEmbed>>,
|
||||||
/// Masquerade to apply to this message
|
/// Masquerade to apply to this message
|
||||||
#[validate]
|
#[validate]
|
||||||
masquerade: Option<Masquerade>,
|
pub masquerade: Option<Masquerade>,
|
||||||
/// Information about how this message should be interacted with
|
/// Information about how this message should be interacted with
|
||||||
interactions: Option<Interactions>,
|
pub interactions: Option<Interactions>,
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
// ignoring I L O and U is intentional
|
// ignoring I L O and U is intentional
|
||||||
static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
pub static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Send Message
|
/// # Send Message
|
||||||
@@ -86,7 +86,7 @@ pub async fn message_send(
|
|||||||
let mut message = Message {
|
let mut message = Message {
|
||||||
id: message_id.clone(),
|
id: message_id.clone(),
|
||||||
channel: channel.id().to_string(),
|
channel: channel.id().to_string(),
|
||||||
author: user.id.clone(),
|
author: Some(user.id.clone()),
|
||||||
masquerade: data.masquerade,
|
masquerade: data.masquerade,
|
||||||
interactions: data.interactions.unwrap_or_default(),
|
interactions: data.interactions.unwrap_or_default(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -128,11 +128,11 @@ pub async fn message_send(
|
|||||||
for Reply { id, mention } in entries {
|
for Reply { id, mention } in entries {
|
||||||
let message = Ref::from_unchecked(id).as_message(db).await?;
|
let message = Ref::from_unchecked(id).as_message(db).await?;
|
||||||
|
|
||||||
replies.insert(message.id);
|
|
||||||
|
|
||||||
if mention {
|
if mention {
|
||||||
mentions.insert(message.author);
|
mentions.insert(message.author_id().to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
replies.insert(message.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ pub async fn message_send(
|
|||||||
// 8. Pass-through nonce value for clients
|
// 8. Pass-through nonce value for clients
|
||||||
message.nonce = Some(idempotency.into_key());
|
message.nonce = Some(idempotency.into_key());
|
||||||
|
|
||||||
message.create(db, &channel, Some(&user)).await?;
|
message.create(db, &channel, Some(MessageAuthor::User(&user))).await?;
|
||||||
|
|
||||||
// Queue up a task for processing embeds
|
// Queue up a task for processing embeds
|
||||||
if let Some(content) = &message.content {
|
if let Some(content) = &message.content {
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ mod message_query;
|
|||||||
mod message_query_stale;
|
mod message_query_stale;
|
||||||
mod message_react;
|
mod message_react;
|
||||||
mod message_search;
|
mod message_search;
|
||||||
mod message_send;
|
pub mod message_send;
|
||||||
mod message_unreact;
|
mod message_unreact;
|
||||||
mod permissions_set;
|
mod permissions_set;
|
||||||
mod permissions_set_default;
|
mod permissions_set_default;
|
||||||
mod voice_join;
|
mod voice_join;
|
||||||
|
mod webhook_create;
|
||||||
|
mod webhook_fetch_all;
|
||||||
|
|
||||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
openapi_get_routes_spec![
|
openapi_get_routes_spec![
|
||||||
@@ -49,6 +51,8 @@ pub fn routes() -> (Vec<Route>, OpenApi) {
|
|||||||
permissions_set_default::req,
|
permissions_set_default::req,
|
||||||
message_react::react_message,
|
message_react::react_message,
|
||||||
message_unreact::unreact_message,
|
message_unreact::unreact_message,
|
||||||
message_clear_reactions::clear_reactions
|
message_clear_reactions::clear_reactions,
|
||||||
|
webhook_create::req,
|
||||||
|
webhook_fetch_all::req,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
50
crates/delta/src/routes/channels/webhook_create.rs
Normal file
50
crates/delta/src/routes/channels/webhook_create.rs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
use revolt_quark::{models::{User, Webhook, File}, perms, Db, Error, Permission, Ref, Result};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ulid::Ulid;
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub struct CreateWebhookBody {
|
||||||
|
#[validate(length(min = 1, max = 32))]
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
#[validate(length(min = 1, max = 128))]
|
||||||
|
avatar: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Creates a webhook
|
||||||
|
///
|
||||||
|
/// creates a webhook which 3rd party platforms can use to send messages
|
||||||
|
#[openapi(tag = "Webhooks")]
|
||||||
|
#[post("/<target>/webhooks", data = "<data>")]
|
||||||
|
pub async fn req(db: &Db, user: User, target: Ref, data: Json<CreateWebhookBody>) -> Result<Json<Webhook>> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
let mut permissions = perms(&user).channel(&channel);
|
||||||
|
permissions
|
||||||
|
.has_permission(db, Permission::ManageWebhooks)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let webhook_id = Ulid::new().to_string();
|
||||||
|
|
||||||
|
let avatar = match &data.avatar {
|
||||||
|
Some(id) => Some(File::use_avatar(db, id, &webhook_id).await?),
|
||||||
|
None => None
|
||||||
|
};
|
||||||
|
|
||||||
|
let webhook = Webhook {
|
||||||
|
id: webhook_id,
|
||||||
|
name: data.name,
|
||||||
|
avatar,
|
||||||
|
channel: channel.id().to_string(),
|
||||||
|
token: nanoid::nanoid!(64)
|
||||||
|
};
|
||||||
|
|
||||||
|
webhook.create(db).await?;
|
||||||
|
|
||||||
|
Ok(Json(webhook))
|
||||||
|
}
|
||||||
19
crates/delta/src/routes/channels/webhook_fetch_all.rs
Normal file
19
crates/delta/src/routes/channels/webhook_fetch_all.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use revolt_quark::{models::{User, Webhook}, perms, Db, Permission, Ref, Result};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # Gets all webhooks
|
||||||
|
///
|
||||||
|
/// gets all webhooks inside the channel
|
||||||
|
#[openapi(tag = "Webhooks")]
|
||||||
|
#[get("/<target>/webhooks")]
|
||||||
|
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<Webhook>>> {
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
let mut permissions = perms(&user).channel(&channel);
|
||||||
|
permissions
|
||||||
|
.has_permission(db, Permission::ManageWebhooks)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let webhooks = db.fetch_webhooks_for_channel(channel.id()).await?;
|
||||||
|
|
||||||
|
Ok(Json(webhooks))
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ mod root;
|
|||||||
mod servers;
|
mod servers;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod users;
|
mod users;
|
||||||
|
mod webhooks;
|
||||||
|
|
||||||
pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
let settings = OpenApiSettings::default();
|
let settings = OpenApiSettings::default();
|
||||||
@@ -33,6 +34,7 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
|
|||||||
"/onboard" => onboard::routes(),
|
"/onboard" => onboard::routes(),
|
||||||
"/push" => push::routes(),
|
"/push" => push::routes(),
|
||||||
"/sync" => sync::routes(),
|
"/sync" => sync::routes(),
|
||||||
|
"/webhooks" => webhooks::routes()
|
||||||
};
|
};
|
||||||
|
|
||||||
rocket
|
rocket
|
||||||
@@ -82,7 +84,8 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
"Messaging",
|
"Messaging",
|
||||||
"Interactions",
|
"Interactions",
|
||||||
"Groups",
|
"Groups",
|
||||||
"Voice"
|
"Voice",
|
||||||
|
"Webhooks",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -277,6 +280,13 @@ fn custom_openapi_spec() -> OpenApi {
|
|||||||
),
|
),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
Tag {
|
||||||
|
name: "Webhooks".to_owned(),
|
||||||
|
description: Some(
|
||||||
|
"Send messages from 3rd party services".to_owned(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|||||||
16
crates/delta/src/routes/webhooks/mod.rs
Normal file
16
crates/delta/src/routes/webhooks/mod.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use rocket::Route;
|
||||||
|
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||||
|
|
||||||
|
mod webhook_delete;
|
||||||
|
mod webhook_edit;
|
||||||
|
mod webhook_execute;
|
||||||
|
mod webhook_fetch;
|
||||||
|
|
||||||
|
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||||
|
openapi_get_routes_spec![
|
||||||
|
webhook_delete::req,
|
||||||
|
webhook_edit::req,
|
||||||
|
webhook_execute::req,
|
||||||
|
webhook_fetch::req,
|
||||||
|
]
|
||||||
|
}
|
||||||
18
crates/delta/src/routes/webhooks/webhook_delete.rs
Normal file
18
crates/delta/src/routes/webhooks/webhook_delete.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use revolt_quark::{Db, Ref, Result, EmptyResponse, Error};
|
||||||
|
|
||||||
|
/// # Deletes a webhook
|
||||||
|
///
|
||||||
|
/// deletes a webhook
|
||||||
|
#[openapi(tag = "Webhooks")]
|
||||||
|
#[delete("/<target>/<token>")]
|
||||||
|
pub async fn req(db: &Db, target: Ref, token: String) -> Result<EmptyResponse> {
|
||||||
|
let webhook = target.as_webhook(db).await?;
|
||||||
|
|
||||||
|
(webhook.token == token)
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(Error::InvalidCredentials)?;
|
||||||
|
|
||||||
|
webhook.delete(db).await?;
|
||||||
|
|
||||||
|
Ok(EmptyResponse)
|
||||||
|
}
|
||||||
57
crates/delta/src/routes/webhooks/webhook_edit.rs
Normal file
57
crates/delta/src/routes/webhooks/webhook_edit.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
use revolt_quark::{Db, Ref, Result, Error, models::{webhook::{FieldsWebhook, Webhook, PartialWebhook}, File}};
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use validator::Validate;
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Validate, JsonSchema)]
|
||||||
|
pub struct WebhookEditBody {
|
||||||
|
#[validate(length(min = 1, max = 32))]
|
||||||
|
name: Option<String>,
|
||||||
|
|
||||||
|
#[validate(length(min = 1, max = 128))]
|
||||||
|
avatar: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
remove: Vec<FieldsWebhook>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Edits a webhook
|
||||||
|
///
|
||||||
|
/// edits a webhook
|
||||||
|
#[openapi(tag = "Webhooks")]
|
||||||
|
#[patch("/<target>/<token>", data="<data>")]
|
||||||
|
pub async fn req(db: &Db, target: Ref, token: String, data: Json<WebhookEditBody>) -> Result<Json<Webhook>> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
let mut webhook = target.as_webhook(db).await?;
|
||||||
|
|
||||||
|
(webhook.token == token)
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(Error::InvalidCredentials)?;
|
||||||
|
|
||||||
|
if data.name.is_none()
|
||||||
|
&& data.avatar.is_none()
|
||||||
|
&& data.remove.is_empty()
|
||||||
|
{
|
||||||
|
return Ok(Json(webhook))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut partial = PartialWebhook::default();
|
||||||
|
|
||||||
|
let WebhookEditBody { name, avatar, remove } = data;
|
||||||
|
|
||||||
|
if let Some(name) = name {
|
||||||
|
partial.name = Some(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(avatar) = avatar {
|
||||||
|
let file = File::use_avatar(db, &avatar, &webhook.id).await?;
|
||||||
|
partial.avatar = Some(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.update(db, partial, remove).await?;
|
||||||
|
|
||||||
|
Ok(Json(webhook))
|
||||||
|
}
|
||||||
138
crates/delta/src/routes/webhooks/webhook_execute.rs
Normal file
138
crates/delta/src/routes/webhooks/webhook_execute.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use revolt_quark::{Db, Ref, Result, Error, models::{Message, message::Reply}, web::idempotency::IdempotencyKey, types::push::MessageAuthor};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use ulid::Ulid;
|
||||||
|
use crate::routes::channels::message_send::{DataMessageSend, RE_MENTION};
|
||||||
|
use validator::Validate;
|
||||||
|
|
||||||
|
/// # Executes a webhook
|
||||||
|
///
|
||||||
|
/// executes a webhook and sends a message
|
||||||
|
#[openapi(tag = "Webhooks")]
|
||||||
|
#[post("/<target>/<token>", data="<data>")]
|
||||||
|
pub async fn req(db: &Db, target: Ref, token: String, data: Json<DataMessageSend>, mut idempotency: IdempotencyKey) -> Result<Json<Message>> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
let webhook = target.as_webhook(db).await?;
|
||||||
|
|
||||||
|
(webhook.token == token)
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(Error::InvalidCredentials)?;
|
||||||
|
|
||||||
|
Message::validate_sum(&data.content, &data.embeds)?;
|
||||||
|
|
||||||
|
idempotency.consume_nonce(data.nonce).await?;
|
||||||
|
|
||||||
|
let channel = Ref::from_unchecked(webhook.channel.clone()).as_channel(db).await?;
|
||||||
|
|
||||||
|
if (data.content.as_ref().map_or(true, |v| v.is_empty()))
|
||||||
|
&& (data.attachments.as_ref().map_or(true, |v| v.is_empty()))
|
||||||
|
&& (data.embeds.as_ref().map_or(true, |v| v.is_empty()))
|
||||||
|
{
|
||||||
|
return Err(Error::EmptyMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message_id = Ulid::new().to_string();
|
||||||
|
let mut message = Message {
|
||||||
|
id: message_id.clone(),
|
||||||
|
channel: channel.id().to_string(),
|
||||||
|
webhook: Some(webhook.id.clone()),
|
||||||
|
masquerade: data.masquerade,
|
||||||
|
interactions: data.interactions.unwrap_or_default(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Parse mentions in message.
|
||||||
|
let mut mentions = HashSet::new();
|
||||||
|
if let Some(content) = &data.content {
|
||||||
|
for capture in RE_MENTION.captures_iter(content) {
|
||||||
|
if let Some(mention) = capture.get(1) {
|
||||||
|
mentions.insert(mention.as_str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Verify replies are valid.
|
||||||
|
let mut replies = HashSet::new();
|
||||||
|
if let Some(entries) = data.replies {
|
||||||
|
if entries.len() > 5 {
|
||||||
|
return Err(Error::TooManyReplies);
|
||||||
|
}
|
||||||
|
|
||||||
|
for Reply { id, mention } in entries {
|
||||||
|
let message = Ref::from_unchecked(id).as_message(db).await?;
|
||||||
|
|
||||||
|
if mention {
|
||||||
|
mentions.insert(message.author_id().to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
replies.insert(message.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mentions.is_empty() {
|
||||||
|
message.mentions.replace(mentions.into_iter().collect());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !replies.is_empty() {
|
||||||
|
message
|
||||||
|
.replies
|
||||||
|
.replace(replies.into_iter().collect::<Vec<String>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Process included embeds.
|
||||||
|
let mut embeds = vec![];
|
||||||
|
if let Some(sendable_embeds) = data.embeds {
|
||||||
|
for sendable_embed in sendable_embeds {
|
||||||
|
embeds.push(sendable_embed.into_embed(db, message_id.clone()).await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !embeds.is_empty() {
|
||||||
|
message.embeds.replace(embeds);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Add attachments to message.
|
||||||
|
let mut attachments = vec![];
|
||||||
|
if let Some(ids) = &data.attachments {
|
||||||
|
// ! FIXME: move this to app config
|
||||||
|
if ids.len() > 5 {
|
||||||
|
return Err(Error::TooManyAttachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
for attachment_id in ids {
|
||||||
|
attachments.push(
|
||||||
|
db.find_and_use_attachment(attachment_id, "attachments", "message", &message_id)
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachments.is_empty() {
|
||||||
|
message.attachments.replace(attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Set content
|
||||||
|
message.content = data.content;
|
||||||
|
|
||||||
|
// 8. Pass-through nonce value for clients
|
||||||
|
message.nonce = Some(idempotency.into_key());
|
||||||
|
|
||||||
|
message.create(db, &channel, Some(MessageAuthor::Webhook(&webhook))).await?;
|
||||||
|
|
||||||
|
// Queue up a task for processing embeds
|
||||||
|
if let Some(content) = &message.content {
|
||||||
|
revolt_quark::tasks::process_embeds::queue(
|
||||||
|
channel.id().to_string(),
|
||||||
|
message.id.to_string(),
|
||||||
|
content.clone(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(message))
|
||||||
|
|
||||||
|
}
|
||||||
17
crates/delta/src/routes/webhooks/webhook_fetch.rs
Normal file
17
crates/delta/src/routes/webhooks/webhook_fetch.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
use revolt_quark::{Db, Ref, Result, Error, models::Webhook};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
/// # gets a webhook
|
||||||
|
///
|
||||||
|
/// gets a webhook
|
||||||
|
#[openapi(tag = "Webhooks")]
|
||||||
|
#[get("/<target>/<token>")]
|
||||||
|
pub async fn req(db: &Db, target: Ref, token: String) -> Result<Json<Webhook>> {
|
||||||
|
let webhook = target.as_webhook(db).await?;
|
||||||
|
|
||||||
|
(webhook.token == token)
|
||||||
|
.then_some(())
|
||||||
|
.ok_or(Error::InvalidCredentials)?;
|
||||||
|
|
||||||
|
Ok(Json(webhook))
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::models::channel::{FieldsChannel, PartialChannel};
|
use crate::models::channel::{FieldsChannel, PartialChannel};
|
||||||
|
use crate::models::webhook::{PartialWebhook, FieldsWebhook};
|
||||||
use crate::models::message::{AppendMessage, PartialMessage};
|
use crate::models::message::{AppendMessage, PartialMessage};
|
||||||
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
|
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
|
||||||
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
|
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
|
||||||
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus};
|
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;
|
use crate::Error;
|
||||||
|
|
||||||
/// WebSocket Client Errors
|
/// WebSocket Client Errors
|
||||||
@@ -200,4 +201,19 @@ pub enum EventV1 {
|
|||||||
|
|
||||||
/// Delete emoji
|
/// Delete emoji
|
||||||
EmojiDelete { id: String },
|
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 crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
|
||||||
|
|
||||||
use super::super::DummyDb;
|
use super::super::DummyDb;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ impl AbstractMessage for DummyDb {
|
|||||||
Ok(Message {
|
Ok(Message {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
channel: "channel".into(),
|
channel: "channel".into(),
|
||||||
author: "author".into(),
|
author: Some("author".into()),
|
||||||
content: Some("message content".into()),
|
content: Some("message content".into()),
|
||||||
|
|
||||||
..Default::default()
|
..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_invite;
|
||||||
pub mod channel_unread;
|
pub mod channel_unread;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod servers {
|
pub mod servers {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{
|
|||||||
tasks::ack::AckEvent,
|
tasks::ack::AckEvent,
|
||||||
types::{
|
types::{
|
||||||
january::{Embed, Text},
|
january::{Embed, Text},
|
||||||
push::PushNotification,
|
push::{PushNotification, MessageAuthor},
|
||||||
},
|
},
|
||||||
Database, Error, Permission, Result,
|
Database, Error, Permission, Result,
|
||||||
};
|
};
|
||||||
@@ -66,7 +66,7 @@ impl Message {
|
|||||||
&mut self,
|
&mut self,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
channel: &Channel,
|
channel: &Channel,
|
||||||
sender: Option<&User>,
|
sender: Option<MessageAuthor<'_>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.create_no_web_push(db, channel.id(), channel.is_direct_dm())
|
self.create_no_web_push(db, channel.id(), channel.is_direct_dm())
|
||||||
.await?;
|
.await?;
|
||||||
@@ -271,6 +271,14 @@ impl Message {
|
|||||||
// Write to database
|
// Write to database
|
||||||
db.clear_reaction(&self.id, emoji).await
|
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 {
|
pub trait IntoUsers {
|
||||||
@@ -279,7 +287,11 @@ pub trait IntoUsers {
|
|||||||
|
|
||||||
impl IntoUsers for Message {
|
impl IntoUsers for Message {
|
||||||
fn get_user_ids(&self) -> Vec<String> {
|
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 {
|
if let Some(msg) = &self.system {
|
||||||
match msg {
|
match msg {
|
||||||
@@ -319,7 +331,7 @@ impl SystemMessage {
|
|||||||
Message {
|
Message {
|
||||||
id: Ulid::new().to_string(),
|
id: Ulid::new().to_string(),
|
||||||
channel,
|
channel,
|
||||||
author: "00000000000000000000000000".to_string(),
|
author: Some("00000000000000000000000000".to_string()),
|
||||||
system: Some(self),
|
system: Some(self),
|
||||||
|
|
||||||
..Default::default()
|
..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_invite;
|
||||||
pub mod channel_unread;
|
pub mod channel_unread;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod servers {
|
pub mod servers {
|
||||||
|
|||||||
@@ -37,9 +37,24 @@ impl MongoDb {
|
|||||||
operation: "delete_many",
|
operation: "delete_many",
|
||||||
with: "channel_unreads",
|
with: "channel_unreads",
|
||||||
})
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())?;
|
||||||
|
|
||||||
// update many attachments with parent id
|
// 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_invite;
|
||||||
pub mod channel_unread;
|
pub mod channel_unread;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod servers {
|
pub mod servers {
|
||||||
|
|||||||
@@ -117,9 +117,12 @@ pub struct Message {
|
|||||||
pub nonce: Option<String>,
|
pub nonce: Option<String>,
|
||||||
/// Id of the channel this message was sent in
|
/// Id of the channel this message was sent in
|
||||||
pub channel: String,
|
pub channel: String,
|
||||||
/// Id of the user that sent this message
|
/// Id of the user that sent this message, will be none when a webhook sent the message
|
||||||
pub author: String,
|
#[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
|
/// Message content
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub content: Option<String>,
|
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_invite;
|
||||||
pub mod channel_unread;
|
pub mod channel_unread;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod servers {
|
mod servers {
|
||||||
@@ -47,3 +48,4 @@ pub use server_member::Member;
|
|||||||
pub use simple::SimpleModel;
|
pub use simple::SimpleModel;
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
pub use user_settings::UserSettings;
|
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};
|
use crate::{OverrideField, Result};
|
||||||
|
|
||||||
#[async_trait]
|
#[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_invite;
|
||||||
pub mod channel_unread;
|
pub mod channel_unread;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
pub mod webhook;
|
||||||
}
|
}
|
||||||
|
|
||||||
mod servers {
|
mod servers {
|
||||||
@@ -35,6 +36,7 @@ pub use channels::channel::AbstractChannel;
|
|||||||
pub use channels::channel_invite::AbstractChannelInvite;
|
pub use channels::channel_invite::AbstractChannelInvite;
|
||||||
pub use channels::channel_unread::AbstractChannelUnread;
|
pub use channels::channel_unread::AbstractChannelUnread;
|
||||||
pub use channels::message::AbstractMessage;
|
pub use channels::message::AbstractMessage;
|
||||||
|
pub use channels::webhook::AbstractWebhook;
|
||||||
|
|
||||||
pub use servers::server::AbstractServer;
|
pub use servers::server::AbstractServer;
|
||||||
pub use servers::server_ban::AbstractServerBan;
|
pub use servers::server_ban::AbstractServerBan;
|
||||||
@@ -60,5 +62,6 @@ pub trait AbstractDatabase:
|
|||||||
+ AbstractBot
|
+ AbstractBot
|
||||||
+ AbstractUser
|
+ AbstractUser
|
||||||
+ AbstractUserSettings
|
+ AbstractUserSettings
|
||||||
|
+ AbstractWebhook
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::time::SystemTime;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
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};
|
use crate::variables::delta::{APP_URL, AUTUMN_URL, PUBLIC_URL};
|
||||||
|
|
||||||
/// Push Notification
|
/// Push Notification
|
||||||
@@ -25,14 +25,42 @@ pub struct PushNotification {
|
|||||||
pub url: String,
|
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 {
|
impl PushNotification {
|
||||||
/// Create a new notification from a given message, author and channel ID
|
/// Create a new notification from a given message, author and channel ID
|
||||||
pub fn new(msg: Message, author: Option<&User>, channel_id: &str) -> Self {
|
pub fn new(msg: Message, author: Option<MessageAuthor<'_>>, channel_id: &str) -> Self {
|
||||||
let icon = if let Some(author) = author {
|
let icon = if let Some(author) = &author {
|
||||||
if let Some(avatar) = &author.avatar {
|
if let Some(avatar) = author.avatar() {
|
||||||
format!("{}/avatars/{}", &*AUTUMN_URL, avatar.id)
|
format!("{}/avatars/{}", &*AUTUMN_URL, avatar.id)
|
||||||
} else {
|
} else {
|
||||||
format!("{}/users/{}/default_avatar", &*PUBLIC_URL, msg.author)
|
format!("{}/users/{}/default_avatar", &*PUBLIC_URL, author.id())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
format!("{}/assets/logo.png", &*APP_URL)
|
format!("{}/assets/logo.png", &*APP_URL)
|
||||||
@@ -59,7 +87,7 @@ impl PushNotification {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
author: author
|
author: author
|
||||||
.map(|x| x.username.to_string())
|
.map(|x| x.username().to_string())
|
||||||
.unwrap_or_else(|| "Revolt".to_string()),
|
.unwrap_or_else(|| "Revolt".to_string()),
|
||||||
icon,
|
icon,
|
||||||
image,
|
image,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use schemars::schema::{InstanceType, Schema, SchemaObject, SingleOrVec};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
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::presence::presence_is_online;
|
||||||
use crate::{Database, Error, Result};
|
use crate::{Database, Error, Result};
|
||||||
|
|
||||||
@@ -78,6 +78,11 @@ impl Ref {
|
|||||||
pub async fn as_emoji(&self, db: &Database) -> Result<Emoji> {
|
pub async fn as_emoji(&self, db: &Database) -> Result<Emoji> {
|
||||||
db.fetch_emoji(&self.id).await
|
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 {
|
impl<'r> FromParam<'r> for Ref {
|
||||||
|
|||||||
Reference in New Issue
Block a user