Initial commit.
commit
6df6577967
|
@ -0,0 +1 @@
|
|||
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "mpsc_bot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serenity = "0.10"
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
serde_json = "*"
|
|
@ -0,0 +1,18 @@
|
|||
*A bot for copying a channel's messages, optionally including those in threads, to another channel.*
|
||||
**Commands:**
|
||||
Gives information on how to use this bot's commands.
|
||||
`help`
|
||||
Sets which channels to use as source or target.
|
||||
`set (source | s | target | t) <channel id> [(source | s | target | t) <channel id>]`
|
||||
Tells the bot whether or not to copy from threads. Default is true.
|
||||
`weave (false | f | true | t)`
|
||||
Sets source and target to `None`.
|
||||
`reset`
|
||||
Deletes info messages sent by this bot and command messages issued by users.
|
||||
`clean`
|
||||
Deletes all messages sent by this bot.
|
||||
`flush`
|
||||
Stops the bot.
|
||||
`stop`
|
||||
Begins the bot.
|
||||
`begin`
|
|
@ -0,0 +1,466 @@
|
|||
use std::{str::FromStr, sync::atomic::Ordering};
|
||||
|
||||
use serenity::{
|
||||
client::Context,
|
||||
framework::standard::{
|
||||
macros::{command, group},
|
||||
Args, CommandResult,
|
||||
},
|
||||
model::{channel::Message, id::ChannelId, misc::ChannelIdParseError},
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
|
||||
use crate::track::{Active, InfoSent, Sent, SourceChannel, TargetChannel, Weave};
|
||||
|
||||
use crate::err_strs::*;
|
||||
|
||||
#[group]
|
||||
#[commands(set, stop, begin, clean, flush, help, reset, weave)]
|
||||
pub struct General;
|
||||
|
||||
const SET_USAGE: &str =
|
||||
"Usage: `set (source | target) <channel id> [(source | target) <channel id>]`";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum SetArg {
|
||||
Source,
|
||||
Target,
|
||||
Id(ChannelId),
|
||||
Unset,
|
||||
}
|
||||
|
||||
impl SetArg {
|
||||
pub fn get(self) -> Option<ChannelId> {
|
||||
match self {
|
||||
Self::Id(id) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SetArg {
|
||||
type Err = ChannelIdParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "source" || s == "s" {
|
||||
Ok(Self::Source)
|
||||
} else if s == "target" || s == "t" {
|
||||
Ok(Self::Target)
|
||||
} else {
|
||||
Ok(Self::Id(ChannelId::from_str(s)?))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
async fn help(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(
|
||||
ctx,
|
||||
"*A bot for copying a channel's messages, optionally including those in threads, to another channel.*
|
||||
**Commands:**
|
||||
Gives information on how to use this bot's commands.
|
||||
`help`
|
||||
Sets which channels to use as source or target.
|
||||
`set (source | s | target | t) <channel id> [(source | s | target | t) <channel id>]`
|
||||
Tells the bot whether or not to copy from threads. Default is true.
|
||||
`weave (false | f | true | t)`
|
||||
Sets source and target to `None`.
|
||||
`reset`
|
||||
Deletes info messages sent by this bot and command messages issued by users.
|
||||
`clean`
|
||||
Deletes all messages sent by this bot.
|
||||
`flush`
|
||||
Stops the bot.
|
||||
`stop`
|
||||
Begins the bot.
|
||||
`begin`",
|
||||
)
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[usage(SET_USAGE)]
|
||||
/// Sets the channel that MPSC Bot will clone messages from.
|
||||
/// If a channel id is given, will compile all thread messages into the target channel.
|
||||
async fn set(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
if args.len() > 4 {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "Too many arguments.").await?.id,
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
if args.len() % 2 == 1 {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "Not enough arguments.").await?.id,
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
use SetArg::*;
|
||||
let mut arg_frame: [SetArg; 4] = [Unset, Unset, Unset, Unset];
|
||||
|
||||
for (n, arg) in args.iter::<SetArg>().enumerate() {
|
||||
if let Ok(arg) = arg {
|
||||
arg_frame[n] = arg;
|
||||
} else {
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(ctx, SET_USAGE).await?.id));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
match arg_frame {
|
||||
[Source, source @ Id(_), Unset | Target, target]
|
||||
| [Target, target @ Id(_), Unset | Source, source] => {
|
||||
if let Some(source) = source.get() {
|
||||
let ch = data.get::<SourceChannel>().expect(EXPECTED_SOURCE_CHANNEL);
|
||||
*ch.lock().await = Some(source);
|
||||
}
|
||||
if let Some(target) = target.get() {
|
||||
let ch = data.get::<TargetChannel>().expect(EXPECTED_TARGET_CHANNEL);
|
||||
*ch.lock().await = Some(target);
|
||||
}
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(
|
||||
ctx,
|
||||
match (
|
||||
*data
|
||||
.get::<SourceChannel>()
|
||||
.expect(EXPECTED_SOURCE_CHANNEL)
|
||||
.lock()
|
||||
.await,
|
||||
*data
|
||||
.get::<TargetChannel>()
|
||||
.expect(EXPECTED_SOURCE_CHANNEL)
|
||||
.lock()
|
||||
.await,
|
||||
) {
|
||||
(None, None) => MessageBuilder::new()
|
||||
.push("Source channel: None\nTarget Channel: None")
|
||||
.build(),
|
||||
(None, Some(target)) => MessageBuilder::new()
|
||||
.push("Source channel: None\nTarget channel: ")
|
||||
.mention(&target)
|
||||
.build(),
|
||||
(Some(source), None) => MessageBuilder::new()
|
||||
.push("Source channel: ")
|
||||
.mention(&source)
|
||||
.push("\nTarget channel: None")
|
||||
.build(),
|
||||
(Some(source), Some(target)) => MessageBuilder::new()
|
||||
.push("Source channel: ")
|
||||
.mention(&source)
|
||||
.push("\nTarget channel: ")
|
||||
.mention(&target)
|
||||
.build(),
|
||||
},
|
||||
)
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
}
|
||||
[Id(_), ..] => {
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(
|
||||
ctx,
|
||||
"Please specify if source or target channel: `/set source <channel>` or `/set target <channel>`",
|
||||
)
|
||||
.await?.id));
|
||||
}
|
||||
[Source, Target, Unset, Unset] | [Target, Source, Unset, Unset] => {
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(ctx, "? what?").await?.id));
|
||||
}
|
||||
[Source, Target, ..] | [Target, Source, ..] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "Please specify a channel.").await?.id,
|
||||
));
|
||||
}
|
||||
[Target, Id(_), Target, Id(_)] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(
|
||||
ctx,
|
||||
"Uhh, you feelin' okay there, bud? You specified target *twice*.",
|
||||
)
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
}
|
||||
[Target, Target, Target, Target] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "You really need to stop. You're wasting CPU cycles.")
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
}
|
||||
[Target, Target, ..] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "This just makes no sense. Why would you do this?")
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
}
|
||||
[Source, Id(_), Source, Id(_)] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(
|
||||
ctx,
|
||||
"Uhh, you feelin' okay there, bud? You specified source twice.",
|
||||
)
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
}
|
||||
[Source, Source, Source, Source] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "Okay what's going on here?").await?.id,
|
||||
));
|
||||
}
|
||||
[Source, Source, ..] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "Seriously, why would you do this?")
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
}
|
||||
[Source, Unset, Unset, Unset] | [Target, Unset, Unset, Unset] => {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "Please specify a channel.").await?.id,
|
||||
));
|
||||
}
|
||||
[Source, Unset, Target, _]
|
||||
| [Source, Unset, Source, _]
|
||||
| [Source, Unset, Id(_), _]
|
||||
| [Source, Unset, _, Target]
|
||||
| [Source, Unset, _, Source]
|
||||
| [Source, Unset, _, Id(_)]
|
||||
| [Target, Unset, Target, _]
|
||||
| [Target, Unset, Source, _]
|
||||
| [Target, Unset, Id(_), _]
|
||||
| [Target, Unset, _, Target]
|
||||
| [Target, Unset, _, Source]
|
||||
| [Target, Unset, _, Id(_)] => unreachable![],
|
||||
_ => {
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(ctx, SET_USAGE).await?.id));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[usage("reset")]
|
||||
/// Sets source and target channels to `None`.
|
||||
async fn reset(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
{
|
||||
let ch = data.get::<SourceChannel>().expect(EXPECTED_SOURCE_CHANNEL);
|
||||
*ch.lock().await = None;
|
||||
}
|
||||
{
|
||||
let ch = data.get::<TargetChannel>().expect(EXPECTED_TARGET_CHANNEL);
|
||||
*ch.lock().await = None;
|
||||
}
|
||||
{
|
||||
let active = data.get::<Active>().expect(EXPECTED_TARGET_CHANNEL);
|
||||
active.store(false, Ordering::Relaxed);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[usage("clean")]
|
||||
/// Deletes all messages sent by this bot.
|
||||
async fn clean(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
for (channel_id, message_id) in info_sent.lock().await.iter() {
|
||||
ctx.http
|
||||
.delete_message((*channel_id).into(), (*message_id).into())
|
||||
.await?;
|
||||
}
|
||||
(*info_sent.lock().await).clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[usage("flush")]
|
||||
/// Deletes all messages sent by this bot.
|
||||
async fn flush(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
{
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
for (channel_id, message_id) in info_sent.lock().await.iter() {
|
||||
ctx.http
|
||||
.delete_message((*channel_id).into(), (*message_id).into())
|
||||
.await?;
|
||||
}
|
||||
(*info_sent.lock().await).clear();
|
||||
}
|
||||
{
|
||||
let sent = data.get::<Sent>().expect("Expected Sent in TypeMap.");
|
||||
for (_, (channel_id, message_id)) in sent.lock().await.iter() {
|
||||
ctx.http
|
||||
.delete_message((*channel_id).into(), (*message_id).into())
|
||||
.await?;
|
||||
}
|
||||
(*sent.lock().await).clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[num_args(0)]
|
||||
#[usage("stop")]
|
||||
/// Stops the bot.
|
||||
async fn stop(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
let active = data.get::<Active>().expect(EXPECTED_ACTIVE);
|
||||
|
||||
if !active.load(Ordering::Relaxed) {
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(ctx, "Not started.").await?.id));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(ctx, "Stopping...").await?.id));
|
||||
|
||||
active.store(false, Ordering::Relaxed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[num_args(0)]
|
||||
#[usage("begin")]
|
||||
/// Begins the bot.
|
||||
async fn begin(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.id));
|
||||
if let Some(target) = data.get::<TargetChannel>() {
|
||||
if (*target.lock().await).is_none() {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(
|
||||
ctx,
|
||||
"Target channel must be specified first. Try specifying one using the `~set` command.",
|
||||
)
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
if let Some(source) = data.get::<SourceChannel>() {
|
||||
if (*source.lock().await).is_none() {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(
|
||||
ctx,
|
||||
"Source channel must be specified first. Try specifying one using the `~set` command.",
|
||||
)
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
(*info_sent.lock().await).push((msg.channel_id, msg.reply(ctx, "Starting...").await?.id));
|
||||
|
||||
let active = data.get::<Active>().expect(EXPECTED_ACTIVE);
|
||||
|
||||
active.store(true, Ordering::Relaxed);
|
||||
|
||||
for (channel_id, message_id) in info_sent.lock().await.iter() {
|
||||
ctx.http
|
||||
.delete_message((*channel_id).into(), (*message_id).into())
|
||||
.await?;
|
||||
}
|
||||
(*info_sent.lock().await).clear();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
#[owners_only]
|
||||
#[num_args(1)]
|
||||
#[usage("weave (false | f | true | t)")]
|
||||
/// Tells the bot whether or not to copy from threads. Default is true.
|
||||
async fn weave(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let data = ctx.data.read().await;
|
||||
let info_sent = data
|
||||
.get::<InfoSent>()
|
||||
.expect("Expected InfoSent in TypeMap.");
|
||||
for arg in args.iter::<String>() {
|
||||
let arg = arg.expect("Failed to read argument.");
|
||||
if arg == "false" || arg == "f" {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "I will not copy from threads.").await?.id,
|
||||
));
|
||||
let weave = data.get::<Weave>().expect(EXPECTED_ACTIVE);
|
||||
weave.store(false, Ordering::Relaxed);
|
||||
} else if arg == "true" || arg == "t" {
|
||||
(*info_sent.lock().await).push((
|
||||
msg.channel_id,
|
||||
msg.reply(ctx, "I will weave the threads into a beautiful tapestry.")
|
||||
.await?
|
||||
.id,
|
||||
));
|
||||
let weave = data.get::<Weave>().expect(EXPECTED_ACTIVE);
|
||||
weave.store(true, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,380 @@
|
|||
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(replied_to) = $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/{}/{}/{}",
|
||||
replied_to
|
||||
.guild_id
|
||||
.expect("Really unsure how we got here, but we're here."),
|
||||
replied_to.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 _: () = {};
|
||||
// })
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
#![feature(in_band_lifetimes)]
|
||||
#![feature(async_closure)]
|
||||
use serenity::client::Client;
|
||||
use serenity::framework::standard::StandardFramework;
|
||||
use serenity::futures::lock::Mutex;
|
||||
use serenity::http::Http;
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod commands;
|
||||
mod handler;
|
||||
mod track;
|
||||
|
||||
use commands::*;
|
||||
use handler::*;
|
||||
use track::{err_strs, Active, InfoSent, Sent, SourceChannel, TargetChannel, Weave};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let token = env::var("MPSC_BOT_TOKEN").expect("token");
|
||||
|
||||
let http = Http::new_with_token(&token);
|
||||
|
||||
let (owners, _) = match http.get_current_application_info().await {
|
||||
Ok(info) => {
|
||||
let mut owners = HashSet::new();
|
||||
if let Some(team) = info.team {
|
||||
owners.insert(team.owner_user_id);
|
||||
} else {
|
||||
owners.insert(info.owner.id);
|
||||
}
|
||||
match http.get_current_user().await {
|
||||
Ok(bot_id) => (owners, bot_id.id),
|
||||
Err(why) => panic!("Could not access the bot id: {:?}", why),
|
||||
}
|
||||
}
|
||||
Err(why) => panic!("Could not access application info: {:?}", why),
|
||||
};
|
||||
|
||||
let framework = StandardFramework::new()
|
||||
.configure(|c| {
|
||||
c.with_whitespace(true)
|
||||
.prefix("~")
|
||||
.owners(owners)
|
||||
.ignore_bots(true)
|
||||
})
|
||||
.group(&GENERAL_GROUP);
|
||||
|
||||
let mut client = Client::builder(token)
|
||||
.event_handler(Handler::default())
|
||||
.framework(framework)
|
||||
.await
|
||||
.expect("Error creating client");
|
||||
|
||||
{
|
||||
let mut data = client.data.write().await;
|
||||
data.insert::<Active>(Arc::new(AtomicBool::new(false)));
|
||||
data.insert::<InfoSent>(Arc::new(Mutex::new(Vec::new())));
|
||||
data.insert::<Sent>(Arc::new(Mutex::new(HashMap::new())));
|
||||
data.insert::<SourceChannel>(Arc::new(Mutex::new(None)));
|
||||
data.insert::<TargetChannel>(Arc::new(Mutex::new(None)));
|
||||
data.insert::<Weave>(Arc::new(AtomicBool::new(true)));
|
||||
}
|
||||
|
||||
// start listening for events by starting a single shard
|
||||
if let Err(why) = client.start().await {
|
||||
println!("An error occurred while running the client: {:?}", why);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use serenity::{
|
||||
futures::lock::Mutex,
|
||||
model::id::{ChannelId, MessageId},
|
||||
prelude::TypeMapKey,
|
||||
};
|
||||
|
||||
pub mod err_strs {
|
||||
pub const EXPECTED_SOURCE_CHANNEL: &str = "Expected SourceChannel in TypeMap";
|
||||
pub const EXPECTED_TARGET_CHANNEL: &str = "Expected TargetChannel in TypeMap";
|
||||
pub const EXPECTED_ACTIVE: &str = "Expected Active in TypeMap";
|
||||
}
|
||||
|
||||
pub struct Active;
|
||||
|
||||
impl TypeMapKey for Active {
|
||||
type Value = Arc<AtomicBool>;
|
||||
}
|
||||
|
||||
pub struct InfoSent;
|
||||
|
||||
impl TypeMapKey for InfoSent {
|
||||
type Value = Arc<Mutex<Vec<(ChannelId, MessageId)>>>;
|
||||
}
|
||||
|
||||
pub struct Sent;
|
||||
|
||||
impl TypeMapKey for Sent {
|
||||
type Value = Arc<Mutex<HashMap<(ChannelId, MessageId), (ChannelId, MessageId)>>>;
|
||||
}
|
||||
|
||||
pub struct SourceChannel;
|
||||
|
||||
impl TypeMapKey for SourceChannel {
|
||||
type Value = Arc<Mutex<Option<ChannelId>>>;
|
||||
}
|
||||
|
||||
pub struct TargetChannel;
|
||||
|
||||
impl TypeMapKey for TargetChannel {
|
||||
type Value = Arc<Mutex<Option<ChannelId>>>;
|
||||
}
|
||||
|
||||
pub struct Weave;
|
||||
|
||||
impl TypeMapKey for Weave {
|
||||
type Value = Arc<AtomicBool>;
|
||||
}
|
Loading…
Reference in New Issue