377 lines
12 KiB
Rust
377 lines
12 KiB
Rust
use std::{str::FromStr, sync::atomic::Ordering};
|
||
|
||
use serde_json::Value;
|
||
use serenity::{
|
||
async_trait,
|
||
client::{Context, EventHandler},
|
||
http::{
|
||
request::{Request, RequestBuilder},
|
||
routing::RouteInfo,
|
||
},
|
||
model::{
|
||
channel::{Channel, ChannelType, Message},
|
||
id::ChannelId,
|
||
id::GuildId,
|
||
},
|
||
};
|
||
|
||
use crate::track::{err_strs::*, Active, Sent, SourceChannel, TargetChannel, Weave};
|
||
|
||
macro_rules! create_embed {
|
||
($embed:expr, $message:expr, $channel_name:expr) => {{
|
||
$embed.author(|author| {
|
||
author
|
||
.icon_url($message.author.face())
|
||
.name(format!["in #{}\n", $channel_name])
|
||
.url(format![
|
||
"https://discord.com/channels/{}/{}",
|
||
$message
|
||
.guild_id
|
||
.expect("Unsure how we got here, but we did"),
|
||
$message.channel_id
|
||
]);
|
||
author
|
||
});
|
||
if let (Some(guild_id), Some(replied_to)) = ($message.guild_id, $message.referenced_message) {
|
||
$embed
|
||
.title(format![
|
||
"@{}: {}",
|
||
replied_to.author.name,
|
||
if replied_to.content.chars().count() > 48 {
|
||
format![
|
||
"{}...",
|
||
replied_to.content.chars().take(64).collect::<String>()
|
||
]
|
||
} else {
|
||
replied_to.content.clone()
|
||
}
|
||
])
|
||
.url(format![
|
||
"https://discord.com/channels/{}/{}/{}",
|
||
guild_id, $message.channel_id, replied_to.id,
|
||
]);
|
||
}
|
||
#[allow(clippy::invisible_characters)]
|
||
if !$message.content.is_empty() {
|
||
$embed
|
||
.description(format![
|
||
"<@{}> [➤➤➤](https://discord.com/channels/{}/{}/{})\n{}",
|
||
$message.author.id,
|
||
$message
|
||
.guild_id
|
||
.expect("Unsure how we got here, but we did"),
|
||
$message.channel_id,
|
||
$message.id,
|
||
$message.timestamp.to_string()
|
||
])
|
||
.field(
|
||
"",
|
||
$message.content.chars().take(1024).collect::<String>(),
|
||
true,
|
||
);
|
||
if $message.content.chars().count() > 1024 {
|
||
#[allow(clippy::invisible_characters)]
|
||
$embed.field(
|
||
"",
|
||
$message
|
||
.content
|
||
.chars()
|
||
.skip(1024)
|
||
.take(1024)
|
||
.collect::<String>(),
|
||
true,
|
||
);
|
||
}
|
||
}
|
||
if !$message.attachments.is_empty() {
|
||
let mut s = String::new();
|
||
for attachment in $message.attachments {
|
||
s = format!["{}\n{}", s, attachment.url];
|
||
}
|
||
let mut size = 0usize;
|
||
let mut stopped_at = None;
|
||
$embed.field(
|
||
"__Attachments__",
|
||
s.lines()
|
||
.enumerate()
|
||
.map(|(n, line)| {
|
||
if stopped_at.is_none() {
|
||
if (size + line.chars().count()) > 1024 {
|
||
stopped_at = Some(n);
|
||
""
|
||
} else {
|
||
size += line.chars().count();
|
||
line
|
||
}
|
||
} else {
|
||
""
|
||
}
|
||
})
|
||
.collect::<String>(),
|
||
false,
|
||
);
|
||
if let Some(n) = stopped_at {
|
||
#[allow(clippy::invisible_characters)]
|
||
$embed.field("", s.lines().skip(n + 1).collect::<String>(), true);
|
||
}
|
||
}
|
||
$embed
|
||
}};
|
||
}
|
||
|
||
#[derive(Default)]
|
||
pub struct Handler;
|
||
|
||
#[async_trait]
|
||
impl EventHandler for Handler {
|
||
fn message<'life0, 'async_trait>(
|
||
&'life0 self,
|
||
ctx: Context,
|
||
new_message: Message,
|
||
) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
where
|
||
'life0: 'async_trait,
|
||
Self: 'async_trait,
|
||
{
|
||
Box::pin(async move {
|
||
let data = ctx.data.read().await;
|
||
if data
|
||
.get::<Active>()
|
||
.expect(EXPECTED_ACTIVE)
|
||
.load(Ordering::Relaxed)
|
||
{
|
||
let source = if let Some(source) = *data
|
||
.get::<SourceChannel>()
|
||
.expect(EXPECTED_SOURCE_CHANNEL)
|
||
.lock()
|
||
.await
|
||
{
|
||
source
|
||
} else {
|
||
return;
|
||
};
|
||
let weave = data
|
||
.get::<Weave>()
|
||
.expect(EXPECTED_ACTIVE)
|
||
.load(Ordering::Relaxed);
|
||
|
||
if source != new_message.channel_id {
|
||
if weave {
|
||
let request = RequestBuilder::new(RouteInfo::GetChannel {
|
||
channel_id: new_message.channel_id.into(),
|
||
});
|
||
let request = Request::new(request);
|
||
if let Ok(response) = ctx.http.request(request).await {
|
||
if let Ok(response) = response.json::<Value>().await {
|
||
if let Some(parent_id) = response.get("parent_id") {
|
||
if let Ok(parent_id) = ChannelId::from_str(&format![
|
||
"<#{}>",
|
||
parent_id.as_str().unwrap_or("")
|
||
]) {
|
||
if source != parent_id {
|
||
return;
|
||
}
|
||
} else {
|
||
return;
|
||
};
|
||
} else {
|
||
return;
|
||
};
|
||
} else {
|
||
#[cfg(debug)]
|
||
println!["Failed to deserialize response"];
|
||
return;
|
||
};
|
||
} else {
|
||
#[cfg(debug)]
|
||
println!["Failed to get channel information"];
|
||
return;
|
||
};
|
||
} else {
|
||
return;
|
||
}
|
||
}
|
||
|
||
let target = if let Some(target) = *data
|
||
.get::<TargetChannel>()
|
||
.expect(EXPECTED_SOURCE_CHANNEL)
|
||
.lock()
|
||
.await
|
||
{
|
||
target
|
||
} else {
|
||
return;
|
||
};
|
||
let channel_name =
|
||
if let Ok(Channel::Guild(gc)) = new_message.channel_id.to_channel(&ctx.http).await {
|
||
if gc.kind == ChannelType::PrivateThread {
|
||
return;
|
||
}
|
||
gc.name
|
||
} else {
|
||
return;
|
||
};
|
||
let msg = target
|
||
.send_message(&ctx.http, |m| {
|
||
m.embed(|e| {
|
||
let e = create_embed![e, new_message, channel_name];
|
||
e
|
||
});
|
||
m
|
||
})
|
||
.await
|
||
.expect("Failed to send message");
|
||
let sent = data.get::<Sent>().expect("Expected Sent in TypeMap");
|
||
(*sent.lock().await).insert((source, new_message.id), (target, msg.id));
|
||
}
|
||
})
|
||
}
|
||
|
||
fn message_delete<'life0, 'async_trait>(
|
||
&'life0 self,
|
||
ctx: Context,
|
||
event_channel_id: ChannelId,
|
||
deleted_message_id: serenity::model::id::MessageId,
|
||
_guild_id: Option<GuildId>,
|
||
) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
where
|
||
'life0: 'async_trait,
|
||
Self: 'async_trait,
|
||
{
|
||
Box::pin(async move {
|
||
let data = ctx.data.read().await;
|
||
let sent = data
|
||
.get::<Sent>()
|
||
.expect("Expected Sent in TypeMap")
|
||
.clone();
|
||
if let Some((channel_id, message_id)) =
|
||
(*sent.lock().await).remove(&(event_channel_id, deleted_message_id))
|
||
{
|
||
ctx.http
|
||
.delete_message(channel_id.into(), message_id.into())
|
||
.await
|
||
.expect("Failed to delete message");
|
||
};
|
||
})
|
||
}
|
||
|
||
fn message_delete_bulk<'life0, 'async_trait>(
|
||
&'life0 self,
|
||
ctx: Context,
|
||
event_channel_id: ChannelId,
|
||
multiple_deleted_messages_ids: Vec<serenity::model::id::MessageId>,
|
||
_guild_id: Option<GuildId>,
|
||
) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
where
|
||
'life0: 'async_trait,
|
||
Self: 'async_trait,
|
||
{
|
||
Box::pin(async move {
|
||
let data = ctx.data.read().await;
|
||
let sent = data.get::<Sent>().expect("Expected Sent in TypeMap");
|
||
for deleted_message_id in multiple_deleted_messages_ids {
|
||
if let Some((channel_id, message_id)) =
|
||
(*sent.lock().await).remove(&(event_channel_id, deleted_message_id))
|
||
{
|
||
ctx.http
|
||
.delete_message(channel_id.into(), message_id.into())
|
||
.await
|
||
.expect("Failed to delete message");
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
fn message_update<'life0, 'async_trait>(
|
||
&'life0 self,
|
||
ctx: Context,
|
||
_old_if_available: Option<Message>,
|
||
new: Option<Message>,
|
||
event: serenity::model::event::MessageUpdateEvent,
|
||
) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
where
|
||
'life0: 'async_trait,
|
||
Self: 'async_trait,
|
||
{
|
||
Box::pin(async move {
|
||
let data = ctx.data.read().await;
|
||
let sent = data
|
||
.get::<Sent>()
|
||
.expect("Expected Sent in TypeMap")
|
||
.clone();
|
||
let channel_name = if let Some(nym) = event.channel_id.name(&ctx).await {
|
||
nym
|
||
} else {
|
||
return;
|
||
};
|
||
if let Some(new) = new {
|
||
if let Some((channel_id, message_id)) =
|
||
(*sent.lock().await).get(&(event.channel_id, event.id))
|
||
{
|
||
channel_id
|
||
.edit_message(ctx.http, *message_id, |m| {
|
||
m.embed(|e| {
|
||
let e = create_embed![e, new, channel_name];
|
||
e
|
||
});
|
||
m
|
||
})
|
||
.await
|
||
.expect("Failed to delete message");
|
||
}
|
||
}
|
||
})
|
||
}
|
||
|
||
// fn ready<'life0, 'async_trait>(
|
||
// &'life0 self,
|
||
// _ctx: Context,
|
||
// _data_about_bot: serenity::model::prelude::Ready,
|
||
// ) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
// where
|
||
// 'life0: 'async_trait,
|
||
// Self: 'async_trait,
|
||
// {
|
||
// Box::pin(async move {
|
||
// let __self = self;
|
||
// let _ctx = _ctx;
|
||
// let _data_about_bot = _data_about_bot;
|
||
// let _: () = {};
|
||
// })
|
||
// }
|
||
|
||
// fn thread_create<'life0, 'async_trait>(
|
||
// &'life0 self,
|
||
// _ctx: Context,
|
||
// _thread: serenity::model::channel::GuildChannel,
|
||
// ) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
// where
|
||
// 'life0: 'async_trait,
|
||
// Self: 'async_trait,
|
||
// {
|
||
// Box::pin(async move {
|
||
// let __self = self;
|
||
// let _ctx = _ctx;
|
||
// let _thread = _thread;
|
||
// let _: () = {};
|
||
// })
|
||
// }
|
||
|
||
// fn thread_delete<'life0, 'async_trait>(
|
||
// &'life0 self,
|
||
// _ctx: Context,
|
||
// _thread: serenity::model::channel::PartialGuildChannel,
|
||
// ) -> core::pin::Pin<Box<dyn core::future::Future<Output = ()> + core::marker::Send + 'async_trait>>
|
||
// where
|
||
// 'life0: 'async_trait,
|
||
// Self: 'async_trait,
|
||
// {
|
||
// Box::pin(async move {
|
||
// let __self = self;
|
||
// let _ctx = _ctx;
|
||
// let _thread = _thread;
|
||
// let _: () = {};
|
||
// })
|
||
// }
|
||
}
|