From 287d2657f3dfd16b118280887ef822a82f09c3e5 Mon Sep 17 00:00:00 2001 From: lemonsh Date: Sat, 16 Jul 2022 18:33:43 +0200 Subject: [PATCH] Port Leek and Waifu command to new model (incl. bonus: rustfmt) --- src/bot.rs | 50 +++++++++++++++----------- src/bots/mod.rs | 4 --- src/commands/help.rs | 20 +++++++++++ src/{bots => commands}/leek.rs | 66 +++++++++++++++++++++------------- src/commands/mod.rs | 3 ++ src/commands/waifu.rs | 24 +++++++++++++ src/config.rs | 3 +- src/database.rs | 14 ++++++-- src/main.rs | 43 ++++++++-------------- 9 files changed, 145 insertions(+), 82 deletions(-) delete mode 100644 src/bots/mod.rs create mode 100644 src/commands/help.rs rename src/{bots => commands}/leek.rs (67%) create mode 100644 src/commands/mod.rs create mode 100644 src/commands/waifu.rs diff --git a/src/bot.rs b/src/bot.rs index a00b776..3608dea 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; -use fancy_regex::Regex; use crate::ExecutorConnection; use async_trait::async_trait; +use fancy_regex::{Captures, Regex}; +use std::collections::HashMap; fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) { if let Some(o) = str.find(' ') { @@ -11,50 +11,58 @@ fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) { } } -pub trait RegexCommand { - fn execute(&mut self, message: String) -> anyhow::Result; +#[async_trait] +pub trait Trigger { + async fn execute(&mut self, msg: Message, matches: Captures) -> anyhow::Result; } #[async_trait] -pub trait NormalCommand { - async fn execute(&mut self, last_msg: &HashMap, message: String) -> anyhow::Result; +pub trait Command { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result; } -#[derive(Default)] -struct Commands { - regex: Vec<(Regex, Box)>, - normal: HashMap>, +pub struct Message<'a> { + pub last_msg: &'a HashMap, + pub author: &'a str, + pub content: Option<&'a str>, } pub struct Bot anyhow::Result<()>> { last_msg: HashMap, prefix: String, db: ExecutorConnection, - commands: Commands, - sendmsg: SF + commands: HashMap>, + triggers: Vec<(Regex, Box)>, + sendmsg: SF, } impl anyhow::Result<()>> Bot { pub fn new(prefix: String, db: ExecutorConnection, sendmsg: SF) -> Self { Bot { last_msg: HashMap::new(), + commands: HashMap::new(), + triggers: Vec::new(), prefix, db, - commands: Commands::default(), - sendmsg + sendmsg, } } - pub fn add_command(&mut self, name: String, cmd: C) { - self.commands.normal.insert(name, Box::new(cmd)); + pub fn add_command(&mut self, name: String, cmd: C) { + self.commands.insert(name, Box::new(cmd)); } - pub fn add_regex_command(&mut self, regex: Regex, cmd: C) { - self.commands.regex.push((regex, Box::new(cmd))); + pub fn add_regex_command(&mut self, regex: Regex, cmd: C) { + self.triggers.push((regex, Box::new(cmd))); } - pub async fn handle_message(&mut self, origin: &str, author: &str, content: &str) -> anyhow::Result<()> { - (self.sendmsg)(origin.into(), content.into()).unwrap(); + pub async fn handle_message( + &mut self, + origin: &str, + author: &str, + content: &str, + ) -> anyhow::Result<()> { Ok(()) } -} \ No newline at end of file +} diff --git a/src/bots/mod.rs b/src/bots/mod.rs deleted file mode 100644 index 99c5cd8..0000000 --- a/src/bots/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod leek; -pub mod misc; -pub mod sed; -pub mod title; diff --git a/src/commands/help.rs b/src/commands/help.rs new file mode 100644 index 0000000..21d6dfb --- /dev/null +++ b/src/commands/help.rs @@ -0,0 +1,20 @@ +use crate::bot::{Message, Command}; +use async_trait::async_trait; + +const HELP: &str = concat!( + "=- \x1d\x02Überbot\x0f ", env!("CARGO_PKG_VERSION"), " -=\n", + " * waifu \n", + " * owo/mock/leet [user]\n", + " * ev \n", + " - This bot also provides titles of URLs and details for Spotify URIs/links. It can also resolve sed expressions.\n" +); + +pub struct Help; + +#[async_trait] +impl Command for Help { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, _msg: Message<'a>) -> anyhow::Result { + Ok(HELP.into()) + } +} diff --git a/src/bots/leek.rs b/src/commands/leek.rs similarity index 67% rename from src/bots/leek.rs rename to src/commands/leek.rs index e4c1d28..1cf88dd 100644 --- a/src/bots/leek.rs +++ b/src/commands/leek.rs @@ -1,4 +1,6 @@ +use crate::bot::{Command, Message}; use arrayvec::ArrayString; +use async_trait::async_trait; use rand::Rng; use std::{ error::Error, @@ -85,11 +87,11 @@ fn owoify(input: &str) -> LeekResult { // textmoji '.' => { builder.try_push_str(match rng.gen_range(0..6) { - 1 => " OwO", + 1 => " >~<", 2 => " (◕ᴗ◕✿)", 3 => " >w<", 4 => " >_<", - 5 => " ^•ﻌ•^", + 5 => " OwO", _ => " ^^", })?; } @@ -103,33 +105,49 @@ fn owoify(input: &str) -> LeekResult { } #[derive(Debug, Clone, Copy)] -pub enum Command { +enum LeekCommand { Owo, Leet, Mock, } -pub fn execute( - state: &mut crate::AppState, - cmd: Command, - target: &str, - nick: &str, -) -> anyhow::Result<()> { - match state.last_msgs.get(nick) { - Some(msg) => { - tracing::debug!("Executing {:?} on {:?}", cmd, msg); - let output = match cmd { - Command::Owo => super::leek::owoify(msg)?, - Command::Leet => super::leek::leetify(msg), - Command::Mock => super::leek::mock(msg), - }; - state.client.send_privmsg(target, &output)?; - } - None => { - state - .client - .send_privmsg(target, "No last messages found.")?; +fn execute_leek(cmd: LeekCommand, msg: &Message) -> anyhow::Result { + let nick = msg.content.unwrap_or(msg.author); + match msg.last_msg.get(nick) { + Some(msg) => Ok(match cmd { + LeekCommand::Owo => owoify(msg)?, + LeekCommand::Leet => leetify(msg), + LeekCommand::Mock => mock(msg), } + .to_string()), + None => Ok("No previous messages found.".into()), + } +} + +pub struct Owo; +pub struct Leet; +pub struct Mock; + +#[async_trait] +impl Command for Owo { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result { + execute_leek(LeekCommand::Owo, &msg) + } +} + +#[async_trait] +impl Command for Leet { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result { + execute_leek(LeekCommand::Leet, &msg) + } +} + +#[async_trait] +impl Command for Mock { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result { + execute_leek(LeekCommand::Mock, &msg) } - Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..b7471c4 --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,3 @@ +pub mod help; +pub mod leek; +pub mod waifu; diff --git a/src/commands/waifu.rs b/src/commands/waifu.rs new file mode 100644 index 0000000..95e3959 --- /dev/null +++ b/src/commands/waifu.rs @@ -0,0 +1,24 @@ +use crate::bot::{Message, Command}; +use async_trait::async_trait; +use serde_json::Value; + +pub struct Waifu; + +#[async_trait] +impl Command for Waifu { + //noinspection RsNeedlessLifetimes + async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result { + let category = msg.content.unwrap_or("waifu"); + let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category)) + .await? + .text() + .await?; + let api_resp = api_resp.trim(); + let value: Value = serde_json::from_str(api_resp)?; + let url = value["url"] + .as_str() + .unwrap_or("Invalid API Response.") + .to_string(); + Ok(url) + } +} diff --git a/src/config.rs b/src/config.rs index 2578231..ee7bbd9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,3 @@ -use std::net::SocketAddr; use serde::Deserialize; #[derive(Deserialize)] @@ -24,4 +23,4 @@ pub struct IrcConfig { pub port: u16, pub username: String, pub prefix: String, -} \ No newline at end of file +} diff --git a/src/database.rs b/src/database.rs index cd5838a..91aea83 100644 --- a/src/database.rs +++ b/src/database.rs @@ -135,6 +135,16 @@ impl ExecutorConnection { Option, author: Option ); - executor_wrapper!(search_quotes, Task::SearchQuotes, Option>, query: String); - executor_wrapper!(random_n_quotes, Task::RandomNQuotes, Option>, count: u8); + executor_wrapper!( + search_quotes, + Task::SearchQuotes, + Option>, + query: String + ); + executor_wrapper!( + random_n_quotes, + Task::RandomNQuotes, + Option>, + count: u8 + ); } diff --git a/src/main.rs b/src/main.rs index a9eb273..54b16b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,31 @@ #![allow(clippy::match_wildcard_for_single_variants)] +use std::env; use std::fs::File; use std::io::Read; use std::sync::Arc; use std::thread; -use std::env; -use std::fmt::Display; +use crate::bot::Bot; +use crate::commands::waifu::Waifu; use futures_util::stream::StreamExt; use irc::client::prelude::Config; use irc::client::{Client, ClientStream}; use irc::proto::{ChannelExt, Command, Prefix}; use rspotify::Credentials; -use serde::Deserialize; use tokio::select; use tokio::sync::broadcast; use tokio::sync::mpsc::unbounded_channel; use tracing_subscriber::EnvFilter; -use crate::bot::Bot; -use crate::bots::misc::Waifu; +use crate::commands::leek::Owo; use crate::config::UberConfig; use crate::database::{DbExecutor, ExecutorConnection}; -mod bots; -mod database; mod bot; +mod commands; mod config; - -// 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. It can also resolve sed expressions." -]; +mod database; #[cfg(unix)] async fn terminate_signal() { @@ -60,7 +50,7 @@ async fn terminate_signal() { pub struct AppState anyhow::Result<()>> { client: Arc, stream: ClientStream, - bot: Bot + bot: Bot, } #[tokio::main] @@ -76,8 +66,7 @@ async fn main() -> anyhow::Result<()> { let cfg: UberConfig = toml::from_str(&client_conf)?; - let (db_exec, db_conn) = - DbExecutor::create(cfg.db_path.as_deref().unwrap_or("uberbot.db3"))?; + let (db_exec, db_conn) = DbExecutor::create(cfg.db_path.as_deref().unwrap_or("uberbot.db3"))?; let exec_thread = thread::spawn(move || { db_exec.run(); tracing::info!("Database executor has been shut down"); @@ -85,12 +74,7 @@ async fn main() -> anyhow::Result<()> { let uber_ver = concat!("Überbot ", env!("CARGO_PKG_VERSION")); let irc_config = Config { - nickname: Some( - cfg - .irc - .nickname - .unwrap_or_else(|| cfg.irc.username.clone()), - ), + nickname: Some(cfg.irc.nickname.unwrap_or_else(|| cfg.irc.username.clone())), username: Some(cfg.irc.username.clone()), realname: Some(cfg.irc.username), server: Some(cfg.irc.host), @@ -116,11 +100,12 @@ async fn main() -> anyhow::Result<()> { }); bot.add_command("waifu".into(), Waifu); + bot.add_command("owo".into(), Owo); let state = AppState { client: client.clone(), stream, - bot + bot, }; let message_loop_task = tokio::spawn(async move { if let Err(e) = message_loop(state).await { @@ -157,7 +142,9 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn message_loop anyhow::Result<()>>(mut state: AppState) -> anyhow::Result<()> { +async fn message_loop anyhow::Result<()>>( + mut state: AppState, +) -> anyhow::Result<()> { while let Some(message) = state.stream.next().await.transpose()? { if let Command::PRIVMSG(ref origin, content) = message.command { if origin.is_channel_name() { @@ -178,5 +165,3 @@ async fn message_loop anyhow::Result<()>>(mut state } Ok(()) } - -