chore(monorepo): delta, january, quark
This commit is contained in:
23
crates/delta/src/routes/channels/channel_ack.rs
Normal file
23
crates/delta/src/routes/channels/channel_ack.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result};
|
||||
|
||||
/// # Acknowledge Message
|
||||
///
|
||||
/// Lets the server and all other clients know that we've seen this message id in this channel.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[put("/<target>/ack/<message>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, message: Ref) -> Result<EmptyResponse> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission(db, Permission::ViewChannel)
|
||||
.await?;
|
||||
|
||||
channel
|
||||
.ack(&user.id, &message.id)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
41
crates/delta/src/routes/channels/channel_delete.rs
Normal file
41
crates/delta/src/routes/channels/channel_delete.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
use revolt_quark::{
|
||||
models::{channel::PartialChannel, Channel, User},
|
||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Close Channel
|
||||
///
|
||||
/// Deletes a server channel, leaves a group or closes a group.
|
||||
#[openapi(tag = "Channel Information")]
|
||||
#[delete("/<target>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
|
||||
let mut channel = target.as_channel(db).await?;
|
||||
let mut perms = perms(&user).channel(&channel);
|
||||
perms.throw_permission(db, Permission::ViewChannel).await?;
|
||||
|
||||
match &channel {
|
||||
Channel::SavedMessages { .. } => Err(Error::NoEffect),
|
||||
Channel::DirectMessage { .. } => channel
|
||||
.update(
|
||||
db,
|
||||
PartialChannel {
|
||||
active: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
.map(|_| EmptyResponse),
|
||||
Channel::Group { .. } => channel
|
||||
.remove_user_from_group(db, &user.id, None)
|
||||
.await
|
||||
.map(|_| EmptyResponse),
|
||||
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
|
||||
perms
|
||||
.throw_permission(db, Permission::ManageChannel)
|
||||
.await?;
|
||||
|
||||
channel.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
173
crates/delta/src/routes/channels/channel_edit.rs
Normal file
173
crates/delta/src/routes/channels/channel_edit.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
channel::{Channel, FieldsChannel, PartialChannel},
|
||||
message::SystemMessage,
|
||||
File, User,
|
||||
},
|
||||
perms, Database, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Channel Details
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataEditChannel {
|
||||
/// Channel name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
name: Option<String>,
|
||||
/// Channel description
|
||||
#[validate(length(min = 0, max = 1024))]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
description: Option<String>,
|
||||
/// Icon
|
||||
///
|
||||
/// Provide an Autumn attachment Id.
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
icon: Option<String>,
|
||||
/// Whether this channel is age-restricted
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
nsfw: Option<bool>,
|
||||
#[validate(length(min = 1))]
|
||||
remove: Option<Vec<FieldsChannel>>,
|
||||
}
|
||||
|
||||
/// # Edit Channel
|
||||
///
|
||||
/// Edit a channel object by its id.
|
||||
#[openapi(tag = "Channel Information")]
|
||||
#[patch("/<target>", data = "<data>")]
|
||||
pub async fn req(
|
||||
db: &State<Database>,
|
||||
user: User,
|
||||
target: Ref,
|
||||
data: Json<DataEditChannel>,
|
||||
) -> Result<Json<Channel>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission_and_view_channel(db, Permission::ManageChannel)
|
||||
.await?;
|
||||
|
||||
if data.name.is_none()
|
||||
&& data.description.is_none()
|
||||
&& data.icon.is_none()
|
||||
&& data.nsfw.is_none()
|
||||
&& data.remove.is_none()
|
||||
{
|
||||
return Ok(Json(channel));
|
||||
}
|
||||
|
||||
let mut partial: PartialChannel = Default::default();
|
||||
match &mut channel {
|
||||
Channel::Group {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
..
|
||||
}
|
||||
| Channel::TextChannel {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
..
|
||||
}
|
||||
| Channel::VoiceChannel {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
icon,
|
||||
nsfw,
|
||||
..
|
||||
} => {
|
||||
if let Some(fields) = &data.remove {
|
||||
if fields.contains(&FieldsChannel::Icon) {
|
||||
if let Some(icon) = &icon {
|
||||
db.mark_attachment_as_deleted(&icon.id).await?;
|
||||
}
|
||||
}
|
||||
|
||||
for field in fields {
|
||||
match field {
|
||||
FieldsChannel::Description => {
|
||||
description.take();
|
||||
}
|
||||
FieldsChannel::Icon => {
|
||||
icon.take();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(icon_id) = data.icon {
|
||||
partial.icon = Some(File::use_icon(db, &icon_id, id).await?);
|
||||
*icon = partial.icon.clone();
|
||||
}
|
||||
|
||||
if let Some(new_name) = data.name {
|
||||
*name = new_name.clone();
|
||||
partial.name = Some(new_name);
|
||||
}
|
||||
|
||||
if let Some(new_description) = data.description {
|
||||
partial.description = Some(new_description);
|
||||
*description = partial.description.clone();
|
||||
}
|
||||
|
||||
if let Some(new_nsfw) = data.nsfw {
|
||||
*nsfw = new_nsfw;
|
||||
partial.nsfw = Some(new_nsfw);
|
||||
}
|
||||
|
||||
// Send out mutation system messages.
|
||||
if let Channel::Group { .. } = &channel {
|
||||
if let Some(name) = &partial.name {
|
||||
SystemMessage::ChannelRenamed {
|
||||
name: name.to_string(),
|
||||
by: user.id.clone(),
|
||||
}
|
||||
.into_message(channel.id().to_string())
|
||||
.create(db, &channel, None)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
if partial.description.is_some() {
|
||||
SystemMessage::ChannelDescriptionChanged {
|
||||
by: user.id.clone(),
|
||||
}
|
||||
.into_message(channel.id().to_string())
|
||||
.create(db, &channel, None)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
if partial.icon.is_some() {
|
||||
SystemMessage::ChannelIconChanged { by: user.id }
|
||||
.into_message(channel.id().to_string())
|
||||
.create(db, &channel, None)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
channel
|
||||
.update(db, partial, data.remove.unwrap_or_default())
|
||||
.await?;
|
||||
}
|
||||
_ => return Err(Error::InvalidOperation),
|
||||
};
|
||||
|
||||
Ok(Json(channel))
|
||||
}
|
||||
21
crates/delta/src/routes/channels/channel_fetch.rs
Normal file
21
crates/delta/src/routes/channels/channel_fetch.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
perms, Database, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::{serde::json::Json, State};
|
||||
|
||||
/// # Fetch Channel
|
||||
///
|
||||
/// Fetch channel by its id.
|
||||
#[openapi(tag = "Channel Information")]
|
||||
#[get("/<target>")]
|
||||
pub async fn req(db: &State<Database>, user: User, target: Ref) -> Result<Json<Channel>> {
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission(db, Permission::ViewChannel)
|
||||
.await?;
|
||||
|
||||
Ok(Json(channel))
|
||||
}
|
||||
32
crates/delta/src/routes/channels/group_add_member.rs
Normal file
32
crates/delta/src/routes/channels/group_add_member.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Add Member to Group
|
||||
///
|
||||
/// Adds another user to the group.
|
||||
#[openapi(tag = "Groups")]
|
||||
#[put("/<target>/recipients/<member>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let mut channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission_and_view_channel(db, Permission::InviteOthers)
|
||||
.await?;
|
||||
|
||||
match &channel {
|
||||
Channel::Group { .. } => {
|
||||
let member = member.as_user(db).await?;
|
||||
channel
|
||||
.add_user_to_group(db, &member.id, &user.id)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
84
crates/delta/src/routes/channels/group_create.rs
Normal file
84
crates/delta/src/routes/channels/group_create.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::{collections::HashSet, iter::FromIterator};
|
||||
|
||||
use revolt_quark::{
|
||||
get_relationship,
|
||||
models::{user::RelationshipStatus, Channel, User},
|
||||
variables::delta::MAX_GROUP_SIZE,
|
||||
Db, Error, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Group Data
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataCreateGroup {
|
||||
/// Group name
|
||||
#[validate(length(min = 1, max = 32))]
|
||||
name: String,
|
||||
/// Group description
|
||||
#[validate(length(min = 0, max = 1024))]
|
||||
description: Option<String>,
|
||||
/// Array of user IDs to add to the group
|
||||
///
|
||||
/// Must be friends with these users.
|
||||
#[validate(length(min = 0, max = 49))]
|
||||
users: Vec<String>,
|
||||
/// Whether this group is age-restricted
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
nsfw: Option<bool>,
|
||||
}
|
||||
|
||||
/// # Create Group
|
||||
///
|
||||
/// Create a new group channel.
|
||||
#[openapi(tag = "Groups")]
|
||||
#[post("/create", data = "<info>")]
|
||||
pub async fn req(db: &Db, user: User, info: Json<DataCreateGroup>) -> Result<Json<Channel>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let info = info.into_inner();
|
||||
info.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut set: HashSet<String> = HashSet::from_iter(info.users.into_iter());
|
||||
set.insert(user.id.clone());
|
||||
|
||||
if set.len() > *MAX_GROUP_SIZE {
|
||||
return Err(Error::GroupTooLarge {
|
||||
max: *MAX_GROUP_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
for target in &set {
|
||||
match get_relationship(&user, target) {
|
||||
RelationshipStatus::Friend | RelationshipStatus::User => {}
|
||||
_ => {
|
||||
return Err(Error::NotFriends);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let group = Channel::Group {
|
||||
id: Ulid::new().to_string(),
|
||||
|
||||
name: info.name,
|
||||
owner: user.id,
|
||||
description: info.description,
|
||||
recipients: set.into_iter().collect::<Vec<String>>(),
|
||||
|
||||
icon: None,
|
||||
last_message_id: None,
|
||||
|
||||
permissions: None,
|
||||
|
||||
nsfw: info.nsfw.unwrap_or(false),
|
||||
};
|
||||
|
||||
group.create(db).await?;
|
||||
Ok(Json(group))
|
||||
}
|
||||
42
crates/delta/src/routes/channels/group_remove_member.rs
Normal file
42
crates/delta/src/routes/channels/group_remove_member.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Remove Member from Group
|
||||
///
|
||||
/// Removes a user from the group.
|
||||
#[openapi(tag = "Groups")]
|
||||
#[delete("/<target>/recipients/<member>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyResponse> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
|
||||
match &channel {
|
||||
Channel::Group {
|
||||
owner, recipients, ..
|
||||
} => {
|
||||
if &user.id != owner {
|
||||
return Error::from_permission(Permission::ManageChannel);
|
||||
}
|
||||
|
||||
let member = member.as_user(db).await?;
|
||||
if user.id == member.id {
|
||||
return Err(Error::CannotRemoveYourself);
|
||||
}
|
||||
|
||||
if !recipients.iter().any(|x| *x == member.id) {
|
||||
return Err(Error::NotInGroup);
|
||||
}
|
||||
|
||||
channel
|
||||
.remove_user_from_group(db, &member.id, Some(&user.id))
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
_ => Err(Error::InvalidOperation),
|
||||
}
|
||||
}
|
||||
27
crates/delta/src/routes/channels/invite_create.rs
Normal file
27
crates/delta/src/routes/channels/invite_create.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use revolt_quark::{
|
||||
models::{Invite, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Create Invite
|
||||
///
|
||||
/// Creates an invite to this channel.
|
||||
///
|
||||
/// Channel must be a `TextChannel`.
|
||||
#[openapi(tag = "Channel Invites")]
|
||||
#[post("/<target>/invites")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Invite>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission_and_view_channel(db, Permission::InviteOthers)
|
||||
.await?;
|
||||
|
||||
Invite::create(db, &user, &channel).await.map(Json)
|
||||
}
|
||||
35
crates/delta/src/routes/channels/members_fetch.rs
Normal file
35
crates/delta/src/routes/channels/members_fetch.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Group Members
|
||||
///
|
||||
/// Retrieves all users who are part of this group.
|
||||
#[openapi(tag = "Groups")]
|
||||
#[get("/<target>/members")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<Vec<User>>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission(db, Permission::ViewChannel)
|
||||
.await?;
|
||||
|
||||
if let Channel::Group { recipients, .. } = channel {
|
||||
Ok(Json(
|
||||
db.fetch_users(&recipients)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|x| x.with_relationship(&user))
|
||||
.collect::<Vec<User>>(),
|
||||
))
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
59
crates/delta/src/routes/channels/message_bulk_delete.rs
Normal file
59
crates/delta/src/routes/channels/message_bulk_delete.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use chrono::Utc;
|
||||
use revolt_quark::{
|
||||
models::{Message, User},
|
||||
perms, Db, EmptyResponse, Error, Permission, Ref, Result,
|
||||
};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
use validator::Validate;
|
||||
|
||||
/// # Search Parameters
|
||||
#[derive(Validate, Deserialize, JsonSchema)]
|
||||
pub struct OptionsBulkDelete {
|
||||
/// Message IDs
|
||||
#[validate(length(min = 1, max = 100))]
|
||||
ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// # Bulk Delete Messages
|
||||
///
|
||||
/// Delete multiple messages you've sent or one you have permission to delete.
|
||||
///
|
||||
/// This will always require `ManageMessages` permission regardless of whether you own the message or not.
|
||||
///
|
||||
/// Messages must have been sent within the past 1 week.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[delete("/<target>/messages/bulk", data = "<options>", rank = 1)]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
options: Json<OptionsBulkDelete>,
|
||||
) -> Result<EmptyResponse> {
|
||||
let options = options.into_inner();
|
||||
options
|
||||
.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
for id in &options.ids {
|
||||
if ulid::Ulid::from_string(id)
|
||||
.map_err(|_| Error::InvalidOperation)?
|
||||
.datetime()
|
||||
.signed_duration_since(Utc::now())
|
||||
.num_days()
|
||||
.abs()
|
||||
> 7
|
||||
{
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
}
|
||||
|
||||
perms(&user)
|
||||
.channel(&target.as_channel(db).await?)
|
||||
.throw_permission(db, Permission::ManageMessages)
|
||||
.await?;
|
||||
|
||||
Message::bulk_delete(db, &target.id, options.ids)
|
||||
.await
|
||||
.map(|_| EmptyResponse)
|
||||
}
|
||||
22
crates/delta/src/routes/channels/message_delete.rs
Normal file
22
crates/delta/src/routes/channels/message_delete.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use revolt_quark::{models::User, perms, Db, EmptyResponse, Error, Permission, Ref, Result};
|
||||
|
||||
/// # Delete Message
|
||||
///
|
||||
/// Delete a message you've sent or one you have permission to delete.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[delete("/<target>/messages/<msg>", rank = 2)]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<EmptyResponse> {
|
||||
let message = msg.as_message(db).await?;
|
||||
if message.channel != target.id {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
if message.author != user.id {
|
||||
perms(&user)
|
||||
.channel(&target.as_channel(db).await?)
|
||||
.throw_permission(db, Permission::ManageMessages)
|
||||
.await?;
|
||||
}
|
||||
|
||||
message.delete(db).await.map(|_| EmptyResponse)
|
||||
}
|
||||
93
crates/delta/src/routes/channels/message_edit.rs
Normal file
93
crates/delta/src/routes/channels/message_edit.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use revolt_quark::{
|
||||
models::message::{PartialMessage, SendableEmbed},
|
||||
models::{Message, User},
|
||||
types::january::Embed,
|
||||
Db, Error, Ref, Result, Timestamp,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Message Details
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataEditMessage {
|
||||
/// New message content
|
||||
#[validate(length(min = 1, max = 2000))]
|
||||
content: Option<String>,
|
||||
/// Embeds to include in the message
|
||||
#[validate(length(min = 0, max = 10))]
|
||||
embeds: Option<Vec<SendableEmbed>>,
|
||||
}
|
||||
|
||||
/// # Edit Message
|
||||
///
|
||||
/// Edits a message that you've previously sent.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[patch("/<target>/messages/<msg>", data = "<edit>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: String,
|
||||
msg: Ref,
|
||||
edit: Json<DataEditMessage>,
|
||||
) -> Result<Json<Message>> {
|
||||
let edit = edit.into_inner();
|
||||
edit.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let mut message = msg.as_message(db).await?;
|
||||
if message.channel != target {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
if message.author != user.id {
|
||||
return Err(Error::CannotEditMessage);
|
||||
}
|
||||
|
||||
message.edited = Some(Timestamp::now_utc());
|
||||
let mut partial = PartialMessage {
|
||||
edited: message.edited,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 1. Handle content update
|
||||
if let Some(content) = &edit.content {
|
||||
partial.content = Some(content.clone());
|
||||
}
|
||||
|
||||
// 2. Clear any auto generated embeds
|
||||
let mut new_embeds: Vec<Embed> = vec![];
|
||||
if let Some(embeds) = &message.embeds {
|
||||
for embed in embeds {
|
||||
if let Embed::Text(embed) = embed {
|
||||
new_embeds.push(Embed::Text(embed.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Replace if we are given new embeds
|
||||
if let Some(embeds) = edit.embeds {
|
||||
new_embeds.clear();
|
||||
|
||||
for embed in embeds {
|
||||
new_embeds.push(embed.clone().into_embed(db, message.id.clone()).await?);
|
||||
}
|
||||
}
|
||||
|
||||
partial.embeds = Some(new_embeds);
|
||||
|
||||
message.update(db, partial).await?;
|
||||
|
||||
// Queue up a task for processing embeds
|
||||
if let Some(content) = edit.content {
|
||||
revolt_quark::tasks::process_embeds::queue(
|
||||
message.channel.to_string(),
|
||||
message.id.to_string(),
|
||||
content,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(Json(message))
|
||||
}
|
||||
26
crates/delta/src/routes/channels/message_fetch.rs
Normal file
26
crates/delta/src/routes/channels/message_fetch.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use revolt_quark::{
|
||||
models::{Message, User},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
/// # Fetch Message
|
||||
///
|
||||
/// Retrieves a message by its id.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[get("/<target>/messages/<msg>")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref, msg: Ref) -> Result<Json<Message>> {
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission(db, Permission::ViewChannel)
|
||||
.await?;
|
||||
|
||||
let message = msg.as_message(db).await?;
|
||||
if message.channel != channel.as_id() {
|
||||
return Err(Error::NotFound);
|
||||
}
|
||||
|
||||
Ok(Json(message))
|
||||
}
|
||||
81
crates/delta/src/routes/channels/message_query.rs
Normal file
81
crates/delta/src/routes/channels/message_query.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
message::{BulkMessageResponse, MessageSort},
|
||||
User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Query Parameters
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
|
||||
pub struct OptionsQueryMessages {
|
||||
/// Maximum number of messages to fetch
|
||||
///
|
||||
/// For fetching nearby messages, this is \`(limit + 1)\`.
|
||||
#[validate(range(min = 1, max = 100))]
|
||||
limit: Option<i64>,
|
||||
/// Message id before which messages should be fetched
|
||||
#[validate(length(min = 26, max = 26))]
|
||||
before: Option<String>,
|
||||
/// Message id after which messages should be fetched
|
||||
#[validate(length(min = 26, max = 26))]
|
||||
after: Option<String>,
|
||||
/// Message sort direction
|
||||
sort: Option<MessageSort>,
|
||||
/// Message id to search around
|
||||
///
|
||||
/// Specifying 'nearby' ignores 'before', 'after' and 'sort'.
|
||||
/// It will also take half of limit rounded as the limits to each side.
|
||||
/// It also fetches the message ID specified.
|
||||
#[validate(length(min = 26, max = 26))]
|
||||
nearby: Option<String>,
|
||||
/// Whether to include user (and member, if server channel) objects
|
||||
include_users: Option<bool>,
|
||||
}
|
||||
|
||||
/// # Fetch Messages
|
||||
///
|
||||
/// Fetch multiple messages.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[get("/<target>/messages?<options..>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
options: OptionsQueryMessages,
|
||||
) -> Result<Json<BulkMessageResponse>> {
|
||||
options
|
||||
.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
if let Some(MessageSort::Relevance) = options.sort {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission_and_view_channel(db, Permission::ReadMessageHistory)
|
||||
.await?;
|
||||
|
||||
let OptionsQueryMessages {
|
||||
limit,
|
||||
before,
|
||||
after,
|
||||
sort,
|
||||
nearby,
|
||||
include_users,
|
||||
..
|
||||
} = options;
|
||||
|
||||
let messages = db
|
||||
.fetch_messages(channel.id(), limit, before, after, sort, nearby)
|
||||
.await?;
|
||||
|
||||
BulkMessageResponse::transform(db, &channel, messages, include_users)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
26
crates/delta/src/routes/channels/message_query_stale.rs
Normal file
26
crates/delta/src/routes/channels/message_query_stale.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use revolt_quark::{models::User, Ref, Result};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Query Parameters
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct OptionsQueryStale {
|
||||
/// Array of message IDs
|
||||
#[validate(length(min = 0, max = 150))]
|
||||
ids: Vec<String>,
|
||||
}
|
||||
|
||||
/// # Poll Message Changes
|
||||
///
|
||||
/// This route returns any changed message objects and tells you if any have been deleted.
|
||||
///
|
||||
/// Don't actually poll this route, instead use this to update your local database.
|
||||
///
|
||||
/// **DEPRECATED**
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[post("/<_target>/messages/stale", data = "<_data>")]
|
||||
pub async fn req(_user: User, _target: Ref, _data: Json<OptionsQueryStale>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
82
crates/delta/src/routes/channels/message_search.rs
Normal file
82
crates/delta/src/routes/channels/message_search.rs
Normal file
@@ -0,0 +1,82 @@
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
message::{BulkMessageResponse, MessageSort},
|
||||
User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
|
||||
/// # Search Parameters
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct OptionsMessageSearch {
|
||||
/// Full-text search query
|
||||
///
|
||||
/// See [MongoDB documentation](https://docs.mongodb.com/manual/text-search/#-text-operator) for more information.
|
||||
#[validate(length(min = 1, max = 64))]
|
||||
query: String,
|
||||
|
||||
/// Maximum number of messages to fetch
|
||||
#[validate(range(min = 1, max = 100))]
|
||||
limit: Option<i64>,
|
||||
/// Message id before which messages should be fetched
|
||||
#[validate(length(min = 26, max = 26))]
|
||||
before: Option<String>,
|
||||
/// Message id after which messages should be fetched
|
||||
#[validate(length(min = 26, max = 26))]
|
||||
after: Option<String>,
|
||||
/// Message sort direction
|
||||
///
|
||||
/// By default, it will be sorted by relevance.
|
||||
#[serde(default = "MessageSort::default")]
|
||||
sort: MessageSort,
|
||||
/// Whether to include user (and member, if server channel) objects
|
||||
include_users: Option<bool>,
|
||||
}
|
||||
|
||||
/// # Search for Messages
|
||||
///
|
||||
/// This route searches for messages within the given parameters.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[post("/<target>/search", data = "<options>")]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
options: Json<OptionsMessageSearch>,
|
||||
) -> Result<Json<BulkMessageResponse>> {
|
||||
if user.bot.is_some() {
|
||||
return Err(Error::IsBot);
|
||||
}
|
||||
|
||||
let options = options.into_inner();
|
||||
options
|
||||
.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
perms(&user)
|
||||
.channel(&channel)
|
||||
.throw_permission_and_view_channel(db, Permission::ReadMessageHistory)
|
||||
.await?;
|
||||
|
||||
let OptionsMessageSearch {
|
||||
query,
|
||||
limit,
|
||||
before,
|
||||
after,
|
||||
sort,
|
||||
include_users,
|
||||
} = options;
|
||||
|
||||
let messages = db
|
||||
.search_messages(channel.id(), &query, limit, before, after, sort)
|
||||
.await?;
|
||||
|
||||
BulkMessageResponse::transform(db, &channel, messages, include_users)
|
||||
.await
|
||||
.map(Json)
|
||||
}
|
||||
194
crates/delta/src/routes/channels/message_send.rs
Normal file
194
crates/delta/src/routes/channels/message_send.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{
|
||||
message::{Masquerade, Reply, SendableEmbed},
|
||||
Message, User,
|
||||
},
|
||||
perms, Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ulid::Ulid;
|
||||
use validator::Validate;
|
||||
|
||||
use crate::util::idempotency::IdempotencyKey;
|
||||
|
||||
#[derive(Validate, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DataMessageSend {
|
||||
/// Unique token to prevent duplicate message sending
|
||||
///
|
||||
/// **This is deprecated and replaced by `Idempotency-Key`!**
|
||||
nonce: Option<String>,
|
||||
|
||||
/// Message content to send
|
||||
#[validate(length(min = 0, max = 2000))]
|
||||
content: Option<String>,
|
||||
/// Attachments to include in message
|
||||
#[validate(length(min = 1, max = 128))]
|
||||
attachments: Option<Vec<String>>,
|
||||
/// Messages to reply to
|
||||
replies: Option<Vec<Reply>>,
|
||||
/// Embeds to include in message
|
||||
#[validate(length(min = 1, max = 10))]
|
||||
embeds: Option<Vec<SendableEmbed>>,
|
||||
/// Masquerade to apply to this message
|
||||
#[validate]
|
||||
masquerade: Option<Masquerade>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// ignoring I L O and U is intentional
|
||||
static ref RE_MENTION: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
||||
}
|
||||
|
||||
/// # Send Message
|
||||
///
|
||||
/// Sends a message to the given channel.
|
||||
#[openapi(tag = "Messaging")]
|
||||
#[post("/<target>/messages", data = "<data>")]
|
||||
pub async fn message_send(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
data: Json<DataMessageSend>,
|
||||
mut idempotency: IdempotencyKey,
|
||||
) -> Result<Json<Message>> {
|
||||
let data = data.into_inner();
|
||||
data.validate()
|
||||
.map_err(|error| Error::FailedValidation { error })?;
|
||||
|
||||
idempotency.consume_nonce(data.nonce).await?;
|
||||
|
||||
let channel = target.as_channel(db).await?;
|
||||
let mut permissions = perms(&user).channel(&channel);
|
||||
permissions
|
||||
.throw_permission_and_view_channel(db, Permission::SendMessage)
|
||||
.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(),
|
||||
author: user.id.clone(),
|
||||
masquerade: data.masquerade,
|
||||
..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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Verify permissions for masquerade.
|
||||
if message.masquerade.is_some() {
|
||||
permissions
|
||||
.throw_permission(db, Permission::Masquerade)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 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()
|
||||
.filter(|id| !user.has_blocked(id))
|
||||
.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
|
||||
.throw_permission(db, Permission::UploadFiles)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// ! 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 = data.content;
|
||||
|
||||
// 7. Pass-through nonce value for clients
|
||||
message.nonce = Some(idempotency.into_key());
|
||||
|
||||
message.create(db, &channel, Some(&user)).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))
|
||||
}
|
||||
48
crates/delta/src/routes/channels/mod.rs
Normal file
48
crates/delta/src/routes/channels/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use rocket::Route;
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
|
||||
mod channel_ack;
|
||||
mod channel_delete;
|
||||
mod channel_edit;
|
||||
mod channel_fetch;
|
||||
mod group_add_member;
|
||||
mod group_create;
|
||||
mod group_remove_member;
|
||||
mod invite_create;
|
||||
mod members_fetch;
|
||||
mod message_bulk_delete;
|
||||
mod message_delete;
|
||||
mod message_edit;
|
||||
mod message_fetch;
|
||||
mod message_query;
|
||||
mod message_query_stale;
|
||||
mod message_search;
|
||||
mod message_send;
|
||||
mod permissions_set;
|
||||
mod permissions_set_default;
|
||||
mod voice_join;
|
||||
|
||||
pub fn routes() -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
channel_ack::req,
|
||||
channel_fetch::req,
|
||||
members_fetch::req,
|
||||
channel_delete::req,
|
||||
channel_edit::req,
|
||||
invite_create::req,
|
||||
message_send::message_send,
|
||||
message_query::req,
|
||||
message_search::req,
|
||||
message_query_stale::req,
|
||||
message_fetch::req,
|
||||
message_edit::req,
|
||||
message_bulk_delete::req,
|
||||
message_delete::req,
|
||||
group_create::req,
|
||||
group_add_member::req,
|
||||
group_remove_member::req,
|
||||
voice_join::req,
|
||||
permissions_set::req,
|
||||
permissions_set_default::req,
|
||||
]
|
||||
}
|
||||
59
crates/delta/src/routes/channels/permissions_set.rs
Normal file
59
crates/delta/src/routes/channels/permissions_set.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
perms, Db, Error, Override, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Permission Value
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
pub struct Data {
|
||||
/// Allow / deny values to set for this role
|
||||
permissions: Override,
|
||||
}
|
||||
|
||||
/// # Set Role Permission
|
||||
///
|
||||
/// Sets permissions for the specified role in this channel.
|
||||
///
|
||||
/// Channel must be a `TextChannel` or `VoiceChannel`.
|
||||
#[openapi(tag = "Channel Permissions")]
|
||||
#[put("/<target>/permissions/<role_id>", data = "<data>", rank = 2)]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
role_id: String,
|
||||
data: Json<Data>,
|
||||
) -> Result<Json<Channel>> {
|
||||
let mut channel = target.as_channel(db).await?;
|
||||
let mut permissions = perms(&user).channel(&channel);
|
||||
|
||||
permissions
|
||||
.throw_permission_and_view_channel(db, Permission::ManagePermissions)
|
||||
.await?;
|
||||
|
||||
if let Some(server) = permissions.server.get() {
|
||||
if let Some(role) = server.roles.get(&role_id) {
|
||||
if role.rank <= permissions.get_member_rank().unwrap_or(i64::MIN) {
|
||||
return Err(Error::NotElevated);
|
||||
}
|
||||
|
||||
let current_value: Override = role.permissions.into();
|
||||
permissions
|
||||
.throw_permission_override(db, current_value, data.permissions)
|
||||
.await?;
|
||||
|
||||
channel
|
||||
.set_role_permission(db, &role_id, data.permissions.into())
|
||||
.await?;
|
||||
|
||||
Ok(Json(channel))
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
} else {
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
}
|
||||
95
crates/delta/src/routes/channels/permissions_set_default.rs
Normal file
95
crates/delta/src/routes/channels/permissions_set_default.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use rocket::serde::json::Json;
|
||||
use serde::Deserialize;
|
||||
|
||||
use revolt_quark::{
|
||||
models::{channel::PartialChannel, Channel, User},
|
||||
perms, Db, Error, Override, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
/// # Permission Value
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
#[serde(untagged)]
|
||||
pub enum DataDefaultChannelPermissions {
|
||||
Value {
|
||||
/// Permission values to set for members in a `Group`
|
||||
permissions: u64,
|
||||
},
|
||||
Field {
|
||||
/// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel`
|
||||
permissions: Override,
|
||||
},
|
||||
}
|
||||
|
||||
/// # Set Default Permission
|
||||
///
|
||||
/// Sets permissions for the default role in this channel.
|
||||
///
|
||||
/// Channel must be a `Group`, `TextChannel` or `VoiceChannel`.
|
||||
#[openapi(tag = "Channel Permissions")]
|
||||
#[put("/<target>/permissions/default", data = "<data>", rank = 1)]
|
||||
pub async fn req(
|
||||
db: &Db,
|
||||
user: User,
|
||||
target: Ref,
|
||||
data: Json<DataDefaultChannelPermissions>,
|
||||
) -> Result<Json<Channel>> {
|
||||
let data = data.into_inner();
|
||||
|
||||
let mut channel = target.as_channel(db).await?;
|
||||
let mut perm = perms(&user).channel(&channel);
|
||||
|
||||
perm.throw_permission_and_view_channel(db, Permission::ManagePermissions)
|
||||
.await?;
|
||||
|
||||
match &channel {
|
||||
Channel::Group { .. } => {
|
||||
if let DataDefaultChannelPermissions::Value { permissions } = data {
|
||||
channel
|
||||
.update(
|
||||
db,
|
||||
PartialChannel {
|
||||
permissions: Some(permissions as i64),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
}
|
||||
Channel::TextChannel {
|
||||
default_permissions,
|
||||
..
|
||||
}
|
||||
| Channel::VoiceChannel {
|
||||
default_permissions,
|
||||
..
|
||||
} => {
|
||||
if let DataDefaultChannelPermissions::Field { permissions } = data {
|
||||
perm.throw_permission_override(
|
||||
db,
|
||||
default_permissions.map(|x| x.into()),
|
||||
permissions,
|
||||
)
|
||||
.await?;
|
||||
|
||||
channel
|
||||
.update(
|
||||
db,
|
||||
PartialChannel {
|
||||
default_permissions: Some(permissions.into()),
|
||||
..Default::default()
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
return Err(Error::InvalidOperation);
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::InvalidOperation),
|
||||
}
|
||||
|
||||
Ok(Json(channel))
|
||||
}
|
||||
100
crates/delta/src/routes/channels/voice_join.rs
Normal file
100
crates/delta/src/routes/channels/voice_join.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
use revolt_quark::{
|
||||
models::{Channel, User},
|
||||
perms,
|
||||
variables::delta::{USE_VOSO, VOSO_MANAGE_TOKEN, VOSO_URL},
|
||||
Db, Error, Permission, Ref, Result,
|
||||
};
|
||||
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// # Voice Server Token Response
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CreateVoiceUserResponse {
|
||||
/// Token for authenticating with the voice server
|
||||
token: String,
|
||||
}
|
||||
|
||||
/// # Join Call
|
||||
///
|
||||
/// Asks the voice server for a token to join the call.
|
||||
#[openapi(tag = "Voice")]
|
||||
#[post("/<target>/join_call")]
|
||||
pub async fn req(db: &Db, user: User, target: Ref) -> Result<Json<CreateVoiceUserResponse>> {
|
||||
let channel = target.as_channel(db).await?;
|
||||
let mut permissions = perms(&user).channel(&channel);
|
||||
|
||||
permissions
|
||||
.throw_permission_and_view_channel(db, Permission::Connect)
|
||||
.await?;
|
||||
|
||||
if !*USE_VOSO {
|
||||
return Err(Error::VosoUnavailable);
|
||||
}
|
||||
|
||||
match channel {
|
||||
Channel::SavedMessages { .. } | Channel::TextChannel { .. } => {
|
||||
return Err(Error::CannotJoinCall)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// To join a call:
|
||||
// - Check if the room exists.
|
||||
// - If not, create it.
|
||||
let client = reqwest::Client::new();
|
||||
let result = client
|
||||
.get(&format!("{}/room/{}", *VOSO_URL, channel.id()))
|
||||
.header(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
VOSO_MANAGE_TOKEN.to_string(),
|
||||
)
|
||||
.send()
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Err(_) => return Err(Error::VosoUnavailable),
|
||||
Ok(result) => match result.status() {
|
||||
reqwest::StatusCode::OK => (),
|
||||
reqwest::StatusCode::NOT_FOUND => {
|
||||
if (client
|
||||
.post(&format!("{}/room/{}", *VOSO_URL, channel.id()))
|
||||
.header(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
VOSO_MANAGE_TOKEN.to_string(),
|
||||
)
|
||||
.send()
|
||||
.await)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::VosoUnavailable);
|
||||
}
|
||||
}
|
||||
_ => return Err(Error::VosoUnavailable),
|
||||
},
|
||||
}
|
||||
|
||||
// Then create a user for the room.
|
||||
if let Ok(response) = client
|
||||
.post(&format!(
|
||||
"{}/room/{}/user/{}",
|
||||
*VOSO_URL,
|
||||
channel.id(),
|
||||
user.id
|
||||
))
|
||||
.header(
|
||||
reqwest::header::AUTHORIZATION,
|
||||
VOSO_MANAGE_TOKEN.to_string(),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|_| Error::InvalidOperation)
|
||||
.map(Json)
|
||||
} else {
|
||||
Err(Error::VosoUnavailable)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user