mirror of
https://github.com/stoatchat/stoatchat.git
synced 2026-07-14 05:26:59 +00:00
feat: parse message push notification content and replace internal formatting (#693)
* parse message push notification content and replace internal formatting Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com> * fix: don't unwrap the error I was using for db debugging Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com> * fix: emojis & remove compiler warning Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com> * fix: emojis in pushd Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com> * feat: report errors in the render function to sentry Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com> --------- Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
This commit is contained in:
11
Cargo.lock
generated
11
Cargo.lock
generated
@@ -6660,9 +6660,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.2"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -6672,9 +6672,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.10"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -7149,9 +7149,12 @@ dependencies = [
|
||||
"iso8601-timestamp",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"redis-kiss",
|
||||
"regex",
|
||||
"revolt-config",
|
||||
"revolt-database",
|
||||
"revolt-models",
|
||||
"revolt-parser",
|
||||
"revolt-presence",
|
||||
"revolt-result",
|
||||
"revolt_a2",
|
||||
|
||||
@@ -89,6 +89,11 @@ production = true
|
||||
# Increasing this will resolve mentions faster, but will consume more memory while resolving.
|
||||
mass_mention_chunk_size = 200
|
||||
|
||||
# How long pushd will cache resolved names for rendered message notifications.
|
||||
# Increasing this will result in lower database usage, but may result in a situation where a user/channel/role name changes
|
||||
# and the notifications still resolve to the old name.
|
||||
render_cache_time = 60
|
||||
|
||||
# none of these should need changing
|
||||
exchange = "revolt.notifications"
|
||||
message_queue = "notifications.origin.message"
|
||||
|
||||
@@ -248,6 +248,7 @@ pub struct Pushd {
|
||||
pub production: bool,
|
||||
pub exchange: String,
|
||||
pub mass_mention_chunk_size: usize,
|
||||
pub render_cache_time: usize,
|
||||
|
||||
// Queues
|
||||
pub message_queue: String,
|
||||
|
||||
@@ -388,6 +388,7 @@ impl Message {
|
||||
mut role_mentions,
|
||||
mut mentions_everyone,
|
||||
mut mentions_online,
|
||||
..
|
||||
} = message_mentions;
|
||||
|
||||
if allow_mass_mentions && server_id.is_some() && !role_mentions.is_empty() {
|
||||
|
||||
@@ -133,7 +133,10 @@ auto_derived!(
|
||||
#[serde(rename = "message_unpinned")]
|
||||
MessageUnpinned { id: String, by: String },
|
||||
#[serde(rename = "call_started")]
|
||||
CallStarted { by: String, finished_at: Option<Timestamp> },
|
||||
CallStarted {
|
||||
by: String,
|
||||
finished_at: Option<Timestamp>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Name and / or avatar override information
|
||||
@@ -201,6 +204,9 @@ auto_derived!(
|
||||
pub image: Option<String>,
|
||||
/// Message content or system message information
|
||||
pub body: String,
|
||||
/// The raw body, if the body has been rendered
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub raw_body: Option<String>,
|
||||
/// Unique tag, usually the channel ID
|
||||
pub tag: String,
|
||||
/// Timestamp at which this notification was created
|
||||
@@ -508,6 +514,7 @@ impl PushNotification {
|
||||
icon,
|
||||
image,
|
||||
body,
|
||||
raw_body: None,
|
||||
tag: channel.id().to_string(),
|
||||
timestamp,
|
||||
url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id),
|
||||
|
||||
@@ -16,23 +16,29 @@ pub enum MessageToken<'a> {
|
||||
UserMention(&'a str),
|
||||
#[regex("<%(?&id)>", |lex| &lex.slice()[2..lex.slice().len() - 1],)]
|
||||
RoleMention(&'a str),
|
||||
#[regex("<#(?&id)>", |lex| &lex.slice()[2..lex.slice().len() - 1],)]
|
||||
ChannelMention(&'a str),
|
||||
#[regex(":(?&id):", |lex| &lex.slice()[1..lex.slice().len() - 1],)]
|
||||
Emoji(&'a str),
|
||||
#[token("@everyone")]
|
||||
MentionEveryone,
|
||||
#[token("@online")]
|
||||
MentionOnline
|
||||
MentionOnline,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct MessageResults {
|
||||
pub user_mentions: HashSet<String>,
|
||||
pub role_mentions: HashSet<String>,
|
||||
pub channel_mentions: HashSet<String>,
|
||||
pub emojis: HashSet<String>,
|
||||
pub mentions_everyone: bool,
|
||||
pub mentions_online: bool
|
||||
pub mentions_online: bool,
|
||||
}
|
||||
|
||||
struct MessageParserIterator<'a, I> {
|
||||
inner: I,
|
||||
temp: VecDeque<MessageToken<'a>>
|
||||
temp: VecDeque<MessageToken<'a>>,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = MessageToken<'a>>> Iterator for MessageParserIterator<'a, I> {
|
||||
@@ -55,11 +61,11 @@ impl<'a, I: Iterator<Item = MessageToken<'a>>> Iterator for MessageParserIterato
|
||||
if next_token == Some(MessageToken::CodeblockMarker(ty)) {
|
||||
self.temp.clear();
|
||||
self.temp.push_back(MessageToken::CodeblockMarker(ty));
|
||||
break next_token
|
||||
break next_token;
|
||||
} else if let Some(token) = next_token {
|
||||
self.temp.push_back(token);
|
||||
} else {
|
||||
break Some(MessageToken::CodeblockMarker(ty))
|
||||
break Some(MessageToken::CodeblockMarker(ty));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -69,10 +75,10 @@ impl<'a, I: Iterator<Item = MessageToken<'a>>> Iterator for MessageParserIterato
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_message_iter(text: &str) -> impl Iterator<Item = MessageToken> + '_ {
|
||||
pub fn parse_message_iter(text: &str) -> impl Iterator<Item = MessageToken<'_>> + '_ {
|
||||
MessageParserIterator {
|
||||
inner: MessageToken::lexer(text).flatten(),
|
||||
temp: VecDeque::new()
|
||||
temp: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,13 +88,23 @@ pub fn parse_message(text: &str) -> MessageResults {
|
||||
for token in parse_message_iter(text) {
|
||||
match token {
|
||||
MessageToken::Escape => {}
|
||||
MessageToken::CodeblockMarker(_) => {},
|
||||
MessageToken::UserMention(id) => { results.user_mentions.insert(id.to_string()); },
|
||||
MessageToken::RoleMention(id) => { results.role_mentions.insert(id.to_string()); },
|
||||
MessageToken::CodeblockMarker(_) => {}
|
||||
MessageToken::UserMention(id) => {
|
||||
results.user_mentions.insert(id.to_string());
|
||||
}
|
||||
MessageToken::RoleMention(id) => {
|
||||
results.role_mentions.insert(id.to_string());
|
||||
}
|
||||
MessageToken::ChannelMention(id) => {
|
||||
results.channel_mentions.insert(id.to_string());
|
||||
}
|
||||
MessageToken::Emoji(id) => {
|
||||
results.emojis.insert(id.to_string());
|
||||
}
|
||||
MessageToken::MentionEveryone => results.mentions_everyone = true,
|
||||
MessageToken::MentionOnline => results.mentions_online = true,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
results
|
||||
}
|
||||
@@ -109,7 +125,10 @@ mod tests {
|
||||
let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>.").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(
|
||||
output[0],
|
||||
MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -117,7 +136,10 @@ mod tests {
|
||||
let output = parse_message_iter("Hello <%01FD58YK5W7QRV5H3D64KTQYX3>.").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(output[0], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(
|
||||
output[0],
|
||||
MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -138,29 +160,57 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_everything() {
|
||||
let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>, <%01FD58YK5W7QRV5H3D64KTQYX3>, @everyone and @online.").collect::<Vec<_>>();
|
||||
let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>, <%01FD58YK5W7QRV5H3D64KTQYX3>, <#01FD58YK5W7QRV5H3D64KTQYX3> @everyone and @online. :01FD58YK5W7QRV5H3D64KTQYX3:").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 4);
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[3], MessageToken::MentionOnline);
|
||||
assert_eq!(output.len(), 6);
|
||||
assert_eq!(
|
||||
output[0],
|
||||
MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[1],
|
||||
MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[2],
|
||||
MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(output[3], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[4], MessageToken::MentionOnline);
|
||||
assert_eq!(output[5], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_everything_no_spaces() {
|
||||
let output = parse_message_iter("<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::<Vec<_>>();
|
||||
let output = parse_message_iter(
|
||||
"<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:",
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 4);
|
||||
assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[3], MessageToken::MentionOnline);
|
||||
assert_eq!(output.len(), 6);
|
||||
assert_eq!(
|
||||
output[0],
|
||||
MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[1],
|
||||
MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[2],
|
||||
MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(output[3], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[4], MessageToken::MentionOnline);
|
||||
assert_eq!(output[5], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codeblock_no_mentions() {
|
||||
let output = parse_message_iter("```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online\n```").collect::<Vec<_>>();
|
||||
let output = parse_message_iter(
|
||||
"```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:\n```",
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 2);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
|
||||
@@ -169,19 +219,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_uncontained_codeblock_should_mention() {
|
||||
let output = parse_message_iter("```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::<Vec<_>>();
|
||||
let output = parse_message_iter(
|
||||
"```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:",
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 5);
|
||||
assert_eq!(output.len(), 7);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
|
||||
assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[3], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[4], MessageToken::MentionOnline);
|
||||
assert_eq!(
|
||||
output[1],
|
||||
MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[2],
|
||||
MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[3],
|
||||
MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(output[4], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[5], MessageToken::MentionOnline);
|
||||
assert_eq!(output[6], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_codeblock_no_mentions() {
|
||||
let output = parse_message_iter("`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online`").collect::<Vec<_>>();
|
||||
let output = parse_message_iter(
|
||||
"`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:`",
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 2);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(1));
|
||||
@@ -190,19 +257,33 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_uncontained_inline_codeblock_should_mention() {
|
||||
let output = parse_message_iter("`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::<Vec<_>>();
|
||||
let output = parse_message_iter(
|
||||
"`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:",
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 5);
|
||||
assert_eq!(output.len(), 6);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(1));
|
||||
assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
assert_eq!(output[3], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[4], MessageToken::MentionOnline);
|
||||
assert_eq!(
|
||||
output[1],
|
||||
MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[2],
|
||||
MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(
|
||||
output[3],
|
||||
MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
|
||||
);
|
||||
assert_eq!(output[4], MessageToken::MentionEveryone);
|
||||
assert_eq!(output[5], MessageToken::MentionOnline);
|
||||
assert_eq!(output[6], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_codeblock_with_language_no_mentions() {
|
||||
let output = parse_message_iter("```rust\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online```").collect::<Vec<_>>();
|
||||
let output = parse_message_iter("```rust\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:```").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 2);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
|
||||
@@ -220,7 +301,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_double_inline_codeblock_with_backticks_inside() {
|
||||
let output = parse_message_iter("``this `should` not `ping` @everyone``").collect::<Vec<_>>();
|
||||
let output =
|
||||
parse_message_iter("``this `should` not `ping` @everyone``").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 2);
|
||||
assert_eq!(output[0], MessageToken::CodeblockMarker(2));
|
||||
@@ -238,7 +320,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_escaped_codeblock() {
|
||||
let output = parse_message_iter("i am ~~not~~ pinging \\`@everyone` ok.").collect::<Vec<_>>();
|
||||
let output =
|
||||
parse_message_iter("i am ~~not~~ pinging \\`@everyone` ok.").collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(output.len(), 3);
|
||||
assert_eq!(output[0], MessageToken::Escape);
|
||||
@@ -253,4 +336,4 @@ mod tests {
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(output[0], MessageToken::Escape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ revolt-models = { version = "0.11.5", path = "../../core/models", features = [
|
||||
revolt-presence = { version = "0.11.5", path = "../../core/presence", features = [
|
||||
"redis-is-patched",
|
||||
] }
|
||||
revolt-parser = { version = "0.11.5", path = "../../core/parser" }
|
||||
|
||||
anyhow = { version = "1.0.98" }
|
||||
|
||||
@@ -26,6 +27,7 @@ fcm_v1 = "0.3.0"
|
||||
web-push = "0.10.0"
|
||||
isahc = { optional = true, version = "1.7", features = ["json"] }
|
||||
revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] }
|
||||
redis-kiss = "0.1.4"
|
||||
tokio = "1.39.2"
|
||||
async-trait = "0.1.81"
|
||||
ulid = "1.0.0"
|
||||
@@ -35,6 +37,8 @@ authifier = "1.0.16"
|
||||
log = "0.4.11"
|
||||
pretty_env_logger = "0.4.0"
|
||||
|
||||
regex = "1.12.3"
|
||||
|
||||
#serialization
|
||||
serde_json = "1"
|
||||
revolt_optional_struct = "0.2.0"
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{
|
||||
hash::RandomState,
|
||||
};
|
||||
|
||||
use crate::consumers::inbound::internal::*;
|
||||
use crate::{consumers::inbound::internal::*, utils};
|
||||
use amqprs::{
|
||||
channel::{BasicPublishArguments, Channel},
|
||||
connection::Connection,
|
||||
@@ -17,6 +17,7 @@ use revolt_database::{
|
||||
MessageFlagsValue,
|
||||
};
|
||||
use revolt_models::v0::{MessageFlags, PushNotification};
|
||||
use revolt_result::ToRevoltError;
|
||||
|
||||
pub struct MassMessageConsumer {
|
||||
#[allow(dead_code)]
|
||||
@@ -129,7 +130,17 @@ impl MassMessageConsumer {
|
||||
) -> Result<()> {
|
||||
let config = revolt_config::config().await;
|
||||
let content = String::from_utf8(content)?;
|
||||
let payload: MassMessageSentPayload = serde_json::from_str(content.as_str())?;
|
||||
let mut payload: MassMessageSentPayload = serde_json::from_str(content.as_str())?;
|
||||
|
||||
for push in payload.notifications.iter_mut() {
|
||||
if let Ok(body) = utils::render_notification_content(push, &self.db)
|
||||
.await
|
||||
.to_internal_error()
|
||||
{
|
||||
push.raw_body = Some(push.body.clone());
|
||||
push.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Received mass message event");
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::consumers::inbound::internal::*;
|
||||
use crate::{consumers::inbound::internal::*, utils};
|
||||
use amqprs::{
|
||||
channel::{BasicPublishArguments, Channel},
|
||||
connection::Connection,
|
||||
@@ -11,6 +11,7 @@ use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use log::debug;
|
||||
use revolt_database::{events::rabbit::*, Database};
|
||||
use revolt_result::ToRevoltError;
|
||||
|
||||
pub struct MessageConsumer {
|
||||
#[allow(dead_code)]
|
||||
@@ -64,7 +65,15 @@ impl MessageConsumer {
|
||||
content: Vec<u8>,
|
||||
) -> Result<()> {
|
||||
let content = String::from_utf8(content)?;
|
||||
let payload: MessageSentPayload = serde_json::from_str(content.as_str())?;
|
||||
let mut payload: MessageSentPayload = serde_json::from_str(content.as_str())?;
|
||||
|
||||
if let Ok(body) = utils::render_notification_content(&payload.notification, &self.db)
|
||||
.await
|
||||
.to_internal_error()
|
||||
{
|
||||
payload.notification.raw_body = Some(payload.notification.body);
|
||||
payload.notification.body = body;
|
||||
}
|
||||
|
||||
debug!("Received message event on origin");
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use revolt_config::{config, Settings};
|
||||
use tokio::sync::Notify;
|
||||
|
||||
mod consumers;
|
||||
mod utils;
|
||||
use consumers::{
|
||||
inbound::{
|
||||
ack::AckConsumer, dm_call::DmCallConsumer, fr_accepted::FRAcceptedConsumer,
|
||||
|
||||
2
crates/daemons/pushd/src/utils/mod.rs
Normal file
2
crates/daemons/pushd/src/utils/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
mod renderer;
|
||||
pub use renderer::render_notification_content;
|
||||
272
crates/daemons/pushd/src/utils/renderer.rs
Normal file
272
crates/daemons/pushd/src/utils/renderer.rs
Normal file
@@ -0,0 +1,272 @@
|
||||
use redis_kiss::{get_connection as _get_conn, AsyncCommands, Conn};
|
||||
use regex::Regex;
|
||||
use revolt_config::config;
|
||||
use revolt_database::{Channel, Database};
|
||||
use revolt_models::v0::PushNotification;
|
||||
use revolt_parser::parse_message;
|
||||
use revolt_result::{create_error, Result, ToRevoltError};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
collections::{HashMap, HashSet},
|
||||
};
|
||||
use tokio::join;
|
||||
|
||||
async fn get_connection() -> Result<Conn> {
|
||||
_get_conn().await.map_err(|_| create_error!(InternalError))
|
||||
}
|
||||
|
||||
pub async fn render_notification_content(
|
||||
notification: &PushNotification,
|
||||
db: &Database,
|
||||
) -> Result<String> {
|
||||
let parsed = parse_message(¬ification.body);
|
||||
|
||||
let user_mentions: HashMap<String, String>;
|
||||
let channel_mentions: HashMap<String, String>;
|
||||
let emojis: HashMap<String, String>;
|
||||
let roles: HashMap<String, String>;
|
||||
|
||||
let server_id: Option<String> = get_channel_server_id(notification.channel.id(), db)
|
||||
.await
|
||||
.map(Some)
|
||||
.or(Ok(None))?;
|
||||
|
||||
if server_id.is_some() {
|
||||
(user_mentions, channel_mentions, emojis, roles) = join!(
|
||||
get_items(
|
||||
parsed.user_mentions,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_user_display_name,
|
||||
"Unknown User".to_string()
|
||||
),
|
||||
get_items(
|
||||
parsed.channel_mentions,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_channel_name,
|
||||
"Unknown Channel".to_string()
|
||||
),
|
||||
get_items(
|
||||
parsed.emojis,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_emoji_name,
|
||||
"Unknown Emoji".to_string()
|
||||
),
|
||||
get_items(
|
||||
parsed.role_mentions,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_role_name,
|
||||
"Unknown Role".to_string()
|
||||
),
|
||||
);
|
||||
} else {
|
||||
roles = HashMap::default();
|
||||
|
||||
(user_mentions, channel_mentions, emojis) = join!(
|
||||
get_items(
|
||||
parsed.user_mentions,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_user_display_name,
|
||||
"Unknown User".to_string()
|
||||
),
|
||||
get_items(
|
||||
parsed.channel_mentions,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_channel_name,
|
||||
"Unknown Channel".to_string()
|
||||
),
|
||||
get_items(
|
||||
parsed.emojis,
|
||||
server_id.as_deref(),
|
||||
db,
|
||||
get_emoji_name,
|
||||
"Unknown Emoji".to_string()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
let mut body = Cow::Borrowed(notification.body.as_str());
|
||||
|
||||
for (uid, name) in user_mentions {
|
||||
replace_all_in_place(
|
||||
Regex::new(format!("<@{uid}>").as_str()).unwrap(),
|
||||
&mut body,
|
||||
format!("@{name}").as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
for (uid, name) in channel_mentions {
|
||||
replace_all_in_place(
|
||||
Regex::new(format!("<#{uid}>").as_str()).unwrap(),
|
||||
&mut body,
|
||||
format!("#{name}").as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
for (uid, name) in roles {
|
||||
replace_all_in_place(
|
||||
Regex::new(format!("<%{uid}>").as_str()).unwrap(),
|
||||
&mut body,
|
||||
format!("%{name}").as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
for (uid, name) in emojis {
|
||||
replace_all_in_place(
|
||||
Regex::new(format!(":{uid}:").as_str()).unwrap(),
|
||||
&mut body,
|
||||
format!(":{name}:").as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(body.to_string())
|
||||
}
|
||||
|
||||
async fn get_items<F>(
|
||||
items: HashSet<String>,
|
||||
server_id: Option<&str>,
|
||||
db: &Database,
|
||||
getter: F,
|
||||
invalid_string: String,
|
||||
) -> HashMap<String, String>
|
||||
where
|
||||
F: AsyncFn(&str, Option<&str>, &Database) -> Result<String>,
|
||||
{
|
||||
let mut resp = HashMap::default();
|
||||
|
||||
for obj_id in items {
|
||||
resp.insert(
|
||||
obj_id.clone(),
|
||||
getter(&obj_id, server_id, db)
|
||||
.await
|
||||
.unwrap_or(invalid_string.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
resp
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
async fn get_user_display_name(id: &str, server: Option<&str>, db: &Database) -> Result<String> {
|
||||
let config = config().await;
|
||||
|
||||
let mut conn = get_connection().await?;
|
||||
let key = format!("pushd-user-cache:{}:{id}", server.unwrap_or("GLOBAL"));
|
||||
|
||||
if let Ok(name) = conn.get(key.clone()).await {
|
||||
return Ok(name);
|
||||
}
|
||||
|
||||
if let Some(server) = server {
|
||||
let member = db.fetch_member(server, id).await?;
|
||||
if let Some(nickname) = member.nickname {
|
||||
conn.set_ex::<_, _, ()>(key, nickname.clone(), config.pushd.render_cache_time)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
return Ok(nickname);
|
||||
}
|
||||
}
|
||||
|
||||
let user = db.fetch_user(id).await?;
|
||||
let name = user.display_name.unwrap_or(user.username);
|
||||
|
||||
conn.set_ex::<_, _, ()>(key, name.clone(), config.pushd.render_cache_time)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
async fn get_channel_name(id: &str, _server: Option<&str>, db: &Database) -> Result<String> {
|
||||
let config = config().await;
|
||||
|
||||
let mut conn = get_connection().await?;
|
||||
let key = format!("pushd-channel-cache:{id}");
|
||||
|
||||
if let Ok(name) = conn.get(key.clone()).await {
|
||||
return Ok(name);
|
||||
}
|
||||
|
||||
let channel = db.fetch_channel(id).await?;
|
||||
let name = match channel {
|
||||
Channel::DirectMessage { .. } => "DM Channel".to_string(),
|
||||
Channel::Group { name, .. } | Channel::TextChannel { name, .. } => name,
|
||||
Channel::SavedMessages { .. } => "Err".to_string(),
|
||||
};
|
||||
|
||||
conn.set_ex::<_, _, ()>(key, name.clone(), config.pushd.render_cache_time)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
async fn get_role_name(id: &str, server: Option<&str>, db: &Database) -> Result<String> {
|
||||
let server = server.unwrap(); // Must be passed, but the interface must stay the same as the other getters
|
||||
let config: revolt_config::Settings = config().await;
|
||||
|
||||
let mut conn = get_connection().await?;
|
||||
let key = format!("pushd-role-cache:{server}:{id}");
|
||||
|
||||
if let Ok(name) = conn.get(key.clone()).await {
|
||||
return Ok(name);
|
||||
}
|
||||
|
||||
let server = db.fetch_server(server).await?;
|
||||
let name = server
|
||||
.roles
|
||||
.get(id)
|
||||
.ok_or_else(|| create_error!(NotFound))?
|
||||
.name
|
||||
.clone();
|
||||
|
||||
conn.set_ex::<_, _, ()>(key, name.clone(), config.pushd.render_cache_time)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
async fn get_emoji_name(id: &str, _server: Option<&str>, db: &Database) -> Result<String> {
|
||||
let config: revolt_config::Settings = config().await;
|
||||
|
||||
let mut conn = get_connection().await?;
|
||||
let key = format!("pushd-emoji-cache:{id}");
|
||||
|
||||
if let Ok(name) = conn.get(key.clone()).await {
|
||||
return Ok(name);
|
||||
}
|
||||
|
||||
let emoji = db.fetch_emoji(id).await?;
|
||||
let name = emoji.name;
|
||||
|
||||
conn.set_ex::<_, _, ()>(key, name.clone(), config.pushd.render_cache_time)
|
||||
.await
|
||||
.to_internal_error()?;
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
|
||||
// utility
|
||||
|
||||
async fn get_channel_server_id(channel_id: &str, db: &Database) -> Result<String> {
|
||||
match db.fetch_channel(channel_id).await? {
|
||||
Channel::DirectMessage { .. } | Channel::Group { .. } | Channel::SavedMessages { .. } => {
|
||||
Err(create_error!(NotFound))
|
||||
}
|
||||
Channel::TextChannel { server, .. } => Ok(server),
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_all_in_place<R: regex::Replacer>(regex: Regex, s: &mut Cow<'_, str>, replacer: R) {
|
||||
let new = regex.replace_all(s, replacer);
|
||||
if let Cow::Owned(o) = new {
|
||||
*s = Cow::Owned(o);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user