Initial commit.

pull/1/head
Elfein Landers 2022-01-24 10:33:09 -08:00
commit 6df6577967
8 changed files with 2302 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1302
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

11
Cargo.toml Normal file
View File

@ -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 = "*"

18
README.md Normal file
View File

@ -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`

466
src/commands.rs Normal file
View File

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

380
src/handler.rs Normal file
View File

@ -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 _: () = {};
// })
// }
}

72
src/main.rs Normal file
View File

@ -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);
}
}

52
src/track.rs Normal file
View File

@ -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>;
}