Optimize executor and add mathbot

This commit is contained in:
lemonsh 2022-01-01 00:51:54 +01:00
parent 68c15be79a
commit 4a27aa42a3
7 changed files with 88 additions and 80 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
uberbot_*.toml uberbot_*.toml
uberbot.toml uberbot.toml
/Cargo.lock /Cargo.lock
.idea

View file

@ -20,4 +20,5 @@ toml = "0.5"
serde = "1.0" serde = "1.0"
arrayvec = "0.7" arrayvec = "0.7"
rand = "0.8" rand = "0.8"
meval = "0.2"
async-circe = { git = "https://git.karx.xyz/circe/async-circe" } async-circe = { git = "https://git.karx.xyz/circe/async-circe" }

View file

@ -27,9 +27,9 @@ pub fn mock(input: &str) -> LeekResult {
for ch in input.chars() { for ch in input.chars() {
if rand::random() { if rand::random() {
builder.try_push(ch.to_ascii_uppercase())?; builder.push(ch.to_ascii_uppercase());
} else { } 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(); let mut builder = ArrayString::<512>::new();
for ch in input.chars() { for ch in input.chars() {
builder.try_push(match ch { builder.push(match ch {
'a' => '4', 'a' => '4',
'e' => '3', 'e' => '3',
'i' => '1', 'i' => '1',
@ -50,14 +50,14 @@ pub fn leetify(input: &str) -> LeekResult {
't' => '7', 't' => '7',
'b' => '8', 'b' => '8',
_ => ch, _ => ch,
})?; });
} }
Ok(builder) Ok(builder)
} }
pub fn owoify(input: &str) -> LeekResult { 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 rng = rand::thread_rng();
let mut last_char = '\0'; let mut last_char = '\0';
for byte in input.bytes() { 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) { builder.try_push_str(match rng.gen_range(0..6) {
1 => " OwO", 1 => " OwO",
2 => " :3", 2 => " (◕ᴗ◕✿)",
3 => " >w<", 3 => " >w<",
4 => " >_<", 4 => " >_<",
5 => " ^•ﻌ•^", 5 => " ^•ﻌ•^",
@ -96,5 +96,7 @@ pub fn owoify(input: &str) -> LeekResult {
builder.try_push(ch)?; builder.try_push(ch)?;
last_char = ch; last_char = ch;
} }
builder.try_push_str("~~")?;
Ok(builder) Ok(builder)
} }

31
src/bots/misc.rs Normal file
View file

@ -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<Option<String>> {
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<String, f64>) -> anyhow::Result<ArrayString<256>> {
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")?)
}
}

View file

@ -1,3 +1,3 @@
pub mod leek; pub mod leek;
pub mod title; pub mod title;
pub mod weeb; pub mod misc;

View file

@ -1,14 +0,0 @@
use serde_json::Value;
pub async fn get_waifu_pic(category: &str) -> anyhow::Result<Option<String>> {
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)
}

View file

@ -1,6 +1,8 @@
mod bots;
use async_circe::{commands::Command, Client, Config}; use async_circe::{commands::Command, Client, Config};
use bots::title::Titlebot; use bots::title::Titlebot;
use bots::{leek, weeb}; use bots::{leek, misc};
use rspotify::Credentials; use rspotify::Credentials;
use serde::Deserialize; use serde::Deserialize;
use std::fs::File; use std::fs::File;
@ -9,19 +11,21 @@ use std::{collections::HashMap, env};
use tokio::select; use tokio::select;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
mod bots; // this will be displayed when the help command is used
const HELP: &[&str] = &[
const HELP: &str = concat!( concat!("=- \x1d\x02Ü\x02berbot\x0f ", env!("CARGO_PKG_VERSION"), " -="),
"=- \x1d\x02Ü\x02berbot\x0f ", " * waifu <category>",
env!("CARGO_PKG_VERSION"), " * owo/mock/leet [user]",
" -=" " * ev <math expression>",
); " - This bot also provides titles of URLs and details for Spotify URIs/links."
];
#[cfg(unix)] #[cfg(unix)]
async fn terminate_signal() { async fn terminate_signal() {
use tokio::signal::unix::{signal, SignalKind}; use tokio::signal::unix::{signal, SignalKind};
let mut sigterm = signal(SignalKind::terminate()).unwrap(); let mut sigterm = signal(SignalKind::terminate()).unwrap();
let mut sigint = signal(SignalKind::interrupt()).unwrap(); let mut sigint = signal(SignalKind::interrupt()).unwrap();
tracing::debug!("Installed ctrl+c handler");
select! { select! {
_ = sigterm.recv() => return, _ = sigterm.recv() => return,
_ = sigint.recv() => return _ = sigint.recv() => return
@ -32,6 +36,7 @@ async fn terminate_signal() {
async fn terminate_signal() { async fn terminate_signal() {
use tokio::signal::windows::ctrl_c; use tokio::signal::windows::ctrl_c;
let mut ctrlc = ctrl_c().unwrap(); let mut ctrlc = ctrl_c().unwrap();
tracing::debug!("Installed ctrl+c handler");
let _ = ctrlc.recv().await; let _ = ctrlc.recv().await;
} }
@ -39,6 +44,7 @@ struct AppState {
prefix: String, prefix: String,
client: Client, client: Client,
last_msgs: HashMap<String, String>, last_msgs: HashMap<String, String>,
last_eval: HashMap<String, f64>,
titlebot: Titlebot, titlebot: Titlebot,
} }
@ -61,8 +67,7 @@ async fn main() -> anyhow::Result<()> {
.with_env_filter(EnvFilter::from_env("UBERBOT_LOG")) .with_env_filter(EnvFilter::from_env("UBERBOT_LOG"))
.init(); .init();
let mut file = let mut file = File::open(env::var("UBERBOT_CONFIG").unwrap_or_else(|_| "uberbot.toml".to_string()))?;
File::open(env::var("UBERBOT_CONFIG").unwrap_or_else(|_| "uberbot.toml".to_string()))?;
let mut client_conf = String::new(); let mut client_conf = String::new();
file.read_to_string(&mut client_conf)?; file.read_to_string(&mut client_conf)?;
@ -81,19 +86,18 @@ async fn main() -> anyhow::Result<()> {
client_config.port, client_config.port,
client_config.username, client_config.username,
); );
tracing::debug!("Creating circe client");
let mut client = Client::new(config).await?; let mut client = Client::new(config).await?;
tracing::debug!("Identifying with IRC");
client.identify().await?; client.identify().await?;
let state = AppState { let state = AppState {
prefix: client_config.prefix, prefix: client_config.prefix,
client, client,
last_msgs: HashMap::new(), last_msgs: HashMap::new(),
last_eval: HashMap::new(),
titlebot: Titlebot::create(spotify_creds).await?, 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); tracing::error!("Error in message loop: {}", e);
} }
@ -102,24 +106,24 @@ async fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
async fn message_loop(mut state: AppState) -> anyhow::Result<()> { async fn executor(mut state: AppState) -> anyhow::Result<()> {
loop { select! {
select! { r = message_loop(&mut state) => r?,
r = state.client.read() => { _ = terminate_signal() => {
if let Ok(command) = r { tracing::info!("Sending QUIT message");
handle_message(&mut state, command).await?; state.client.quit(Some("überbot shutting down")).await?;
}
},
_ = terminate_signal() => {
tracing::info!("Sending QUIT message");
state.client.quit(Some("überbot shutting down")).await?;
break;
}
} }
} }
Ok(()) 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<()> { async fn handle_message(state: &mut AppState, command: Command) -> anyhow::Result<()> {
// change this to a match when more commands are handled // change this to a match when more commands are handled
if let Command::PRIVMSG(nick, channel, message) = command { 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 { enum LeekCommand {
Owo, Owo, Leet, Mock
Leet,
Mock,
} }
async fn execute_leek( async fn execute_leek(state: &mut AppState, cmd: LeekCommand, channel: &str, nick: &str) -> anyhow::Result<()> {
state: &mut AppState,
cmd: LeekCommand,
channel: &str,
nick: &str,
) -> anyhow::Result<()> {
match state.last_msgs.get(nick) { match state.last_msgs.get(nick) {
Some(msg) => { Some(msg) => {
let output = match cmd { let output = match cmd {
LeekCommand::Owo => leek::owoify(msg)?, LeekCommand::Owo => leek::owoify(msg)?,
LeekCommand::Leet => leek::leetify(msg)?, LeekCommand::Leet => leek::leetify(msg)?,
LeekCommand::Mock => leek::mock(msg)?, LeekCommand::Mock => leek::mock(msg)?
}; };
state.client.privmsg(channel, &output).await?; state.client.privmsg(channel, &output).await?;
} }
None => { None => {
state state.client.privmsg(channel, "No last messages found.").await?;
.client
.privmsg(channel, "No last messages found.")
.await?;
} }
} }
Ok(()) Ok(())
@ -170,11 +164,10 @@ async fn handle_privmsg(
message: String, message: String,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if !message.starts_with(state.prefix.as_str()) { 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? { if let Some(titlebot_msg) = state.titlebot.resolve(&message).await? {
state.client.privmsg(&channel, &titlebot_msg).await?; state.client.privmsg(&channel, &titlebot_msg).await?;
} }
state.last_msgs.insert(nick, message);
return Ok(()); return Ok(());
} }
let space_index = message.find(' '); let space_index = message.find(' ');
@ -183,15 +176,17 @@ async fn handle_privmsg(
} else { } else {
(&message[state.prefix.len()..], None) (&message[state.prefix.len()..], None)
}; };
tracing::debug!("Command received ({}; {:?})", command, remainder); tracing::debug!("Command received {:?} -> ({:?}; {:?})", message, command, remainder);
match command { match command {
"help" => { "help" => {
state.client.privmsg(&channel, HELP).await?; for help_line in HELP {
state.client.privmsg(&channel, help_line).await?;
}
} }
"waifu" => { "waifu" => {
let category = remainder.unwrap_or("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 let response = url
.as_ref() .as_ref()
.map(|v| v.as_str()) .map(|v| v.as_str())
@ -199,26 +194,18 @@ async fn handle_privmsg(
state.client.privmsg(&channel, response).await?; state.client.privmsg(&channel, response).await?;
} }
"mock" => { "mock" => {
execute_leek( execute_leek(state, LeekCommand::Mock, channel, remainder.unwrap_or(&nick)).await?;
state,
LeekCommand::Mock,
channel,
remainder.unwrap_or(&nick),
)
.await?;
} }
"leet" => { "leet" => {
execute_leek( execute_leek(state, LeekCommand::Leet, channel, remainder.unwrap_or(&nick)).await?;
state,
LeekCommand::Leet,
channel,
remainder.unwrap_or(&nick),
)
.await?;
} }
"owo" => { "owo" => {
execute_leek(state, LeekCommand::Owo, channel, remainder.unwrap_or(&nick)).await?; 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?; state.client.privmsg(&channel, "Unknown command").await?;
} }