diff --git a/.gitignore b/.gitignore index a6c6890..375073c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -/target -uberbot_*.toml -uberbot.toml -/Cargo.lock +/target +uberbot_*.toml +uberbot.toml +/Cargo.lock diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index b146804..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Debug executable 'uberbot'", - "cargo": { - "args": [ - "build", - "--bin=uberbot", - "--package=uberbot" - ], - "filter": { - "name": "uberbot", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'uberbot'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=uberbot", - "--package=uberbot" - ], - "filter": { - "name": "uberbot", - "kind": "bin" - } - }, - "args": [], - "cwd": "${workspaceFolder}" - } - ] -} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 2e10006..56b274e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,9 @@ name = "uberbot" version = "0.1.0" edition = "2021" +[profile.release] +lto = true + [dependencies] tokio = { version = "1.15", features = ["rt", "macros", "signal"] } anyhow = "1.0" @@ -17,4 +20,4 @@ toml = "0.5" serde = "1.0" arrayvec = "0.7" rand = "0.8" -async-circe = "0.1.1" +async-circe = { git = "https://git.karx.xyz/circe/async-circe" } diff --git a/sample_uberbot.toml b/sample_uberbot.toml index f66384d..0884b51 100644 --- a/sample_uberbot.toml +++ b/sample_uberbot.toml @@ -2,7 +2,7 @@ host = "karx.xyz" port = 6697 username = "uberbot" -channels = ["#main, #no-normies"] +channels = ["#main", "#no-normies"] mode = "+B" # Spotify config diff --git a/src/bots/leek.rs b/src/bots/leek.rs index a9173da..3312fec 100644 --- a/src/bots/leek.rs +++ b/src/bots/leek.rs @@ -1,24 +1,46 @@ -use arrayvec::ArrayString; +use arrayvec::{ArrayString, CapacityError}; +use rand::Rng; +use std::{error::Error, fmt::{Debug, Display}}; -pub fn mock(target: &str) -> ArrayString<512> { +#[derive(Debug)] +pub struct LeekCapacityError(CapacityError); + +impl Display for LeekCapacityError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Display::fmt(&self.0, f) + } +} + +impl Error for LeekCapacityError {} + +impl From> for LeekCapacityError { + fn from(e: CapacityError) -> Self { + Self { 0: e.simplify() } + } +} + + +type LeekResult = Result, LeekCapacityError>; + +pub fn mock(input: &str) -> LeekResult { let mut builder = ArrayString::<512>::new(); - for char in target.chars() { + for ch in input.chars() { if rand::random() { - builder.push(char.to_ascii_uppercase()); + builder.try_push(ch.to_ascii_uppercase())?; } else { - builder.push(char.to_ascii_lowercase()); + builder.try_push(ch.to_ascii_lowercase())?; } } - builder + Ok(builder) } -pub fn leetify(target: &str) -> ArrayString<512> { +pub fn leetify(input: &str) -> LeekResult { let mut builder = ArrayString::<512>::new(); - for char in target.chars() { - builder.push(match char { + for ch in input.chars() { + builder.try_push(match ch { 'a' => '4', 'e' => '3', 'i' => '1', @@ -27,9 +49,52 @@ pub fn leetify(target: &str) -> ArrayString<512> { 's' => '5', 't' => '7', 'b' => '8', - _ => char, - }); + _ => ch, + })?; } - builder + Ok(builder) +} + +pub fn owoify(input: &str) -> LeekResult { + let mut builder: ArrayString<512> = ArrayString::from(input)?; + let mut rng = rand::thread_rng(); + let mut last_char = '\0'; + for byte in input.bytes() { + let mut ch = char::from(byte); + if !ch.is_ascii() { + continue; + } + // owoify character + ch = match ch.to_ascii_lowercase() { + 'r' | 'l' => 'w', + _ => ch, + }; + // stutter (e.g. "o-ohayou gozaimasu!") + if last_char == ' ' && rng.gen_bool(0.2) { + builder.try_push(ch)?; + builder.try_push('-')?; + } + match ch { + // nya-ify + 'a' | 'e' | 'i' | 'o' | 'u' if last_char == 'n' => { + builder.try_push('y')?; + } + // textmoji + '.' => { + builder.try_push_str(match rng.gen_range(0..6) { + 1 => " OwO", + 2 => " :3", + 3 => " >w<", + 4 => " >_<", + 5 => " ^•ﻌ•^", + _ => " ^^", + })?; + } + _ => {} + } + builder.try_push(ch)?; + last_char = ch; + } + Ok(builder) } diff --git a/src/bots/title.rs b/src/bots/title.rs index 2fe1a8c..ca54cf2 100644 --- a/src/bots/title.rs +++ b/src/bots/title.rs @@ -3,7 +3,6 @@ use htmlescape::decode_html; use rspotify::clients::BaseClient; use rspotify::model::PlayableItem; use rspotify::{model::Id, ClientCredsSpotify, Credentials}; -use tracing::debug; fn calculate_playtime(secs: u64) -> (u64, u64) { let mut dur_sec = secs; @@ -22,7 +21,7 @@ async fn resolve_spotify( // if spotify.token.lock().await.unwrap().as_ref().unwrap().is_expired() { // spotify.request_token().await?; // } - debug!( + tracing::debug!( "Resolving Spotify resource '{}' with id '{}'", resource_type, resource_id ); diff --git a/src/bots/weeb.rs b/src/bots/weeb.rs index 7f657ec..fd3bd76 100644 --- a/src/bots/weeb.rs +++ b/src/bots/weeb.rs @@ -1,8 +1,4 @@ -use arrayvec::{ArrayString, CapacityError}; -use rand::Rng; use serde_json::Value; -use std::result::Result; -use tracing::debug; pub async fn get_waifu_pic(category: &str) -> anyhow::Result> { let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category)) @@ -10,62 +6,9 @@ pub async fn get_waifu_pic(category: &str) -> anyhow::Result> { .text() .await?; let api_resp = api_resp.trim(); - debug!("API response: {}", api_resp); + 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 struct OwoCapacityError(CapacityError); - -impl From> for OwoCapacityError { - fn from(e: CapacityError) -> Self { - Self { 0: e.simplify() } - } -} - -pub fn owoify_out_of_place( - input: &str, - output: &mut ArrayString<512>, -) -> Result<(), OwoCapacityError> { - let input: ArrayString<512> = ArrayString::from(input)?; - let mut rng = rand::thread_rng(); - let mut last_char = '\0'; - for byte in input.bytes() { - let mut ch = char::from(byte); - if !ch.is_ascii() { - continue; - } - // owoify character - ch = match ch.to_ascii_lowercase() { - 'r' | 'l' => 'w', - _ => ch, - }; - // stutter (e.g. "o-ohayou gozaimasu!") - if last_char == ' ' && rng.gen_bool(0.2) { - output.try_push(ch)?; - output.try_push('-')?; - } - match ch { - // nya-ify - 'a' | 'e' | 'i' | 'o' | 'u' if last_char == 'n' => { - output.try_push('y')?; - } - // textmoji - '.' => { - output.try_push_str(match rng.gen_range(0..6) { - 1 => " OwO", - 2 => " :3", - 3 => " >w<", - 4 => " >_<", - 5 => " ^•ﻌ•^", - _ => " ^^", - })?; - } - _ => {} - } - output.try_push(ch)?; - last_char = ch; - } - Ok(()) -} diff --git a/src/main.rs b/src/main.rs index f933da8..2aee74f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use async_circe::{commands::Command, Client, Config}; use bots::title::Titlebot; -use bots::weeb; +use bots::{weeb, leek}; use rspotify::Credentials; use serde::Deserialize; use std::fs::File; @@ -61,7 +61,7 @@ async fn main() -> anyhow::Result<()> { .with_env_filter(EnvFilter::from_env("UBERBOT_LOG")) .init(); - let mut file = File::open("uberbot.toml")?; + 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)?; @@ -80,7 +80,9 @@ 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 { @@ -130,6 +132,26 @@ async fn handle_message(state: &mut AppState, command: Command) -> anyhow::Resul Ok(()) } +enum LeekCommand { + Owo, Leet, Mock +} +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)? + }; + state.client.privmsg(channel, &output).await?; + } + None => { + state.client.privmsg(channel, "No last messages found.").await?; + } + } + Ok(()) +} + async fn handle_privmsg( state: &mut AppState, nick: String, @@ -166,42 +188,13 @@ async fn handle_privmsg( state.client.privmsg(&channel, response).await?; } "mock" => { - let user = match remainder { - Some(u) => match u { - "" => &nick, - _ => u, - }, - None => &nick, - } - .trim(); - if let Some(prev_msg) = state.last_msgs.get(user) { - let resp = bots::leek::mock(prev_msg); - state.client.privmsg(&channel, &resp).await?; - } else { - state - .client - .privmsg(&channel, "No previous messages to mock!") - .await?; - } + execute_leek(state, LeekCommand::Mock, channel, remainder.unwrap_or(&nick)).await?; } "leet" => { - let user = match remainder { - Some(u) => match u { - "" => &nick, - _ => u, - }, - None => &nick, - } - .trim(); - if let Some(prev_msg) = state.last_msgs.get(user) { - let resp = bots::leek::leetify(prev_msg); - state.client.privmsg(&channel, &resp).await?; - } else { - state - .client - .privmsg(&channel, "No previous messages to leetify!") - .await?; - } + execute_leek(state, LeekCommand::Leet, channel, remainder.unwrap_or(&nick)).await?; + } + "owo" => { + execute_leek(state, LeekCommand::Owo, channel, remainder.unwrap_or(&nick)).await?; } _ => { state.client.privmsg(&channel, "Unknown command").await?;