Compare commits
16
Commits
20240621-1
...
20240626-1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9cfc9d0e1 | ||
|
|
64a07d09f4 | ||
|
|
00e881799f | ||
|
|
fa598dd6f8 | ||
|
|
ac4e2cb10b | ||
|
|
ec578ab0ef | ||
|
|
ea6ba59841 | ||
|
|
f903f716e3 | ||
|
|
c5f4b94aa5 | ||
|
|
1d5dae4751 | ||
|
|
ac20b6bc99 | ||
|
|
6ec8007e4e | ||
|
|
e5eea267cf | ||
|
|
de5add09d0 | ||
|
|
1ec8f46c1d | ||
|
|
80666848cc |
No files matched your search
@@ -2,8 +2,8 @@ name: Docker Test & Publish
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
# branches:
|
||||||
- "main"
|
# - "main"
|
||||||
tags:
|
tags:
|
||||||
- "*"
|
- "*"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
|
|||||||
Generated
+9
-8
@@ -3452,7 +3452,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel 2.3.1",
|
"async-channel 2.3.1",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -3482,7 +3482,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-config"
|
name = "revolt-config"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"cached",
|
"cached",
|
||||||
@@ -3498,7 +3498,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-database"
|
name = "revolt-database"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-lock",
|
"async-lock",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@@ -3544,7 +3544,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-channel 1.6.1",
|
"async-channel 1.6.1",
|
||||||
"async-std",
|
"async-std",
|
||||||
@@ -3572,6 +3572,7 @@ dependencies = [
|
|||||||
"revolt-database",
|
"revolt-database",
|
||||||
"revolt-models",
|
"revolt-models",
|
||||||
"revolt-permissions",
|
"revolt-permissions",
|
||||||
|
"revolt-presence",
|
||||||
"revolt-result",
|
"revolt-result",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
"rocket",
|
"rocket",
|
||||||
@@ -3590,7 +3591,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"iso8601-timestamp 0.2.11",
|
"iso8601-timestamp 0.2.11",
|
||||||
@@ -3607,7 +3608,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-permissions"
|
name = "revolt-permissions"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -3622,7 +3623,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-presence"
|
name = "revolt-presence"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"log",
|
"log",
|
||||||
@@ -3633,7 +3634,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "revolt-result"
|
name = "revolt-result"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"revolt_okapi",
|
"revolt_okapi",
|
||||||
"revolt_rocket_okapi",
|
"revolt_rocket_okapi",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ Rust 1.70 or higher.
|
|||||||
|
|
||||||
## Development Guide
|
## Development Guide
|
||||||
|
|
||||||
|
Before contributing, make yourself familiar with [our contribution guidelines](https://developers.revolt.chat/contrib.html) and the [technical documentation for this project](https://revoltchat.github.io/backend/).
|
||||||
|
|
||||||
Before getting started, you'll want to install:
|
Before getting started, you'll want to install:
|
||||||
|
|
||||||
- Rust toolchain (rustup recommended)
|
- Rust toolchain (rustup recommended)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-bonfire"
|
name = "revolt-bonfire"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -41,7 +41,7 @@ revolt-result = { path = "../core/result" }
|
|||||||
revolt-models = { path = "../core/models" }
|
revolt-models = { path = "../core/models" }
|
||||||
revolt-config = { path = "../core/config" }
|
revolt-config = { path = "../core/config" }
|
||||||
revolt-database = { path = "../core/database" }
|
revolt-database = { path = "../core/database" }
|
||||||
revolt-permissions = { version = "0.7.8", path = "../core/permissions" }
|
revolt-permissions = { version = "0.7.12", path = "../core/permissions" }
|
||||||
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
|
||||||
|
|
||||||
# redis
|
# redis
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ impl State {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Make sure we see our own user correctly.
|
// Make sure we see our own user correctly.
|
||||||
users.push(user.into_self().await);
|
users.push(user.into_self(true).await);
|
||||||
|
|
||||||
// Set subscription state internally.
|
// Set subscription state internally.
|
||||||
self.reset_state().await;
|
self.reset_state().await;
|
||||||
@@ -540,6 +540,20 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EventV1::Message(message) => {
|
||||||
|
// Since Message events are fanned out to many clients,
|
||||||
|
// we must reconstruct the relationship value at this end.
|
||||||
|
if let Some(user) = &mut message.user {
|
||||||
|
user.relationship = self
|
||||||
|
.cache
|
||||||
|
.users
|
||||||
|
.get(&self.cache.user_id)
|
||||||
|
.expect("missing self?")
|
||||||
|
.relationship_with(&message.author)
|
||||||
|
.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-config"
|
name = "revolt-config"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Paul Makles <me@insrt.uk>"]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
|
|||||||
@@ -46,19 +46,40 @@ webhooks_enabled = false
|
|||||||
|
|
||||||
[features.limits]
|
[features.limits]
|
||||||
|
|
||||||
[features.limits.default]
|
[features.limits.global]
|
||||||
group_size = 100
|
group_size = 100
|
||||||
bots = 5
|
|
||||||
message_length = 2000
|
|
||||||
message_embeds = 5
|
message_embeds = 5
|
||||||
message_replies = 5
|
message_replies = 5
|
||||||
message_attachments = 5
|
|
||||||
message_reactions = 20
|
message_reactions = 20
|
||||||
servers = 100
|
|
||||||
server_emoji = 100
|
server_emoji = 100
|
||||||
server_roles = 200
|
server_roles = 200
|
||||||
server_channels = 200
|
server_channels = 200
|
||||||
|
|
||||||
|
new_user_days = 3
|
||||||
|
|
||||||
|
[features.limits.new_user]
|
||||||
|
outgoing_friend_requests = 5
|
||||||
|
|
||||||
|
bots = 2
|
||||||
|
message_length = 2000
|
||||||
|
message_attachments = 5
|
||||||
|
servers = 100
|
||||||
|
|
||||||
|
attachment_size = 20000000
|
||||||
|
avatar_size = 4000000
|
||||||
|
background_size = 6000000
|
||||||
|
icon_size = 2500000
|
||||||
|
banner_size = 6000000
|
||||||
|
emoji_size = 500000
|
||||||
|
|
||||||
|
[features.limits.default]
|
||||||
|
outgoing_friend_requests = 10
|
||||||
|
|
||||||
|
bots = 5
|
||||||
|
message_length = 2000
|
||||||
|
message_attachments = 5
|
||||||
|
servers = 100
|
||||||
|
|
||||||
attachment_size = 20000000
|
attachment_size = 20000000
|
||||||
avatar_size = 4000000
|
avatar_size = 4000000
|
||||||
background_size = 6000000
|
background_size = 6000000
|
||||||
|
|||||||
@@ -105,19 +105,27 @@ pub struct Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct FeaturesLimits {
|
pub struct GlobalLimits {
|
||||||
pub group_size: usize,
|
pub group_size: usize,
|
||||||
pub bots: usize,
|
|
||||||
pub message_length: usize,
|
|
||||||
pub message_replies: usize,
|
|
||||||
pub message_attachments: usize,
|
|
||||||
pub message_embeds: usize,
|
pub message_embeds: usize,
|
||||||
|
pub message_replies: usize,
|
||||||
pub message_reactions: usize,
|
pub message_reactions: usize,
|
||||||
pub servers: usize,
|
|
||||||
pub server_emoji: usize,
|
pub server_emoji: usize,
|
||||||
pub server_roles: usize,
|
pub server_roles: usize,
|
||||||
pub server_channels: usize,
|
pub server_channels: usize,
|
||||||
|
|
||||||
|
pub new_user_days: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
|
pub struct FeaturesLimits {
|
||||||
|
pub outgoing_friend_requests: usize,
|
||||||
|
|
||||||
|
pub bots: usize,
|
||||||
|
pub message_length: usize,
|
||||||
|
pub message_attachments: usize,
|
||||||
|
pub servers: usize,
|
||||||
|
|
||||||
pub attachment_size: usize,
|
pub attachment_size: usize,
|
||||||
pub avatar_size: usize,
|
pub avatar_size: usize,
|
||||||
pub background_size: usize,
|
pub background_size: usize,
|
||||||
@@ -128,6 +136,9 @@ pub struct FeaturesLimits {
|
|||||||
|
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct FeaturesLimitsCollection {
|
pub struct FeaturesLimitsCollection {
|
||||||
|
pub global: GlobalLimits,
|
||||||
|
|
||||||
|
pub new_user: FeaturesLimits,
|
||||||
pub default: FeaturesLimits,
|
pub default: FeaturesLimits,
|
||||||
|
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-database"
|
name = "revolt-database"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <me@insrt.uk>"]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
@@ -23,13 +23,13 @@ default = ["mongodb", "async-std-runtime", "tasks"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-config = { version = "0.7.8", path = "../config" }
|
revolt-config = { version = "0.7.12", path = "../config" }
|
||||||
revolt-result = { version = "0.7.8", path = "../result" }
|
revolt-result = { version = "0.7.12", path = "../result" }
|
||||||
revolt-models = { version = "0.7.8", path = "../models", features = [
|
revolt-models = { version = "0.7.12", path = "../models", features = [
|
||||||
"validator",
|
"validator",
|
||||||
] }
|
] }
|
||||||
revolt-presence = { version = "0.7.8", path = "../presence" }
|
revolt-presence = { version = "0.7.12", path = "../presence" }
|
||||||
revolt-permissions = { version = "0.7.8", path = "../permissions", features = [
|
revolt-permissions = { version = "0.7.12", path = "../permissions", features = [
|
||||||
"serde",
|
"serde",
|
||||||
"bson",
|
"bson",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ impl Bot {
|
|||||||
return Err(create_error!(IsBot));
|
return Err(create_error!(IsBot));
|
||||||
}
|
}
|
||||||
|
|
||||||
let config = config().await;
|
if db.get_number_of_bots_by_user(&owner.id).await? >= owner.limits().await.bots {
|
||||||
if db.get_number_of_bots_by_user(&owner.id).await? >= config.features.limits.default.bots {
|
|
||||||
return Err(create_error!(ReachedMaximumBots));
|
return Err(create_error!(ReachedMaximumBots));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -201,9 +201,9 @@ impl Channel {
|
|||||||
update_server: bool,
|
update_server: bool,
|
||||||
) -> Result<Channel> {
|
) -> Result<Channel> {
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
if server.channels.len() > config.features.limits.default.server_channels {
|
if server.channels.len() > config.features.limits.global.server_channels {
|
||||||
return Err(create_error!(TooManyChannels {
|
return Err(create_error!(TooManyChannels {
|
||||||
max: config.features.limits.default.server_channels,
|
max: config.features.limits.global.server_channels,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -263,9 +263,9 @@ impl Channel {
|
|||||||
data.users.insert(owner_id.to_string());
|
data.users.insert(owner_id.to_string());
|
||||||
|
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
if data.users.len() > config.features.limits.default.group_size {
|
if data.users.len() > config.features.limits.global.group_size {
|
||||||
return Err(create_error!(GroupTooLarge {
|
return Err(create_error!(GroupTooLarge {
|
||||||
max: config.features.limits.default.group_size,
|
max: config.features.limits.global.group_size,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,9 +346,9 @@ impl Channel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
if recipients.len() >= config.features.limits.default.group_size {
|
if recipients.len() >= config.features.limits.global.group_size {
|
||||||
return Err(create_error!(GroupTooLarge {
|
return Err(create_error!(GroupTooLarge {
|
||||||
max: config.features.limits.default.group_size
|
max: config.features.limits.global.group_size
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,6 +377,8 @@ impl Channel {
|
|||||||
username: &user.username,
|
username: &user.username,
|
||||||
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
self,
|
self,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -688,6 +690,8 @@ impl Channel {
|
|||||||
username: name,
|
username: name,
|
||||||
avatar: None,
|
avatar: None,
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
self,
|
self,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
@@ -723,6 +727,8 @@ impl Channel {
|
|||||||
username: &user.username,
|
username: &user.username,
|
||||||
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
avatar: user.avatar.as_ref().map(|file| file.id.as_ref()),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
|
None,
|
||||||
self,
|
self,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use indexmap::{IndexMap, IndexSet};
|
use indexmap::{IndexMap, IndexSet};
|
||||||
use iso8601_timestamp::Timestamp;
|
use iso8601_timestamp::Timestamp;
|
||||||
use revolt_config::config;
|
use revolt_config::{config, FeaturesLimits};
|
||||||
use revolt_models::v0::{
|
use revolt_models::v0::{
|
||||||
self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageSort, MessageWebhook,
|
self, BulkMessageResponse, DataMessageSend, Embed, MessageAuthor, MessageFlags, MessageSort,
|
||||||
PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION,
|
MessageWebhook, PushNotification, ReplyIntent, SendableEmbed, Text, RE_MENTION,
|
||||||
};
|
};
|
||||||
use revolt_permissions::{ChannelPermission, PermissionValue};
|
use revolt_permissions::{ChannelPermission, PermissionValue};
|
||||||
use revolt_result::Result;
|
use revolt_result::Result;
|
||||||
@@ -65,6 +65,10 @@ auto_derived_partial!(
|
|||||||
/// Name and / or avatar overrides for this message
|
/// Name and / or avatar overrides for this message
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub masquerade: Option<Masquerade>,
|
pub masquerade: Option<Masquerade>,
|
||||||
|
|
||||||
|
/// Bitfield of message flags
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
},
|
},
|
||||||
"PartialMessage"
|
"PartialMessage"
|
||||||
);
|
);
|
||||||
@@ -200,6 +204,7 @@ impl Default for Message {
|
|||||||
reactions: Default::default(),
|
reactions: Default::default(),
|
||||||
interactions: Default::default(),
|
interactions: Default::default(),
|
||||||
masquerade: None,
|
masquerade: None,
|
||||||
|
flags: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,11 +212,15 @@ impl Default for Message {
|
|||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
impl Message {
|
impl Message {
|
||||||
/// Create message from API data
|
/// Create message from API data
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub async fn create_from_api(
|
pub async fn create_from_api(
|
||||||
db: &Database,
|
db: &Database,
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
data: DataMessageSend,
|
data: DataMessageSend,
|
||||||
author: MessageAuthor<'_>,
|
author: MessageAuthor<'_>,
|
||||||
|
user: Option<v0::User>,
|
||||||
|
member: Option<v0::Member>,
|
||||||
|
limits: FeaturesLimits,
|
||||||
mut idempotency: IdempotencyKey,
|
mut idempotency: IdempotencyKey,
|
||||||
generate_embeds: bool,
|
generate_embeds: bool,
|
||||||
allow_mentions: bool,
|
allow_mentions: bool,
|
||||||
@@ -221,7 +230,7 @@ impl Message {
|
|||||||
Message::validate_sum(
|
Message::validate_sum(
|
||||||
&data.content,
|
&data.content,
|
||||||
data.embeds.as_deref().unwrap_or_default(),
|
data.embeds.as_deref().unwrap_or_default(),
|
||||||
config.features.limits.default.message_length,
|
limits.message_length,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
idempotency
|
idempotency
|
||||||
@@ -237,6 +246,13 @@ impl Message {
|
|||||||
return Err(create_error!(EmptyMessage));
|
return Err(create_error!(EmptyMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure flags are either not set or have permissible values
|
||||||
|
if let Some(flags) = &data.flags {
|
||||||
|
if flags != &0 && flags != &1 {
|
||||||
|
return Err(create_error!(InvalidProperty));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure restrict_reactions is not specified without reactions list
|
// Ensure restrict_reactions is not specified without reactions list
|
||||||
if let Some(interactions) = &data.interactions {
|
if let Some(interactions) = &data.interactions {
|
||||||
if interactions.restrict_reactions {
|
if interactions.restrict_reactions {
|
||||||
@@ -270,6 +286,7 @@ impl Message {
|
|||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
author: author_id,
|
author: author_id,
|
||||||
webhook: webhook.map(|w| w.into()),
|
webhook: webhook.map(|w| w.into()),
|
||||||
|
flags: data.flags.map(|v| v as i32),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -288,9 +305,9 @@ impl Message {
|
|||||||
// Verify replies are valid.
|
// Verify replies are valid.
|
||||||
let mut replies = HashSet::new();
|
let mut replies = HashSet::new();
|
||||||
if let Some(entries) = data.replies {
|
if let Some(entries) = data.replies {
|
||||||
if entries.len() > config.features.limits.default.message_replies {
|
if entries.len() > config.features.limits.global.message_replies {
|
||||||
return Err(create_error!(TooManyReplies {
|
return Err(create_error!(TooManyReplies {
|
||||||
max: config.features.limits.default.message_replies,
|
max: config.features.limits.global.message_replies,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,20 +337,20 @@ impl Message {
|
|||||||
if data
|
if data
|
||||||
.attachments
|
.attachments
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|v| v.len() > config.features.limits.default.message_attachments)
|
.is_some_and(|v| v.len() > limits.message_attachments)
|
||||||
{
|
{
|
||||||
return Err(create_error!(TooManyAttachments {
|
return Err(create_error!(TooManyAttachments {
|
||||||
max: config.features.limits.default.message_attachments,
|
max: limits.message_attachments,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if data
|
if data
|
||||||
.embeds
|
.embeds
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|v| v.len() > config.features.limits.default.message_embeds)
|
.is_some_and(|v| v.len() > config.features.limits.global.message_embeds)
|
||||||
{
|
{
|
||||||
return Err(create_error!(TooManyEmbeds {
|
return Err(create_error!(TooManyEmbeds {
|
||||||
max: config.features.limits.default.message_embeds,
|
max: config.features.limits.global.message_embeds,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +377,9 @@ impl Message {
|
|||||||
message.nonce = Some(idempotency.into_key());
|
message.nonce = Some(idempotency.into_key());
|
||||||
|
|
||||||
// Send the message
|
// Send the message
|
||||||
message.send(db, author, &channel, generate_embeds).await?;
|
message
|
||||||
|
.send(db, author, user, member, &channel, generate_embeds)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(message)
|
Ok(message)
|
||||||
}
|
}
|
||||||
@@ -369,13 +388,15 @@ impl Message {
|
|||||||
pub async fn send_without_notifications(
|
pub async fn send_without_notifications(
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
|
user: Option<v0::User>,
|
||||||
|
member: Option<v0::Member>,
|
||||||
is_dm: bool,
|
is_dm: bool,
|
||||||
generate_embeds: bool,
|
generate_embeds: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
db.insert_message(self).await?;
|
db.insert_message(self).await?;
|
||||||
|
|
||||||
// Fan out events
|
// Fan out events
|
||||||
EventV1::Message(self.clone().into())
|
EventV1::Message(self.clone().into_model(user, member))
|
||||||
.p(self.channel.to_string())
|
.p(self.channel.to_string())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
@@ -416,29 +437,40 @@ impl Message {
|
|||||||
&mut self,
|
&mut self,
|
||||||
db: &Database,
|
db: &Database,
|
||||||
author: MessageAuthor<'_>,
|
author: MessageAuthor<'_>,
|
||||||
|
user: Option<v0::User>,
|
||||||
|
member: Option<v0::Member>,
|
||||||
channel: &Channel,
|
channel: &Channel,
|
||||||
generate_embeds: bool,
|
generate_embeds: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
self.send_without_notifications(
|
self.send_without_notifications(
|
||||||
db,
|
db,
|
||||||
|
user,
|
||||||
|
member,
|
||||||
matches!(channel, Channel::DirectMessage { .. }),
|
matches!(channel, Channel::DirectMessage { .. }),
|
||||||
generate_embeds,
|
generate_embeds,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Push out Web Push notifications
|
if !self.has_suppressed_notifications() {
|
||||||
crate::tasks::web_push::queue(
|
// Push out Web Push notifications
|
||||||
{
|
crate::tasks::web_push::queue(
|
||||||
match channel {
|
{
|
||||||
Channel::DirectMessage { recipients, .. }
|
match channel {
|
||||||
| Channel::Group { recipients, .. } => recipients.clone(),
|
Channel::DirectMessage { recipients, .. }
|
||||||
Channel::TextChannel { .. } => self.mentions.clone().unwrap_or_default(),
|
| Channel::Group { recipients, .. } => recipients.clone(),
|
||||||
_ => vec![],
|
Channel::TextChannel { .. } => self.mentions.clone().unwrap_or_default(),
|
||||||
}
|
_ => vec![],
|
||||||
},
|
}
|
||||||
PushNotification::from(self.clone().into(), Some(author), &channel.id()).await,
|
},
|
||||||
)
|
PushNotification::from(
|
||||||
.await;
|
self.clone().into_model(None, None),
|
||||||
|
Some(author),
|
||||||
|
&channel.id(),
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -470,6 +502,16 @@ impl Message {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether this message has suppressed notifications
|
||||||
|
pub fn has_suppressed_notifications(&self) -> bool {
|
||||||
|
if let Some(flags) = self.flags {
|
||||||
|
flags & MessageFlags::SupressNotifications as i32
|
||||||
|
== MessageFlags::SupressNotifications as i32
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Update message data
|
/// Update message data
|
||||||
pub async fn update(&mut self, db: &Database, partial: PartialMessage) -> Result<()> {
|
pub async fn update(&mut self, db: &Database, partial: PartialMessage) -> Result<()> {
|
||||||
self.apply_options(partial.clone());
|
self.apply_options(partial.clone());
|
||||||
@@ -498,7 +540,7 @@ impl Message {
|
|||||||
.fetch_messages(query)
|
.fetch_messages(query)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Into::into)
|
.map(|msg| msg.into_model(None, None))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
if let Some(true) = include_users {
|
if let Some(true) = include_users {
|
||||||
@@ -616,7 +658,7 @@ impl Message {
|
|||||||
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
|
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
|
||||||
// Check how many reactions are already on the message
|
// Check how many reactions are already on the message
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
if self.reactions.len() >= config.features.limits.default.message_reactions
|
if self.reactions.len() >= config.features.limits.global.message_reactions
|
||||||
&& !self.reactions.contains_key(emoji)
|
&& !self.reactions.contains_key(emoji)
|
||||||
{
|
{
|
||||||
return Err(create_error!(InvalidOperation));
|
return Err(create_error!(InvalidOperation));
|
||||||
@@ -781,7 +823,7 @@ impl Interactions {
|
|||||||
if let Some(reactions) = &self.reactions {
|
if let Some(reactions) = &self.reactions {
|
||||||
permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
|
permissions.throw_if_lacking_channel_permission(ChannelPermission::React)?;
|
||||||
|
|
||||||
if reactions.len() > config.features.limits.default.message_reactions {
|
if reactions.len() > config.features.limits.global.message_reactions {
|
||||||
return Err(create_error!(InvalidOperation));
|
return Err(create_error!(InvalidOperation));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ impl Member {
|
|||||||
id: user.id.clone(),
|
id: user.id.clone(),
|
||||||
}
|
}
|
||||||
.into_message(id.to_string())
|
.into_message(id.to_string())
|
||||||
.send_without_notifications(db, false, false)
|
.send_without_notifications(db, None, None, false, false)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -250,7 +250,7 @@ impl Member {
|
|||||||
}
|
}
|
||||||
.into_message(id.to_string())
|
.into_message(id.to_string())
|
||||||
// TODO: support notifications here in the future?
|
// TODO: support notifications here in the future?
|
||||||
.send_without_notifications(db, false, false)
|
.send_without_notifications(db, None, None, false, false)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use std::{collections::HashSet, time::Duration};
|
use std::{collections::HashSet, str::FromStr, time::Duration};
|
||||||
|
|
||||||
use crate::{events::client::EventV1, Database, File, RatelimitEvent};
|
use crate::{events::client::EventV1, Database, File, RatelimitEvent};
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use revolt_config::config;
|
use revolt_config::{config, FeaturesLimits};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
use revolt_presence::filter_online;
|
use revolt_presence::filter_online;
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
@@ -61,6 +61,7 @@ auto_derived!(
|
|||||||
StatusPresence,
|
StatusPresence,
|
||||||
ProfileContent,
|
ProfileContent,
|
||||||
ProfileBackground,
|
ProfileBackground,
|
||||||
|
DisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User's relationship with another user (or themselves)
|
/// User's relationship with another user (or themselves)
|
||||||
@@ -197,6 +198,22 @@ impl User {
|
|||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get limits for this user
|
||||||
|
pub async fn limits(&self) -> FeaturesLimits {
|
||||||
|
let config = config().await;
|
||||||
|
if ulid::Ulid::from_str(&self.id)
|
||||||
|
.expect("`ulid`")
|
||||||
|
.datetime()
|
||||||
|
.elapsed()
|
||||||
|
.expect("time went backwards")
|
||||||
|
<= Duration::from_secs(86400u64 * config.features.limits.global.new_user_days as u64)
|
||||||
|
{
|
||||||
|
config.features.limits.new_user
|
||||||
|
} else {
|
||||||
|
config.features.limits.default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the relationship with another user
|
/// Get the relationship with another user
|
||||||
pub fn relationship_with(&self, user_b: &str) -> RelationshipStatus {
|
pub fn relationship_with(&self, user_b: &str) -> RelationshipStatus {
|
||||||
if self.id == user_b {
|
if self.id == user_b {
|
||||||
@@ -235,12 +252,11 @@ impl User {
|
|||||||
|
|
||||||
/// Check if this user can acquire another server
|
/// Check if this user can acquire another server
|
||||||
pub async fn can_acquire_server(&self, db: &Database) -> Result<()> {
|
pub async fn can_acquire_server(&self, db: &Database) -> Result<()> {
|
||||||
let config = config().await;
|
if db.fetch_server_count(&self.id).await? <= self.limits().await.servers {
|
||||||
if db.fetch_server_count(&self.id).await? <= config.features.limits.default.servers {
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(create_error!(TooManyServers {
|
Err(create_error!(TooManyServers {
|
||||||
max: config.features.limits.default.servers
|
max: self.limits().await.servers
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -477,6 +493,7 @@ impl User {
|
|||||||
RelationshipStatus::Blocked => Err(create_error!(Blocked)),
|
RelationshipStatus::Blocked => Err(create_error!(Blocked)),
|
||||||
RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)),
|
RelationshipStatus::BlockedOther => Err(create_error!(BlockedByOther)),
|
||||||
RelationshipStatus::Incoming => {
|
RelationshipStatus::Incoming => {
|
||||||
|
// Accept incoming friend request
|
||||||
self.apply_relationship(
|
self.apply_relationship(
|
||||||
db,
|
db,
|
||||||
target,
|
target,
|
||||||
@@ -486,6 +503,26 @@ impl User {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
RelationshipStatus::None => {
|
RelationshipStatus::None => {
|
||||||
|
// Get this user's current count of outgoing friend requests
|
||||||
|
let count = self
|
||||||
|
.relations
|
||||||
|
.as_ref()
|
||||||
|
.map(|relations| {
|
||||||
|
relations
|
||||||
|
.iter()
|
||||||
|
.filter(|r| matches!(r.status, RelationshipStatus::Outgoing))
|
||||||
|
.count()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// If we're over the limit, don't allow creating more requests
|
||||||
|
if count >= self.limits().await.outgoing_friend_requests {
|
||||||
|
return Err(create_error!(TooManyPendingFriendRequests {
|
||||||
|
max: self.limits().await.outgoing_friend_requests
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the friend request
|
||||||
self.apply_relationship(
|
self.apply_relationship(
|
||||||
db,
|
db,
|
||||||
target,
|
target,
|
||||||
@@ -621,6 +658,7 @@ impl User {
|
|||||||
x.background = None;
|
x.background = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FieldsUser::DisplayName => self.display_name = None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -326,6 +326,7 @@ impl IntoDocumentPath for FieldsUser {
|
|||||||
FieldsUser::ProfileContent => "profile.content",
|
FieldsUser::ProfileContent => "profile.content",
|
||||||
FieldsUser::StatusPresence => "status.presence",
|
FieldsUser::StatusPresence => "status.presence",
|
||||||
FieldsUser::StatusText => "status.text",
|
FieldsUser::StatusText => "status.text",
|
||||||
|
FieldsUser::DisplayName => "display_name",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ pub async fn worker(db: Database) {
|
|||||||
let embeds = generate(
|
let embeds = generate(
|
||||||
task.content,
|
task.content,
|
||||||
&config.hosts.january,
|
&config.hosts.january,
|
||||||
config.features.limits.default.message_embeds,
|
config.features.limits.global.message_embeds,
|
||||||
semaphore,
|
semaphore,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|||||||
@@ -459,26 +459,29 @@ impl From<Metadata> for crate::Metadata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crate::Message> for Message {
|
impl crate::Message {
|
||||||
fn from(value: crate::Message) -> Self {
|
pub fn into_model(self, user: Option<User>, member: Option<Member>) -> Message {
|
||||||
Message {
|
Message {
|
||||||
id: value.id,
|
id: self.id,
|
||||||
nonce: value.nonce,
|
nonce: self.nonce,
|
||||||
channel: value.channel,
|
channel: self.channel,
|
||||||
author: value.author,
|
author: self.author,
|
||||||
webhook: value.webhook,
|
user,
|
||||||
content: value.content,
|
member,
|
||||||
system: value.system.map(|system| system.into()),
|
webhook: self.webhook,
|
||||||
attachments: value
|
content: self.content,
|
||||||
|
system: self.system.map(Into::into),
|
||||||
|
attachments: self
|
||||||
.attachments
|
.attachments
|
||||||
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
||||||
edited: value.edited,
|
edited: self.edited,
|
||||||
embeds: value.embeds,
|
embeds: self.embeds,
|
||||||
mentions: value.mentions,
|
mentions: self.mentions,
|
||||||
replies: value.replies,
|
replies: self.replies,
|
||||||
reactions: value.reactions,
|
reactions: self.reactions,
|
||||||
interactions: value.interactions.into(),
|
interactions: self.interactions.into(),
|
||||||
masquerade: value.masquerade.map(|masq| masq.into()),
|
masquerade: self.masquerade.map(Into::into),
|
||||||
|
flags: self.flags.map(|flags| flags as u32).unwrap_or_default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -490,9 +493,11 @@ impl From<crate::PartialMessage> for PartialMessage {
|
|||||||
nonce: value.nonce,
|
nonce: value.nonce,
|
||||||
channel: value.channel,
|
channel: value.channel,
|
||||||
author: value.author,
|
author: value.author,
|
||||||
|
user: None,
|
||||||
|
member: None,
|
||||||
webhook: value.webhook,
|
webhook: value.webhook,
|
||||||
content: value.content,
|
content: value.content,
|
||||||
system: value.system.map(|system| system.into()),
|
system: value.system.map(Into::into),
|
||||||
attachments: value
|
attachments: value
|
||||||
.attachments
|
.attachments
|
||||||
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
.map(|v| v.into_iter().map(|f| f.into()).collect()),
|
||||||
@@ -501,8 +506,9 @@ impl From<crate::PartialMessage> for PartialMessage {
|
|||||||
mentions: value.mentions,
|
mentions: value.mentions,
|
||||||
replies: value.replies,
|
replies: value.replies,
|
||||||
reactions: value.reactions,
|
reactions: value.reactions,
|
||||||
interactions: value.interactions.map(|interactions| interactions.into()),
|
interactions: value.interactions.map(Into::into),
|
||||||
masquerade: value.masquerade.map(|masq| masq.into()),
|
masquerade: value.masquerade.map(Into::into),
|
||||||
|
flags: value.flags.map(|flags| flags as u32),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1089,7 +1095,33 @@ impl crate::User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn into_self(self) -> User {
|
/// Convert user object into user model without presence information
|
||||||
|
pub fn into_known_static<'a>(self, is_online: bool) -> User {
|
||||||
|
User {
|
||||||
|
username: self.username,
|
||||||
|
discriminator: self.discriminator,
|
||||||
|
display_name: self.display_name,
|
||||||
|
avatar: self.avatar.map(|file| file.into()),
|
||||||
|
relations: vec![],
|
||||||
|
badges: self.badges.unwrap_or_default() as u32,
|
||||||
|
online: is_online
|
||||||
|
&& !matches!(
|
||||||
|
self.status,
|
||||||
|
Some(crate::UserStatus {
|
||||||
|
presence: Some(crate::Presence::Invisible),
|
||||||
|
..
|
||||||
|
})
|
||||||
|
),
|
||||||
|
status: self.status.map(|status| status.into()),
|
||||||
|
flags: self.flags.unwrap_or_default() as u32,
|
||||||
|
privileged: self.privileged,
|
||||||
|
bot: self.bot.map(|bot| bot.into()),
|
||||||
|
relationship: RelationshipStatus::None, // events client will populate this from cache
|
||||||
|
id: self.id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn into_self(self, force_online: bool) -> User {
|
||||||
User {
|
User {
|
||||||
username: self.username,
|
username: self.username,
|
||||||
discriminator: self.discriminator,
|
discriminator: self.discriminator,
|
||||||
@@ -1105,7 +1137,7 @@ impl crate::User {
|
|||||||
})
|
})
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
badges: self.badges.unwrap_or_default() as u32,
|
badges: self.badges.unwrap_or_default() as u32,
|
||||||
online: revolt_presence::is_online(&self.id).await
|
online: (force_online || revolt_presence::is_online(&self.id).await)
|
||||||
&& !matches!(
|
&& !matches!(
|
||||||
self.status,
|
self.status,
|
||||||
Some(crate::UserStatus {
|
Some(crate::UserStatus {
|
||||||
@@ -1182,6 +1214,7 @@ impl From<FieldsUser> for crate::FieldsUser {
|
|||||||
FieldsUser::ProfileContent => crate::FieldsUser::ProfileContent,
|
FieldsUser::ProfileContent => crate::FieldsUser::ProfileContent,
|
||||||
FieldsUser::StatusPresence => crate::FieldsUser::StatusPresence,
|
FieldsUser::StatusPresence => crate::FieldsUser::StatusPresence,
|
||||||
FieldsUser::StatusText => crate::FieldsUser::StatusText,
|
FieldsUser::StatusText => crate::FieldsUser::StatusText,
|
||||||
|
FieldsUser::DisplayName => crate::FieldsUser::DisplayName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1194,6 +1227,7 @@ impl From<crate::FieldsUser> for FieldsUser {
|
|||||||
crate::FieldsUser::ProfileContent => FieldsUser::ProfileContent,
|
crate::FieldsUser::ProfileContent => FieldsUser::ProfileContent,
|
||||||
crate::FieldsUser::StatusPresence => FieldsUser::StatusPresence,
|
crate::FieldsUser::StatusPresence => FieldsUser::StatusPresence,
|
||||||
crate::FieldsUser::StatusText => FieldsUser::StatusText,
|
crate::FieldsUser::StatusText => FieldsUser::StatusText,
|
||||||
|
crate::FieldsUser::DisplayName => FieldsUser::DisplayName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,12 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
|
|||||||
if let Some(other_user) = &self.user {
|
if let Some(other_user) = &self.user {
|
||||||
if self.perspective.id == other_user.id {
|
if self.perspective.id == other_user.id {
|
||||||
return RelationshipStatus::User;
|
return RelationshipStatus::User;
|
||||||
|
} else if let Some(bot) = &other_user.bot {
|
||||||
|
// For the purposes of permissions checks,
|
||||||
|
// assume owner is the same as bot
|
||||||
|
if self.perspective.id == bot.owner {
|
||||||
|
return RelationshipStatus::User;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(relations) = &self.perspective.relations {
|
if let Some(relations) = &self.perspective.relations {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-models"
|
name = "revolt-models"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Paul Makles <me@insrt.uk>"]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
@@ -19,8 +19,8 @@ default = ["serde", "partials", "rocket"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-config = { version = "0.7.8", path = "../config" }
|
revolt-config = { version = "0.7.12", path = "../config" }
|
||||||
revolt-permissions = { version = "0.7.8", path = "../permissions" }
|
revolt-permissions = { version = "0.7.12", path = "../permissions" }
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ auto_derived_partial!(
|
|||||||
pub channel: String,
|
pub channel: String,
|
||||||
/// Id of the user or webhook that sent this message
|
/// Id of the user or webhook that sent this message
|
||||||
pub author: String,
|
pub author: String,
|
||||||
|
/// The user that sent this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub user: Option<User>,
|
||||||
|
/// The member that sent this message
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub member: Option<Member>,
|
||||||
/// The webhook that sent this message
|
/// The webhook that sent this message
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub webhook: Option<MessageWebhook>,
|
pub webhook: Option<MessageWebhook>,
|
||||||
@@ -64,6 +70,15 @@ auto_derived_partial!(
|
|||||||
/// Name and / or avatar overrides for this message
|
/// Name and / or avatar overrides for this message
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub masquerade: Option<Masquerade>,
|
pub masquerade: Option<Masquerade>,
|
||||||
|
|
||||||
|
/// Bitfield of message flags
|
||||||
|
///
|
||||||
|
/// https://docs.rs/revolt-models/latest/revolt_models/v0/enum.MessageFlags.html
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde",
|
||||||
|
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||||
|
)]
|
||||||
|
pub flags: u32,
|
||||||
},
|
},
|
||||||
"PartialMessage"
|
"PartialMessage"
|
||||||
);
|
);
|
||||||
@@ -241,6 +256,11 @@ auto_derived!(
|
|||||||
pub 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
|
||||||
pub interactions: Option<Interactions>,
|
pub interactions: Option<Interactions>,
|
||||||
|
|
||||||
|
/// Bitfield of message flags
|
||||||
|
///
|
||||||
|
/// https://docs.rs/revolt-models/latest/revolt_models/v0/enum.MessageFlags.html
|
||||||
|
pub flags: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options for querying messages
|
/// Options for querying messages
|
||||||
@@ -328,6 +348,13 @@ auto_derived!(
|
|||||||
/// Remove all reactions
|
/// Remove all reactions
|
||||||
pub remove_all: Option<bool>,
|
pub remove_all: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Message flag bitfield
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum MessageFlags {
|
||||||
|
/// Message will not send push / desktop notifications
|
||||||
|
SupressNotifications = 1,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Message Author Abstraction
|
/// Message Author Abstraction
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ auto_derived_partial!(
|
|||||||
pub relations: Vec<Relationship>,
|
pub relations: Vec<Relationship>,
|
||||||
|
|
||||||
/// Bitfield of user badges
|
/// Bitfield of user badges
|
||||||
|
///
|
||||||
|
/// https://docs.rs/revolt-models/latest/revolt_models/v0/enum.UserBadges.html
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serde",
|
feature = "serde",
|
||||||
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||||
@@ -52,6 +54,8 @@ auto_derived_partial!(
|
|||||||
pub status: Option<UserStatus>,
|
pub status: Option<UserStatus>,
|
||||||
|
|
||||||
/// Enum of user flags
|
/// Enum of user flags
|
||||||
|
///
|
||||||
|
/// https://docs.rs/revolt-models/latest/revolt_models/v0/enum.UserFlags.html
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serde",
|
feature = "serde",
|
||||||
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
serde(skip_serializing_if = "crate::if_zero_u32", default)
|
||||||
@@ -83,6 +87,7 @@ auto_derived!(
|
|||||||
StatusPresence,
|
StatusPresence,
|
||||||
ProfileContent,
|
ProfileContent,
|
||||||
ProfileBackground,
|
ProfileBackground,
|
||||||
|
DisplayName,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// User's relationship with another user (or themselves)
|
/// User's relationship with another user (or themselves)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-permissions"
|
name = "revolt-permissions"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Paul Makles <me@insrt.uk>"]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
@@ -21,7 +21,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Core
|
# Core
|
||||||
revolt-result = { version = "0.7.8", path = "../result" }
|
revolt-result = { version = "0.7.12", path = "../result" }
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
auto_ops = "0.3.0"
|
auto_ops = "0.3.0"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-presence"
|
name = "revolt-presence"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <me@insrt.uk>"]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-result"
|
name = "revolt-result"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Paul Makles <me@insrt.uk>"]
|
authors = ["Paul Makles <me@insrt.uk>"]
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ pub enum ErrorType {
|
|||||||
Blocked,
|
Blocked,
|
||||||
BlockedByOther,
|
BlockedByOther,
|
||||||
NotFriends,
|
NotFriends,
|
||||||
|
TooManyPendingFriendRequests {
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
|
||||||
// ? Channel related errors
|
// ? Channel related errors
|
||||||
UnknownChannel,
|
UnknownChannel,
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
ErrorType::Blocked => Status::Conflict,
|
ErrorType::Blocked => Status::Conflict,
|
||||||
ErrorType::BlockedByOther => Status::Forbidden,
|
ErrorType::BlockedByOther => Status::Forbidden,
|
||||||
ErrorType::NotFriends => Status::Forbidden,
|
ErrorType::NotFriends => Status::Forbidden,
|
||||||
|
ErrorType::TooManyPendingFriendRequests { .. } => Status::BadRequest,
|
||||||
|
|
||||||
ErrorType::UnknownChannel => Status::NotFound,
|
ErrorType::UnknownChannel => Status::NotFound,
|
||||||
ErrorType::UnknownMessage => Status::NotFound,
|
ErrorType::UnknownMessage => Status::NotFound,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "revolt-delta"
|
name = "revolt-delta"
|
||||||
version = "0.7.8"
|
version = "0.7.12"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
authors = ["Paul Makles <paulmakles@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
@@ -77,6 +77,7 @@ revolt-models = { path = "../core/models", features = [
|
|||||||
"validator",
|
"validator",
|
||||||
"rocket",
|
"rocket",
|
||||||
] }
|
] }
|
||||||
|
revolt-presence = { path = "../core/presence" }
|
||||||
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
|
revolt-result = { path = "../core/result", features = ["rocket", "okapi"] }
|
||||||
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
|
revolt-permissions = { path = "../core/permissions", features = ["schemas"] }
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub async fn fetch_owned_bots(db: &State<Database>, user: User) -> Result<Json<O
|
|||||||
users.sort_by(|a, b| a.id.cmp(&b.id));
|
users.sort_by(|a, b| a.id.cmp(&b.id));
|
||||||
|
|
||||||
Ok(Json(OwnedBotsResponse {
|
Ok(Json(OwnedBotsResponse {
|
||||||
users: join_all(users.into_iter().map(|user| user.into_self())).await,
|
users: join_all(users.into_iter().map(|user| user.into_self(false))).await,
|
||||||
bots: bots.into_iter().map(|bot| bot.into()).collect(),
|
bots: bots.into_iter().map(|bot| bot.into()).collect(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ pub async fn edit(
|
|||||||
return Err(create_error!(InvalidOperation));
|
return Err(create_error!(InvalidOperation));
|
||||||
}
|
}
|
||||||
.into_message(channel.id().to_string())
|
.into_message(channel.id().to_string())
|
||||||
.send(db, user.as_author_for_system(), &channel, false)
|
.send(db, user.as_author_for_system(), None, None, &channel, false)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ pub async fn edit(
|
|||||||
by: user.id.clone(),
|
by: user.id.clone(),
|
||||||
}
|
}
|
||||||
.into_message(channel.id().to_string())
|
.into_message(channel.id().to_string())
|
||||||
.send(db, user.as_author_for_system(), &channel, false)
|
.send(db, user.as_author_for_system(), None, None, &channel, false)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ pub async fn edit(
|
|||||||
by: user.id.clone(),
|
by: user.id.clone(),
|
||||||
}
|
}
|
||||||
.into_message(channel.id().to_string())
|
.into_message(channel.id().to_string())
|
||||||
.send(db, user.as_author_for_system(), &channel, false)
|
.send(db, user.as_author_for_system(), None, None, &channel, false)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ pub async fn edit(
|
|||||||
by: user.id.clone(),
|
by: user.id.clone(),
|
||||||
}
|
}
|
||||||
.into_message(channel.id().to_string())
|
.into_message(channel.id().to_string())
|
||||||
.send(db, user.as_author_for_system(), &channel, false)
|
.send(db, user.as_author_for_system(), None, None, &channel, false)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,10 @@ pub async fn edit(
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let config = config().await;
|
|
||||||
Message::validate_sum(
|
Message::validate_sum(
|
||||||
&edit.content,
|
&edit.content,
|
||||||
edit.embeds.as_deref().unwrap_or_default(),
|
edit.embeds.as_deref().unwrap_or_default(),
|
||||||
config.features.limits.default.message_length,
|
user.limits().await.message_length,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Ensure we have permissions to send a message
|
// Ensure we have permissions to send a message
|
||||||
@@ -98,5 +97,5 @@ pub async fn edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(message.into()))
|
Ok(Json(message.into_model(None, None)))
|
||||||
}
|
}
|
||||||
@@ -29,5 +29,5 @@ pub async fn fetch(
|
|||||||
return Err(create_error!(NotFound));
|
return Err(create_error!(NotFound));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Json(message.into()))
|
Ok(Json(message.into_model(None, None)))
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ use revolt_database::{
|
|||||||
};
|
};
|
||||||
use revolt_database::{Interactions, Message};
|
use revolt_database::{Interactions, Message};
|
||||||
use revolt_models::v0;
|
use revolt_models::v0;
|
||||||
|
use revolt_permissions::PermissionQuery;
|
||||||
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
|
||||||
use revolt_result::{create_error, Result};
|
use revolt_result::{create_error, Result};
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
@@ -75,17 +76,34 @@ pub async fn message_send(
|
|||||||
|
|
||||||
// Create the message
|
// Create the message
|
||||||
let author: v0::User = user.clone().into(db, Some(&user)).await;
|
let author: v0::User = user.clone().into(db, Some(&user)).await;
|
||||||
|
|
||||||
|
// Make sure we have server member (edge case if server owner)
|
||||||
|
query.are_we_a_member().await;
|
||||||
|
|
||||||
|
// Create model user / members
|
||||||
|
let model_user = user
|
||||||
|
.clone()
|
||||||
|
.into_known_static(revolt_presence::is_online(&user.id).await);
|
||||||
|
|
||||||
|
let model_member: Option<v0::Member> = query
|
||||||
|
.member_ref()
|
||||||
|
.as_ref()
|
||||||
|
.map(|member| member.clone().into_owned().into());
|
||||||
|
|
||||||
Ok(Json(
|
Ok(Json(
|
||||||
Message::create_from_api(
|
Message::create_from_api(
|
||||||
db,
|
db,
|
||||||
channel,
|
channel,
|
||||||
data,
|
data,
|
||||||
v0::MessageAuthor::User(&author),
|
v0::MessageAuthor::User(&author),
|
||||||
|
Some(model_user.clone()),
|
||||||
|
model_member.clone(),
|
||||||
|
user.limits().await,
|
||||||
idempotency,
|
idempotency,
|
||||||
permissions.has_channel_permission(ChannelPermission::SendEmbeds),
|
permissions.has_channel_permission(ChannelPermission::SendEmbeds),
|
||||||
allow_mentions,
|
allow_mentions,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into(),
|
.into_model(Some(model_user), model_member),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -45,9 +45,9 @@ pub async fn create_emoji(
|
|||||||
|
|
||||||
// Check that we haven't hit the emoji limit
|
// Check that we haven't hit the emoji limit
|
||||||
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
|
||||||
if emojis.len() >= config.features.limits.default.server_emoji {
|
if emojis.len() >= config.features.limits.global.server_emoji {
|
||||||
return Err(create_error!(TooManyEmoji {
|
return Err(create_error!(TooManyEmoji {
|
||||||
max: config.features.limits.default.server_emoji,
|
max: config.features.limits.global.server_emoji,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub async fn complete(
|
|||||||
Ok(Json(
|
Ok(Json(
|
||||||
User::create(db, data.username, session.user_id, None)
|
User::create(db, data.username, session.user_id, None)
|
||||||
.await?
|
.await?
|
||||||
.into_self()
|
.into_self(false)
|
||||||
.await,
|
.await,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ pub async fn list(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|u| u.into_self()),
|
.map(|u| u.into_self(false)),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ pub async fn create(
|
|||||||
.throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?;
|
.throw_if_lacking_channel_permission(ChannelPermission::ManageRole)?;
|
||||||
|
|
||||||
let config = config().await;
|
let config = config().await;
|
||||||
if server.roles.len() >= config.features.limits.default.server_roles {
|
if server.roles.len() >= config.features.limits.global.server_roles {
|
||||||
return Err(create_error!(TooManyRoles {
|
return Err(create_error!(TooManyRoles {
|
||||||
max: config.features.limits.default.server_roles,
|
max: config.features.limits.global.server_roles,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,23 +24,26 @@ pub async fn edit(
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// Filter out invalid edit fields
|
||||||
|
if !user.privileged && (data.badges.is_some() || data.flags.is_some()) {
|
||||||
|
return Err(create_error!(NotPrivileged));
|
||||||
|
}
|
||||||
|
|
||||||
// If we want to edit a different user than self, ensure we have
|
// If we want to edit a different user than self, ensure we have
|
||||||
// permissions and subsequently replace the user in question
|
// permissions and subsequently replace the user in question
|
||||||
if target.id != "@me" && target.id != user.id {
|
if target.id != "@me" && target.id != user.id {
|
||||||
let target_user = target.as_user(db).await?;
|
let target_user = target.as_user(db).await?;
|
||||||
let is_bot_owner = target_user
|
let is_bot_owner = target_user
|
||||||
.bot
|
.bot
|
||||||
|
.as_ref()
|
||||||
.map(|bot| bot.owner == user.id)
|
.map(|bot| bot.owner == user.id)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if !is_bot_owner && !user.privileged {
|
if !is_bot_owner && !user.privileged {
|
||||||
return Err(create_error!(NotPrivileged));
|
return Err(create_error!(NotPrivileged));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, filter out invalid edit fields
|
user = target_user;
|
||||||
if !user.privileged && (data.badges.is_some() || data.flags.is_some()) {
|
|
||||||
return Err(create_error!(NotPrivileged));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit out early if nothing is changed
|
// Exit out early if nothing is changed
|
||||||
@@ -52,7 +55,7 @@ pub async fn edit(
|
|||||||
&& data.flags.is_none()
|
&& data.flags.is_none()
|
||||||
&& data.remove.is_none()
|
&& data.remove.is_none()
|
||||||
{
|
{
|
||||||
return Ok(Json(user.into_self().await));
|
return Ok(Json(user.into_self(false).await));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. Remove fields from object
|
// 1. Remove fields from object
|
||||||
@@ -126,5 +129,5 @@ pub async fn edit(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Json(user.into_self().await))
|
Ok(Json(user.into_self(false).await))
|
||||||
}
|
}
|
||||||
@@ -9,5 +9,5 @@ use rocket::serde::json::Json;
|
|||||||
#[openapi(tag = "User Information")]
|
#[openapi(tag = "User Information")]
|
||||||
#[get("/@me")]
|
#[get("/@me")]
|
||||||
pub async fn fetch(user: User) -> Result<Json<v0::User>> {
|
pub async fn fetch(user: User) -> Result<Json<v0::User>> {
|
||||||
Ok(Json(user.into_self().await))
|
Ok(Json(user.into_self(false).await))
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ use rocket::{serde::json::Json, State};
|
|||||||
#[get("/<target>")]
|
#[get("/<target>")]
|
||||||
pub async fn fetch(db: &State<Database>, user: User, target: Reference) -> Result<Json<v0::User>> {
|
pub async fn fetch(db: &State<Database>, user: User, target: Reference) -> Result<Json<v0::User>> {
|
||||||
if user.id == target.id {
|
if user.id == target.id {
|
||||||
return Ok(Json(user.into_self().await));
|
return Ok(Json(user.into_self(false).await));
|
||||||
}
|
}
|
||||||
|
|
||||||
let target = target.as_user(db).await?;
|
let target = target.as_user(db).await?;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use revolt_config::config;
|
||||||
use revolt_database::{
|
use revolt_database::{
|
||||||
util::{idempotency::IdempotencyKey, reference::Reference},
|
util::{idempotency::IdempotencyKey, reference::Reference},
|
||||||
Database, Message,
|
Database, Message,
|
||||||
@@ -58,11 +59,14 @@ pub async fn webhook_execute(
|
|||||||
channel,
|
channel,
|
||||||
data,
|
data,
|
||||||
v0::MessageAuthor::Webhook(&webhook.into()),
|
v0::MessageAuthor::Webhook(&webhook.into()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
config().await.features.limits.default,
|
||||||
idempotency,
|
idempotency,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.into(),
|
.into_model(None, None),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -1072,6 +1072,13 @@ pub async fn webhook_execute_github(
|
|||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
message.attach_sendable_embed(db, sendable_embed).await?;
|
message.attach_sendable_embed(db, sendable_embed).await?;
|
||||||
message
|
message
|
||||||
.send(db, MessageAuthor::Webhook(&webhook.into()), &channel, false)
|
.send(
|
||||||
|
db,
|
||||||
|
MessageAuthor::Webhook(&webhook.into()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
&channel,
|
||||||
|
false,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user