From 4a27aa42a336e22717539a8df83312b3f00398f1 Mon Sep 17 00:00:00 2001 From: lemonsh Date: Sat, 1 Jan 2022 00:51:54 +0100 Subject: [PATCH] Optimize executor and add mathbot --- .gitignore | 1 + Cargo.toml | 1 + src/bots/leek.rs | 14 ++++--- src/bots/misc.rs | 31 ++++++++++++++ src/bots/mod.rs | 2 +- src/bots/weeb.rs | 14 ------- src/main.rs | 105 +++++++++++++++++++++-------------------------- 7 files changed, 88 insertions(+), 80 deletions(-) create mode 100644 src/bots/misc.rs delete mode 100644 src/bots/weeb.rs diff --git a/.gitignore b/.gitignore index 375073c..8372106 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ uberbot_*.toml uberbot.toml /Cargo.lock +.idea diff --git a/Cargo.toml b/Cargo.toml index 56b274e..207749d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,5 @@ toml = "0.5" serde = "1.0" arrayvec = "0.7" rand = "0.8" +meval = "0.2" async-circe = { git = "https://git.karx.xyz/circe/async-circe" } diff --git a/src/bots/leek.rs b/src/bots/leek.rs index 3312fec..411077f 100644 --- a/src/bots/leek.rs +++ b/src/bots/leek.rs @@ -27,9 +27,9 @@ pub fn mock(input: &str) -> LeekResult { for ch in input.chars() { if rand::random() { - builder.try_push(ch.to_ascii_uppercase())?; + builder.push(ch.to_ascii_uppercase()); } else { - builder.try_push(ch.to_ascii_lowercase())?; + builder.push(ch.to_ascii_lowercase()); } } @@ -40,7 +40,7 @@ pub fn leetify(input: &str) -> LeekResult { let mut builder = ArrayString::<512>::new(); for ch in input.chars() { - builder.try_push(match ch { + builder.push(match ch { 'a' => '4', 'e' => '3', 'i' => '1', @@ -50,14 +50,14 @@ pub fn leetify(input: &str) -> LeekResult { 't' => '7', 'b' => '8', _ => ch, - })?; + }); } Ok(builder) } pub fn owoify(input: &str) -> LeekResult { - let mut builder: ArrayString<512> = ArrayString::from(input)?; + let mut builder: ArrayString<512> = ArrayString::from("\x1d")?; let mut rng = rand::thread_rng(); let mut last_char = '\0'; for byte in input.bytes() { @@ -84,7 +84,7 @@ pub fn owoify(input: &str) -> LeekResult { '.' => { builder.try_push_str(match rng.gen_range(0..6) { 1 => " OwO", - 2 => " :3", + 2 => " (◕ᴗ◕✿)", 3 => " >w<", 4 => " >_<", 5 => " ^•ﻌ•^", @@ -96,5 +96,7 @@ pub fn owoify(input: &str) -> LeekResult { builder.try_push(ch)?; last_char = ch; } + builder.try_push_str("~~")?; Ok(builder) } + diff --git a/src/bots/misc.rs b/src/bots/misc.rs new file mode 100644 index 0000000..ddaf08d --- /dev/null +++ b/src/bots/misc.rs @@ -0,0 +1,31 @@ +use std::collections::HashMap; +use arrayvec::ArrayString; +use meval::Context; +use serde_json::Value; +use std::fmt::Write; + +pub async fn get_waifu_pic(category: &str) -> anyhow::Result> { + let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category)) + .await? + .text() + .await?; + let api_resp = api_resp.trim(); + tracing::debug!("API response: {}", api_resp); + let value: Value = serde_json::from_str(&api_resp)?; + let url = value["url"].as_str().map(|v| v.to_string()); + Ok(url) +} + +pub fn mathbot(author: String, expr: Option<&str>, last_evals: &mut HashMap) -> anyhow::Result> { + if let Some(expr) = expr { + let last_eval = last_evals.entry(author).or_insert(0.0); + let mut meval_ctx = Context::new(); + let mut result = ArrayString::new(); + let value = meval::eval_str_with_context(expr, meval_ctx.var("x", *last_eval))?; + *last_eval = value; + write!(result, "{} = {}", expr, value)?; + Ok(result) + } else { + Ok(ArrayString::from("No expression to evaluate")?) + } +} diff --git a/src/bots/mod.rs b/src/bots/mod.rs index efcdebc..4511374 100644 --- a/src/bots/mod.rs +++ b/src/bots/mod.rs @@ -1,3 +1,3 @@ pub mod leek; pub mod title; -pub mod weeb; +pub mod misc; diff --git a/src/bots/weeb.rs b/src/bots/weeb.rs deleted file mode 100644 index fd3bd76..0000000 --- a/src/bots/weeb.rs +++ /dev/null @@ -1,14 +0,0 @@ -use serde_json::Value; - -pub async fn get_waifu_pic(category: &str) -> anyhow::Result> { - let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category)) - .await? - .text() - .await?; - let api_resp = api_resp.trim(); - tracing::debug!("API response: {}", api_resp); - let value: Value = serde_json::from_str(&api_resp)?; - let url = value["url"].as_str().map(|v| v.to_string()); - Ok(url) -} - diff --git a/src/main.rs b/src/main.rs index 5fc6f27..fa04262 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ +mod bots; + use async_circe::{commands::Command, Client, Config}; use bots::title::Titlebot; -use bots::{leek, weeb}; +use bots::{leek, misc}; use rspotify::Credentials; use serde::Deserialize; use std::fs::File; @@ -9,19 +11,21 @@ use std::{collections::HashMap, env}; use tokio::select; use tracing_subscriber::EnvFilter; -mod bots; - -const HELP: &str = concat!( - "=- \x1d\x02Ü\x02berbot\x0f ", - env!("CARGO_PKG_VERSION"), - " -=" -); +// this will be displayed when the help command is used +const HELP: &[&str] = &[ + concat!("=- \x1d\x02Ü\x02berbot\x0f ", env!("CARGO_PKG_VERSION"), " -="), + " * waifu ", + " * owo/mock/leet [user]", + " * ev ", + " - This bot also provides titles of URLs and details for Spotify URIs/links." +]; #[cfg(unix)] async fn terminate_signal() { use tokio::signal::unix::{signal, SignalKind}; let mut sigterm = signal(SignalKind::terminate()).unwrap(); let mut sigint = signal(SignalKind::interrupt()).unwrap(); + tracing::debug!("Installed ctrl+c handler"); select! { _ = sigterm.recv() => return, _ = sigint.recv() => return @@ -32,6 +36,7 @@ async fn terminate_signal() { async fn terminate_signal() { use tokio::signal::windows::ctrl_c; let mut ctrlc = ctrl_c().unwrap(); + tracing::debug!("Installed ctrl+c handler"); let _ = ctrlc.recv().await; } @@ -39,6 +44,7 @@ struct AppState { prefix: String, client: Client, last_msgs: HashMap, + last_eval: HashMap, titlebot: Titlebot, } @@ -61,8 +67,7 @@ async fn main() -> anyhow::Result<()> { .with_env_filter(EnvFilter::from_env("UBERBOT_LOG")) .init(); - let mut file = - File::open(env::var("UBERBOT_CONFIG").unwrap_or_else(|_| "uberbot.toml".to_string()))?; + let mut file = File::open(env::var("UBERBOT_CONFIG").unwrap_or_else(|_| "uberbot.toml".to_string()))?; let mut client_conf = String::new(); file.read_to_string(&mut client_conf)?; @@ -81,19 +86,18 @@ async fn main() -> anyhow::Result<()> { client_config.port, client_config.username, ); - tracing::debug!("Creating circe client"); let mut client = Client::new(config).await?; - tracing::debug!("Identifying with IRC"); client.identify().await?; let state = AppState { prefix: client_config.prefix, client, last_msgs: HashMap::new(), + last_eval: HashMap::new(), titlebot: Titlebot::create(spotify_creds).await?, }; - if let Err(e) = message_loop(state).await { + if let Err(e) = executor(state).await { tracing::error!("Error in message loop: {}", e); } @@ -102,24 +106,24 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn message_loop(mut state: AppState) -> anyhow::Result<()> { - loop { - select! { - r = state.client.read() => { - if let Ok(command) = r { - handle_message(&mut state, command).await?; - } - }, - _ = terminate_signal() => { - tracing::info!("Sending QUIT message"); - state.client.quit(Some("überbot shutting down")).await?; - break; - } +async fn executor(mut state: AppState) -> anyhow::Result<()> { + select! { + r = message_loop(&mut state) => r?, + _ = terminate_signal() => { + tracing::info!("Sending QUIT message"); + state.client.quit(Some("überbot shutting down")).await?; } } Ok(()) } +async fn message_loop(state: &mut AppState) -> anyhow::Result<()> { + while let Some(cmd) = state.client.read().await? { + handle_message(state, cmd).await?; + } + Ok(()) +} + async fn handle_message(state: &mut AppState, command: Command) -> anyhow::Result<()> { // change this to a match when more commands are handled if let Command::PRIVMSG(nick, channel, message) = command { @@ -134,30 +138,20 @@ async fn handle_message(state: &mut AppState, command: Command) -> anyhow::Resul } enum LeekCommand { - Owo, - Leet, - Mock, + Owo, Leet, Mock } -async fn execute_leek( - state: &mut AppState, - cmd: LeekCommand, - channel: &str, - nick: &str, -) -> anyhow::Result<()> { +async fn execute_leek(state: &mut AppState, cmd: LeekCommand, channel: &str, nick: &str) -> anyhow::Result<()> { match state.last_msgs.get(nick) { Some(msg) => { let output = match cmd { LeekCommand::Owo => leek::owoify(msg)?, LeekCommand::Leet => leek::leetify(msg)?, - LeekCommand::Mock => leek::mock(msg)?, + LeekCommand::Mock => leek::mock(msg)? }; state.client.privmsg(channel, &output).await?; } None => { - state - .client - .privmsg(channel, "No last messages found.") - .await?; + state.client.privmsg(channel, "No last messages found.").await?; } } Ok(()) @@ -170,11 +164,10 @@ async fn handle_privmsg( message: String, ) -> anyhow::Result<()> { if !message.starts_with(state.prefix.as_str()) { - state.last_msgs.insert(nick, message.clone()); - if let Some(titlebot_msg) = state.titlebot.resolve(&message).await? { state.client.privmsg(&channel, &titlebot_msg).await?; } + state.last_msgs.insert(nick, message); return Ok(()); } let space_index = message.find(' '); @@ -183,15 +176,17 @@ async fn handle_privmsg( } else { (&message[state.prefix.len()..], None) }; - tracing::debug!("Command received ({}; {:?})", command, remainder); + tracing::debug!("Command received {:?} -> ({:?}; {:?})", message, command, remainder); match command { "help" => { - state.client.privmsg(&channel, HELP).await?; + for help_line in HELP { + state.client.privmsg(&channel, help_line).await?; + } } "waifu" => { let category = remainder.unwrap_or("waifu"); - let url = weeb::get_waifu_pic(category).await?; + let url = misc::get_waifu_pic(category).await?; let response = url .as_ref() .map(|v| v.as_str()) @@ -199,26 +194,18 @@ async fn handle_privmsg( state.client.privmsg(&channel, response).await?; } "mock" => { - execute_leek( - state, - LeekCommand::Mock, - channel, - remainder.unwrap_or(&nick), - ) - .await?; + execute_leek(state, LeekCommand::Mock, channel, remainder.unwrap_or(&nick)).await?; } "leet" => { - execute_leek( - state, - LeekCommand::Leet, - channel, - remainder.unwrap_or(&nick), - ) - .await?; + execute_leek(state, LeekCommand::Leet, channel, remainder.unwrap_or(&nick)).await?; } "owo" => { execute_leek(state, LeekCommand::Owo, channel, remainder.unwrap_or(&nick)).await?; } + "ev" => { + let result = misc::mathbot(nick, remainder, &mut state.last_eval)?; + state.client.privmsg(&channel, &result).await?; + } _ => { state.client.privmsg(&channel, "Unknown command").await?; }