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
/Cargo.lock
.idea

View file

@ -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" }

View file

@ -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)
}

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 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 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 <category>",
" * owo/mock/leet [user]",
" * ev <math expression>",
" - 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<String, String>,
last_eval: HashMap<String, f64>,
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?;
}