#[macro_use] extern crate serenity; extern crate dotenv; extern crate calc; extern crate ddg; extern crate regex; #[macro_use] extern crate lazy_static; use serenity::client::{Client, EventHandler}; use serenity::framework::standard::{StandardFramework, help_commands}; use serenity::model::{channel::Message, id::ChannelId}; use serenity::utils::Colour; use std::env; use std::fmt::Display; use regex::Regex; struct Handler; impl EventHandler for Handler {} pub fn main() { dotenv::dotenv().ok(); // Load bot token from environment, let mut client = Client::new(&env::var("DISCORD_TOKEN").expect("token unavailable"), Handler) .expect("Error creating client"); client.with_framework(StandardFramework::new() .configure(|c| c .prefixes(vec!["++", "$", ">"]) .allow_whitespace(true) .case_insensitivity(true) .on_mention(true)) .help(help_commands::with_embeds) .before(|_context, _message, command| { println!("Executing {}!", command); true }) .command("ping", |c| c.cmd(ping).desc("Says Pong.").known_as("test")) .command("search", |c| c.cmd(search).desc("Executes a search using DuckDuckGo.").known_as("ddg")) .command("eval", |c| c.cmd(eval).desc("Evaluates an arithmetic expression.").known_as("calc")) .command("exec", |c| c.cmd(exec).desc("DO NOT USE")) .command("eval-polish", |c| c.cmd(eval_polish).desc("Evaluates a Polish-notation arithmetic expression."))); if let Err(why) = client.start() { eprintln!("An error occured: {:?}", why); } } command!(ping(_context, message) { message.reply("Pong!")?; }); fn send_error(channel: &ChannelId, text: &str) -> std::result::Result<(), serenity::Error> { channel.send_message(|m| { m .embed(|e| e.title("Error").description(text).colour((255, 0, 0))) }).map(|_| ()) } fn send_text(channel: &ChannelId, text: &str) -> std::result::Result<(), serenity::Error> { channel.send_message(|m| { m .embed(|e| e.title("Result").description(text).colour((0, 255, 0))) }).map(|_| ()) } fn send_result(message: &Message, res: &Result) -> std::result::Result<(), serenity::Error> { match res { Ok(x) => send_text(&message.channel_id, &x.to_string()), Err(e) => send_error(&message.channel_id, &e.to_string()) } } // Evaluate an arithmetic expression command!(eval(_context, message, args) { let expr = args.multiple::()?.join(" "); // yes, this is kind of undoing the work the command parser does... send_result(message, &calc::eval(&expr))?; }); // Evaluate an arithmetic expression in polish notation command!(eval_polish(_context, message, args) { let expr = args.multiple::()?.join(" "); send_result(message, &calc::eval_polish(&expr))?; }); command!(exec(_context, message) { lazy_static! { static ref RE: Regex = Regex::new("^.*exec.*```(.*)\n(.*)```").unwrap(); } println!("{:?}", &message.content); println!("{:?}", RE.captures(&message.content)); }); // BELOW THIS LINE BE DRAGONS struct SearchResult { url: Option, image: Option, text: String, title: String } fn send_search_result(channel: &ChannelId, res: SearchResult) -> std::result::Result<(), serenity::Error> { channel.send_message(|m| { m .embed(|e| { let e = e.title(res.title).description(res.text).colour((0, 255, 255)); let e = match res.url { Some(u) => e.url(u), None => e }; let e = match res.image { Some(u) => e.image(u), None => e }; e }) }).map(|_| ()) } fn none_if_empty(s: String) -> Option { if s.len() == 0 { None } else { Some(s) } } fn get_topics(t: ddg::RelatedTopic) -> Vec { match t { ddg::RelatedTopic::TopicResult(t) => vec![t], ddg::RelatedTopic::Topic(t) => { let mut out = vec![]; for subtopic in t.topics { out.append(&mut get_topics(subtopic)) } out } } } command!(search(_context, message, args) { let query = args.multiple::()?.join(" "); let result = ddg::Query::new(query.as_str(), "autobotrobot").no_html().execute()?; let channel = &message.channel_id; match result.response_type { ddg::Type::Article | ddg::Type::Name => send_search_result(channel, SearchResult { title: query, image: none_if_empty(result.image), text: result.abstract_text, url: none_if_empty(result.abstract_url) })?, ddg::Type::Disambiguation => { for related_topic in result.related_topics { for topic in get_topics(related_topic) { send_search_result(channel, SearchResult { url: none_if_empty(topic.first_url), image: none_if_empty(topic.icon.url), title: query.clone(), text: topic.text })?; } } }, ddg::Type::Exclusive => send_search_result(channel, SearchResult { title: query, text: String::from("[see link]"), image: None, url: Some(result.abstract_url) })?, ddg::Type::Nothing => send_error(channel, "No results.")?, other => send_error(channel, &format!("{:?} - unrecognized result type", other))? } });