467 lines
14 KiB
Rust
467 lines
14 KiB
Rust
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(())
|
|
}
|