mpsc_bot/src/commands.rs

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(())
}