#![feature(once_cell)] mod config; use serenity::framework::StandardFramework; use serenity::model::id::ChannelId; use serenity::Client; use std::lazy::SyncLazy; use std::sync::{Arc, Mutex}; fn configure(conf: &mut serenity::framework::standard::Configuration) { conf.prefix(config::GLOBAL_COMMAND_PREFIX) .with_whitespace((true, true, true)) .case_insensitivity(true); } struct Handler; #[derive(Debug, Clone)] pub enum BotError { MissingGuild, } impl std::fmt::Display for BotError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { Self::MissingGuild => write!(f, "this command is only usable in a guild"), } } } impl std::error::Error for BotError {} #[derive(Debug, Clone, Default)] pub struct ChannelCache { pub meta: Option, pub lobby: Option, } static CHANNEL_CACHE: SyncLazy>> = SyncLazy::new(|| Arc::new(Mutex::new(Default::default()))); mod commands { use super::{ChannelCache, CHANNEL_CACHE}; use crate::config; use serenity::client::Context; use serenity::framework::standard::{macros::*, CommandError, CommandResult}; use serenity::model::{ channel::{Channel, ChannelType, GuildChannel, Message}, guild::Guild, id::ChannelId, }; #[group] #[description("Commands for this bot")] #[commands(help, create)] struct Group; #[derive(Debug, Clone)] struct ChannelDescriptor<'n, 'd> { name: &'n str, description: &'d str, kind: ChannelType, parent: Option, } async fn get_guild(ctx: &Context, msg: &Message) -> Result { msg.guild(&ctx.cache) .await .ok_or_else(|| Box::new(super::BotError::MissingGuild).into()) } async fn create_channel_no_cache<'n, 'd>( ctx: &Context, guild: &Guild, desc: ChannelDescriptor<'n, 'd>, ) -> Result { Ok(guild .create_channel(ctx, |c| { let c = c.name(desc.name).topic(desc.description).kind(desc.kind); if let Some(parent) = desc.parent { c.category(parent) } else { c } }) .await?) } async fn get_guild_channel_by_optional_id( ctx: &Context, id: Option, ) -> Option { match id { Some(id) => ctx.cache.guild_channel(id).await, None => None, } } async fn get_channel_by_optional_id( ctx: &Context, kind: ChannelType, id: Option, ) -> Option { match (kind, id) { (ChannelType::Category, Some(id)) => ctx .cache .categories() .await .get(&id) .cloned() .map(Channel::Category), (_, opt_id) => get_guild_channel_by_optional_id(ctx, opt_id) .await .map(Channel::Guild), } } async fn create_channel<'n, 'd>( ctx: &Context, guild: &Guild, desc: ChannelDescriptor<'n, 'd>, ) -> Result { let optional_channel = match desc.kind { ChannelType::Category => { println!("{:?}", ctx.cache.categories().await); ctx.cache.categories().await.values().find_map(|channel| { if channel.name() == desc.name { Some(Channel::Category(channel.clone())) } else { None } }) } _ => get_guild_channel_by_optional_id( ctx, guild.channel_id_from_name(&ctx.cache, desc.name).await, ) .await .map(Channel::Guild), }; match optional_channel { Some(channel) => Ok(channel), None => { let channel = create_channel_no_cache(ctx, guild, desc) .await .map(Channel::Guild); channel } } } async fn get_channel_or_create<'n, 'd, F: Fn(&mut ChannelCache) -> &mut Option>( ctx: &Context, guild: &Guild, desc: ChannelDescriptor<'n, 'd>, f: F, ) -> Result { let lock = || CHANNEL_CACHE.lock().unwrap(); Ok({ let optional_id = *f(&mut *lock()); match get_channel_by_optional_id(ctx, desc.kind, optional_id).await { Some(channel) => channel, None => { let channel = create_channel(ctx, &guild, desc).await?; *f(&mut *lock()) = Some(channel.id()); channel } } }) } async fn get_meta_channel_or_create( ctx: &Context, guild: &Guild, ) -> Result { let desc = ChannelDescriptor { name: config::META_CHANNEL_NAME, description: config::META_CHANNEL_DESCRIPTION, kind: ChannelType::Category, parent: None, }; get_channel_or_create(ctx, guild, desc, |cache| &mut cache.meta).await } async fn get_lobby_channel_or_create( ctx: &Context, guild: &Guild, meta_channel_id: ChannelId, ) -> Result { let desc = ChannelDescriptor { name: config::LOBBY_CHANNEL_NAME, description: config::LOBBY_CHANNEL_DESCRIPTION, kind: ChannelType::Voice, parent: Some(meta_channel_id), }; get_channel_or_create(ctx, guild, desc, |cache| &mut cache.lobby).await } #[command] #[aliases("hepl", "?", "h")] async fn help(ctx: &Context, msg: &Message) -> CommandResult { msg.reply(ctx, crate::config::HELP_TEXT).await?; Ok(()) } #[command] #[aliases("craete", "+", "c", "init")] async fn create(ctx: &Context, msg: &Message) -> CommandResult { let guild = get_guild(ctx, msg).await?; let meta_channel = get_meta_channel_or_create(ctx, &guild).await?; let lobby_channel = get_lobby_channel_or_create(ctx, &guild, meta_channel.id()).await?; Ok(()) } #[command] #[aliases("strat", "s", "r", "run", "rnu")] async fn start(ctx: &Context, msg: &Message) -> CommandResult { let guild = get_guild(ctx, msg).await?; Ok(()) } } impl serenity::client::EventHandler for Handler {} #[tokio::main] async fn main() { println!("crate framework"); let framework = StandardFramework::new() .configure(|c| { configure(c); c }) .group(&commands::GROUP_GROUP); let token = std::env::var(config::TOKEN_ENV).expect("missing token"); println!("crate a new client with token: \"{}\"", token); let mut client = Client::new(token) .framework(framework) .event_handler(Handler) .await .expect("Error creating client"); println!("starting the client"); client.start().await.expect("client error"); }