Optimize executor and add mathbot
This commit is contained in:
parent
68c15be79a
commit
4a27aa42a3
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
uberbot_*.toml
|
uberbot_*.toml
|
||||||
uberbot.toml
|
uberbot.toml
|
||||||
/Cargo.lock
|
/Cargo.lock
|
||||||
|
.idea
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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
31
src/bots/misc.rs
Normal 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")?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod leek;
|
pub mod leek;
|
||||||
pub mod title;
|
pub mod title;
|
||||||
pub mod weeb;
|
pub mod misc;
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
105
src/main.rs
105
src/main.rs
|
@ -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?;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue