From 1fc46f84d9d9ea3efcfa5aaf6cac90dae8e9c8b4 Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Wed, 14 Feb 2018 00:48:06 -0500 Subject: initial commands implemented --- src/commands.rs | 257 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/main.rs | 70 +++++++++------ src/util.rs | 20 +++++ 3 files changed, 316 insertions(+), 31 deletions(-) diff --git a/src/commands.rs b/src/commands.rs index e802bde..ae8ae25 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,6 +1,251 @@ -//use serenity::prelude::*; -//use serenity::framework::StandardFramework; -// -//pub fn register_commands(mut f: StandardFramework) -> StandardFramework { -// f -//} +use std::sync::{Arc, Mutex as SMutex}; +use std::collections::VecDeque; + +use serenity::prelude::*; +use serenity::client::bridge::voice::ClientVoiceManager; +use serenity::framework::StandardFramework; +use serenity::model::channel::GuildChannel; +use serenity::voice::LockedAudio; + +use typemap::Key; + +use {Result, TARGET_GUILD_ID}; + +pub struct VoiceManager; + +impl Key for VoiceManager { + type Value = Arc>; +} + +impl VoiceManager { + pub fn register(c: &mut Client) { + let mut data = c.data.lock(); + data.insert::(Arc::clone(&c.voice_manager)); + } +} + +struct PlayArgs { + url: String, + initiator: String, +} + +struct CurrentItem { + init_args: PlayArgs, + audio: Option, +} + +pub struct PlayQueue { + queue: VecDeque, + playing: Option, +} + +impl Key for PlayQueue { + type Value = Arc>; +} + +impl PlayQueue { + fn new() -> Self { + PlayQueue { + queue: VecDeque::new(), + playing: None, + } + } + + pub fn register(c: &mut Client) { + let mut data = c.data.lock(); + data.insert::(Arc::new(SMutex::new(PlayQueue::new()))); + } + + fn advance(&mut self) { + self.queue.pop_front().map(|info| { + self.playing = Some(CurrentItem { + init_args: info, + audio: None, + }); + }); + } +} + +fn send(channel: &GuildChannel, text: &str, tts: bool) -> Result<()> { + channel.send_message(|m| m.content(text).tts(tts))?; + Ok(()) +} + +pub fn register_commands(f: StandardFramework) -> StandardFramework { + f + .cmd("skip", skip) + .cmd("pause", pause) + .cmd("resume", resume) + .cmd("list", list) + .cmd("die", die) + .cmd("meme", meme) + .cmd("mute", mute) + .cmd("unmute", unmute) +} + +command!(skip(ctx, _msg) { + let data = ctx.data.lock(); + + let mut mgr_lock = data.get::().cloned().unwrap(); + let mut manager = mgr_lock.lock(); + + let mut queue_lock = data.get::().cloned().unwrap(); + let mut play_queue = queue_lock.lock().unwrap(); + + if let Some(handler) = manager.get_mut(*TARGET_GUILD_ID) { + handler.stop(); + play_queue.advance(); + } else { + debug!("got skip with no handler attached"); + } +}); + +command!(pause(ctx, msg) { + let mut queue_lock = ctx.data.lock().get::().cloned().unwrap(); + let mut play_queue = queue_lock.lock().unwrap(); + + let channel_tmp = msg.channel().unwrap().guild().unwrap(); + let channel = channel_tmp.read(); + + let done = || send(&channel, "r u srs", msg.tts); + + let current_item = match play_queue.playing { + Some(ref x) => x, + None => { + done()?; + return Ok(()); + }, + }; + + let locked_audio = match current_item.audio { + Some(ref x) => x, + None => { + done()?; + return Ok(()); + }, + }; + + let mut audio = locked_audio.lock(); + + if !audio.playing { + done()?; + return Ok(()); + } + + audio.pause(); +}); + +command!(resume(ctx, msg) { + let mut queue_lock = ctx.data.lock().get::().cloned().unwrap(); + let mut play_queue = queue_lock.lock().unwrap(); + + let channel_tmp = msg.channel().unwrap().guild().unwrap(); + let channel = channel_tmp.read(); + + let done = || send(&channel, "r u srs", msg.tts); + + let current_item = match play_queue.playing { + Some(ref x) => x, + None => { + done()?; + return Ok(()); + }, + }; + + let locked_audio = match current_item.audio { + Some(ref x) => x, + None => { + done()?; + return Ok(()); + }, + }; + + let mut audio = locked_audio.lock(); + + if audio.playing { + done()?; + return Ok(()); + } + + audio.play(); +}); + +command!(die(ctx, msg) { + let data = ctx.data.lock(); + + let mut mgr_lock = data.get::().cloned().unwrap(); + let mut manager = mgr_lock.lock(); + + let mut queue_lock = data.get::().cloned().unwrap(); + let mut play_queue = queue_lock.lock().unwrap(); + + let channel_tmp = msg.channel().unwrap().guild().unwrap(); + let channel = channel_tmp.read(); + + play_queue.playing = None; + play_queue.queue.clear(); + + if let Some(handler) = manager.get_mut(*TARGET_GUILD_ID) { + handler.stop(); + } else { + send(&channel, "YOU die", msg.tts)?; + debug!("got die with no handler attached"); + } +}); + +command!(list(ctx, msg) { + let mut queue_lock = ctx.data.lock().get::().cloned().unwrap(); + let mut play_queue = queue_lock.lock().unwrap(); + + let channel_tmp = msg.channel().unwrap().guild().unwrap(); + let channel = channel_tmp.read(); + + match play_queue.playing { + Some(ref info) => { + send(&channel, &format!("Currently playing {} ({})", info.init_args.url, info.init_args.initiator), msg.tts)?; + }, + None => { + debug!("`list` called with no items in queue"); + send(&channel, "Nothing is playing you fucking meme", msg.tts)?; + return Ok(()); + }, + } + + play_queue.queue.iter().for_each(|info| { + channel.say(&format!("{} ({})", info.url, info.initiator)).unwrap(); + }); +}); + +command!(meme(_ctx, _msg) { + +}); + +command!(mute(ctx, _msg) { + let mut mgr_lock = ctx.data.lock().get::().cloned().unwrap(); + let mut manager = mgr_lock.lock(); + + manager.get_mut(*TARGET_GUILD_ID) + .map(|handler| { + if handler.self_mute { + trace!("Already muted.") + } else { + handler.mute(true); + trace!("Muted"); + } + }); +}); + +command!(unmute(ctx, _msg) { + let mut mgr_lock = ctx.data.lock().get::().cloned().unwrap(); + let mut manager = mgr_lock.lock(); + + manager.get_mut(*TARGET_GUILD_ID) + .map(|handler| { + if !handler.self_mute { + trace!("Already unmuted.") + } else { + handler.mute(false); + trace!("Unmuted"); + } + }); +}); \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 58e059e..007aae9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#![feature(rustc_private)] + #[macro_use] extern crate serenity; #[macro_use] extern crate log; #[macro_use] extern crate error_chain; @@ -7,44 +9,50 @@ extern crate dotenv; extern crate simple_logger; extern crate typemap; extern crate url; +extern crate parking_lot; mod commands; mod util; +use std::env; +use std::thread; +use std::time::{Duration, Instant}; + +use serenity::prelude::*; +use serenity::framework::StandardFramework; +use serenity::framework::standard::help_commands; +use serenity::model::gateway::Ready; +use serenity::model::id::{UserId, GuildId}; + +use dotenv::dotenv; + +use commands::register_commands; + mod errors { error_chain! { foreign_links { Serenity(::serenity::Error); - OS(::std::env::VarError); + MissingVar(::std::env::VarError); } } } use errors::*; - pub use util::*; -use std::env; -use std::collections::HashSet; -use std::thread; -use std::time::{Duration, Instant}; - -use serenity::prelude::*; -use serenity::framework::StandardFramework; -use serenity::framework::standard::help_commands; -use serenity::model::gateway::Ready; - -use dotenv::dotenv; +lazy_static! { + static ref TARGET_GUILD: u64 = must_env_lookup::("TARGET_GUILD"); + static ref TARGET_GUILD_ID: GuildId = GuildId(*TARGET_GUILD); +} struct Handler; impl EventHandler for Handler { - fn ready(&self, _c: Context, r: Ready) { - let guild = r.guilds.iter().find(|g| { - g.id().0 == 0 - }); + fn ready(&self, _: Context, r: Ready) { + let guild = r.guilds.iter() + .find(|g| g.id().0 == *TARGET_GUILD); if guild.is_none() { - info!("bot isn't in configured guild. let it join here: {:?}", OAUTH_URL.as_str()); + info!("bot isn't in configured guild. join here: {:?}", OAUTH_URL.as_str()); } } } @@ -52,25 +60,30 @@ impl EventHandler for Handler { fn run() -> Result<()> { let token = &env::var("THULANI_TOKEN")?; let mut client = Client::new(token, Handler)?; - let framework = StandardFramework::new() + + commands::VoiceManager::register(&mut client); + commands::PlayQueue::register(&mut client); + + let owner_id = must_env_lookup::("OWNER_ID"); + let mut framework = StandardFramework::new() .configure(|c| c .allow_dm(false) .allow_whitespace(true) .prefixes(vec!["!thulani ", "!thulan ", "!thulando madando ", "!thulando "]) .ignore_bots(true) .on_mention(false) - .owners(HashSet::new()) + .owners(vec![UserId(owner_id)].into_iter().collect()) .case_insensitivity(true) ) .before(|_ctx, message, cmd| { debug!("got command {} from user '{}' ({})", cmd, message.author.name, message.author.id); - - true + + message.guild_id().map_or(false, |x| x.0 == *TARGET_GUILD) }) - .after(|_ctx, _msg, cmd, err| { + .after(|_ctx, _msg, _cmd, err| { match err { Ok(()) => {}, - Err(e) => { + Err(_e) => { } @@ -81,6 +94,8 @@ fn run() -> Result<()> { c }); + framework = register_commands(framework); + client.with_framework(framework); client.start()?; @@ -107,9 +122,14 @@ fn main() { Err(e) => { error!("error encountered running client: {}", e); e.iter().skip(1).for_each(|e| { - + error!("caused by: {}", e); }); + if let Some(bt) = e.backtrace() { + error!("backtrace: {:?}", bt); + } + + ::std::process::exit(1); }, _ => { warn!("somehow `run` completed without an error. should probably take a look at this."); diff --git a/src/util.rs b/src/util.rs index 6f70037..54f10ee 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,11 @@ use std::env; +use std::str::FromStr; use serenity::model::permissions::Permissions; +use serenity::model::id::GuildId; +use serenity::model::channel::Message; +use serenity::client::CACHE; + use url::Url; lazy_static! { @@ -26,3 +31,18 @@ lazy_static! { ) ).unwrap(); } + +pub fn must_env_lookup(s: &str) -> T { + env::var(s).expect(&format!("missing env var {}", s)) + .parse::().unwrap_or_else(|_| panic!(format!("bad format for {}", s))) +} + +pub trait GuildLookup { + fn guild_id(&self) -> Option; +} + +impl GuildLookup for Message { + fn guild_id(&self) -> Option { + CACHE.read().guild_channel(self.channel_id).map(|c| c.read().guild_id) + } +} \ No newline at end of file -- cgit v1.3.1