use serde::de::DeserializeOwned; use serenity::model::channel::Message; use serenity::model::gateway::{Activity, Ready}; use serenity::model::id::ChannelId; use serenity::prelude::*; use std::fmt; mod apt; mod aur; mod dnf; mod man; mod nix; mod pacman; mod xbps; extern crate reqwest; pub struct Handler; pub type CommandHandler = dyn Fn(Context, Message, Vec<&str>); struct Command { trigger: String, handler: Box, } unsafe impl Sync for Command {} const PREFIX: char = '!'; impl EventHandler for Handler { fn message(&self, ctx: Context, msg: Message) { if msg.content.starts_with(PREFIX) { if let Some(command) = COMMANDS .iter() .find(|&c| msg.content.starts_with(&c.trigger)) { let content = msg.content.clone(); let mut args = content.split_whitespace().collect::>(); // the prefix + trigger args.remove(0); if args.is_empty() { send( msg.channel_id, "Error: expected at least 1 additional argument", &ctx, ); return; } (*command.handler)(ctx, msg, args); }; } } fn ready(&self, ctx: Context, bot_data: Ready) { println!("Bot running and serving {} servers", bot_data.guilds.len()); ctx.reset_presence(); ctx.set_activity(Activity::listening(&format!("{}pb help", PREFIX))); } } fn help(ctx: Context, msg: Message, _args: Vec<&str>) { let command_triggers = COMMANDS .iter() .filter(|&c| !c.trigger.contains("help")) .map(|c| c.trigger.clone()) .collect::>() .join(", "); send(msg.channel_id, &format!("Usage: ` `\nSupported commands: `{}`\nSource code & information: https://git.kageru.moe/kageru/pacbot", command_triggers), &ctx) } lazy_static! { static ref COMMANDS: Vec = { let mut command_list = Vec::new(); command_list.push(Command::new("pacman", pacman::query_pacman)); command_list.push(Command::new("apt", apt::query_apt)); // disabled because they seem to no longer have stable json urls // command_list.push(Command::new("nix", nix::query_nix)); command_list.push(Command::new("dnf", dnf::query_dnf)); command_list.push(Command::new("aur", aur::query_aur)); command_list.push(Command::new("xbps", xbps::query_xbps)); command_list.push(Command::new("man", man::query_man)); command_list.push(Command::new("pacbot help", help)); command_list.push(Command::new("pb help", help)); command_list }; } impl Command { pub fn new(trigger: &str, handler: fn(Context, Message, Vec<&str>)) -> Self { let mut trigger_with_prefix = trigger.to_string(); trigger_with_prefix.insert(0, PREFIX); Self { trigger: trigger_with_prefix, handler: Box::new(handler), } } } pub fn send(target: ChannelId, message: &str, ctx: &Context) { if let Err(cause) = target.say(&ctx.http, message) { println!("Could not send message: {}", cause); } } pub fn respond_with_results(target: ChannelId, results: &[T], ctx: &Context) { send( target, &format!( "```{}```", results .iter() .take(5) .map(|p| format!("{}\n", p)) .collect::() ), ctx, ); } pub fn search(url: &str, fallback: impl Fn(reqwest::Error) -> T) -> T { search_inner(url).unwrap_or_else(|e| { println!("{:?}", e); fallback(e) }) } fn search_inner(url: &str) -> Result { let resp = reqwest::get(url)?.json::()?; Ok(resp) }