forked from jmug/stoatchat
feat(messaging): implement fetch and send
This commit is contained in:
@@ -1,11 +1,23 @@
|
|||||||
use revolt_quark::Result;
|
use revolt_quark::{models::{User, Message}, perms, Db, Error, Ref, Result};
|
||||||
|
|
||||||
use rocket::serde::json::Value;
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
#[get("/<target>/messages/<msg>")]
|
#[get("/<target>/messages/<msg>")]
|
||||||
pub async fn req(
|
pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<Json<Message>> {
|
||||||
/*user: UserRef, target: Ref, msg: Ref*/ target: String,
|
let channel = target.as_channel(db).await?;
|
||||||
msg: String,
|
if !perms(&user)
|
||||||
) -> Result<Value> {
|
.channel(&channel)
|
||||||
todo!()
|
.calc_channel(db)
|
||||||
|
.await
|
||||||
|
.get_view()
|
||||||
|
{
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = msg.as_message(db).await?;
|
||||||
|
if message.channel != channel.as_id() {
|
||||||
|
return Err(Error::NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(message))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,28 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use revolt_quark::{
|
use revolt_quark::{
|
||||||
models::{message::Masquerade, User},
|
models::{
|
||||||
Ref, Result,
|
message::{Content, Masquerade, Reply, SendableEmbed},
|
||||||
|
Message, User,
|
||||||
|
},
|
||||||
|
perms, ChannelPermission, Db, Error, Ref, Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rocket::serde::json::{Json, Value};
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ulid::Ulid;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Reply {
|
|
||||||
id: String,
|
|
||||||
mention: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Validate, Serialize, Deserialize, Clone, Debug)]
|
|
||||||
pub struct SendableEmbed {
|
|
||||||
icon_url: Option<String>,
|
|
||||||
url: Option<String>,
|
|
||||||
#[validate(length(min = 1, max = 100))]
|
|
||||||
title: Option<String>,
|
|
||||||
#[validate(length(min = 1, max = 2000))]
|
|
||||||
description: Option<String>,
|
|
||||||
media: Option<String>,
|
|
||||||
colour: Option<String>,
|
|
||||||
}
|
|
||||||
#[derive(Validate, Serialize, Deserialize)]
|
#[derive(Validate, Serialize, Deserialize)]
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
#[validate(length(min = 0, max = 2000))]
|
#[validate(length(min = 0, max = 2000))]
|
||||||
content: String,
|
content: String,
|
||||||
#[validate(length(min = 1, max = 128))]
|
#[validate(length(min = 1, max = 128))]
|
||||||
attachments: Option<Vec<String>>,
|
attachments: Option<Vec<String>>,
|
||||||
nonce: Option<String>,
|
|
||||||
replies: Option<Vec<Reply>>,
|
replies: Option<Vec<Reply>>,
|
||||||
//#[validate]
|
#[validate]
|
||||||
masquerade: Option<Masquerade>,
|
masquerade: Option<Masquerade>,
|
||||||
#[validate(length(min = 1, max = 10))]
|
#[validate(length(min = 1, max = 10))]
|
||||||
embeds: Option<Vec<SendableEmbed>>,
|
embeds: Option<Vec<SendableEmbed>>,
|
||||||
@@ -45,7 +33,126 @@ lazy_static! {
|
|||||||
static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/messages", data = "<message>")]
|
#[post("/<target>/messages", data = "<data>")]
|
||||||
pub async fn message_send(user: User, target: Ref, message: Json<Data>) -> Result<Value> {
|
pub async fn message_send(
|
||||||
todo!()
|
db: &Db,
|
||||||
|
user: User,
|
||||||
|
target: Ref,
|
||||||
|
data: Json<Data>,
|
||||||
|
) -> Result<Json<Message>> {
|
||||||
|
let data = data.into_inner();
|
||||||
|
data.validate()
|
||||||
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
|
let channel = target.as_channel(db).await?;
|
||||||
|
let permissions = perms(&user).channel(&channel).calc_channel(db).await;
|
||||||
|
|
||||||
|
if !permissions.get_send_message() {
|
||||||
|
return Err(Error::MissingPermission {
|
||||||
|
permission: ChannelPermission::SendMessage as i32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.content.is_empty()
|
||||||
|
&& (data.attachments.is_none() || data.attachments.as_ref().unwrap().is_empty())
|
||||||
|
{
|
||||||
|
return Err(Error::EmptyMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message_id = Ulid::new().to_string();
|
||||||
|
let mut message = Message {
|
||||||
|
id: message_id.clone(),
|
||||||
|
channel: channel.as_id(),
|
||||||
|
author: user.id,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1. Parse mentions in message.
|
||||||
|
let mut mentions = HashSet::new();
|
||||||
|
for capture in RE_MENTION.captures_iter(&data.content) {
|
||||||
|
if let Some(mention) = capture.get(1) {
|
||||||
|
mentions.insert(mention.as_str().to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Verify permissions for masquerade.
|
||||||
|
if data.masquerade.is_some() && !permissions.get_masquerade() {
|
||||||
|
return Err(Error::MissingPermission {
|
||||||
|
permission: ChannelPermission::Masquerade as i32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 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?;
|
||||||
|
|
||||||
|
replies.insert(message.id);
|
||||||
|
|
||||||
|
if mention {
|
||||||
|
mentions.insert(message.author);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !mentions.is_empty() {
|
||||||
|
message
|
||||||
|
.mentions
|
||||||
|
.replace(mentions.into_iter().collect::<Vec<String>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !replies.is_empty() {
|
||||||
|
message
|
||||||
|
.replies
|
||||||
|
.replace(replies.into_iter().collect::<Vec<String>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Add attachments to message.
|
||||||
|
let mut attachments = vec![];
|
||||||
|
if let Some(ids) = &data.attachments {
|
||||||
|
if !ids.is_empty() && !permissions.get_upload_files() {
|
||||||
|
return Err(Error::MissingPermission {
|
||||||
|
permission: ChannelPermission::UploadFiles as i32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ! 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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. Set content
|
||||||
|
message.content = Content::Text(data.content);
|
||||||
|
|
||||||
|
db.insert_message(&message).await?;
|
||||||
|
Ok(Json(message))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user