Implement handling messages in Bot

This commit is contained in:
lemonsh 2022-07-16 23:00:43 +02:00
parent 287d2657f3
commit dcbb530465
5 changed files with 78 additions and 24 deletions

View file

@ -5,7 +5,7 @@ tls = true
username = "uberbot" username = "uberbot"
channels = ["#main", "#no-normies"] channels = ["#main", "#no-normies"]
mode = "+B" mode = "+B"
prefix = "!" prefix = "u!"
[spotify] [spotify]
spotify_client_id = "" spotify_client_id = ""

View file

@ -2,6 +2,7 @@ use crate::ExecutorConnection;
use async_trait::async_trait; use async_trait::async_trait;
use fancy_regex::{Captures, Regex}; use fancy_regex::{Captures, Regex};
use std::collections::HashMap; use std::collections::HashMap;
use tokio::sync::{Mutex, RwLock};
fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) { fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) {
if let Some(o) = str.find(' ') { if let Some(o) = str.find(' ') {
@ -13,7 +14,7 @@ fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) {
#[async_trait] #[async_trait]
pub trait Trigger { pub trait Trigger {
async fn execute(&mut self, msg: Message, matches: Captures) -> anyhow::Result<String>; async fn execute<'a>(&mut self, msg: Message<'a>, matches: Captures<'a>) -> anyhow::Result<String>;
} }
#[async_trait] #[async_trait]
@ -23,24 +24,25 @@ pub trait Command {
} }
pub struct Message<'a> { pub struct Message<'a> {
pub last_msg: &'a HashMap<String, String>, pub last_msg: &'a RwLock<HashMap<String, String>>,
pub author: &'a str, pub author: &'a str,
// in case of triggers, this is always Some(...)
pub content: Option<&'a str>, pub content: Option<&'a str>,
} }
pub struct Bot<SF: FnMut(String, String) -> anyhow::Result<()>> { pub struct Bot<SF: Fn(String, String) -> anyhow::Result<()>> {
last_msg: HashMap<String, String>, last_msg: RwLock<HashMap<String, String>>,
prefix: String, prefix: String,
db: ExecutorConnection, db: ExecutorConnection,
commands: HashMap<String, Box<dyn Command + Send>>, commands: HashMap<String, Box<Mutex<dyn Command + Send>>>,
triggers: Vec<(Regex, Box<dyn Trigger + Send>)>, triggers: Vec<(Regex, Box<Mutex<dyn Trigger + Send>>)>,
sendmsg: SF, sendmsg: SF,
} }
impl<SF: FnMut(String, String) -> anyhow::Result<()>> Bot<SF> { impl<SF: Fn(String, String) -> anyhow::Result<()>> Bot<SF> {
pub fn new(prefix: String, db: ExecutorConnection, sendmsg: SF) -> Self { pub fn new(prefix: String, db: ExecutorConnection, sendmsg: SF) -> Self {
Bot { Bot {
last_msg: HashMap::new(), last_msg: RwLock::new(HashMap::new()),
commands: HashMap::new(), commands: HashMap::new(),
triggers: Vec::new(), triggers: Vec::new(),
prefix, prefix,
@ -50,19 +52,55 @@ impl<SF: FnMut(String, String) -> anyhow::Result<()>> Bot<SF> {
} }
pub fn add_command<C: Command + Send + 'static>(&mut self, name: String, cmd: C) { pub fn add_command<C: Command + Send + 'static>(&mut self, name: String, cmd: C) {
self.commands.insert(name, Box::new(cmd)); self.commands.insert(name, Box::new(Mutex::new(cmd)));
} }
pub fn add_regex_command<C: Trigger + Send + 'static>(&mut self, regex: Regex, cmd: C) { pub fn add_trigger<C: Trigger + Send + 'static>(&mut self, regex: Regex, cmd: C) {
self.triggers.push((regex, Box::new(cmd))); self.triggers.push((regex, Box::new(Mutex::new(cmd))));
} }
pub async fn handle_message( async fn handle_message_inner(
&mut self, &self,
origin: &str, origin: &str,
author: &str, author: &str,
content: &str, content: &str,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
if content.starts_with(&self.prefix) {
let (command, remainder) = separate_to_space(content, self.prefix.len());
if let Some(handler) = self.commands.get(command) {
let msg = Message {
last_msg: &self.last_msg,
author,
content: remainder
};
return (self.sendmsg)(origin.into(), handler.lock().await.execute(msg).await?)
}
return (self.sendmsg)(origin.into(), "Unknown command.".into())
} else {
for trigger in &self.triggers {
let captures = trigger.0.captures(content)?;
if let Some(captures) = captures {
let msg = Message {
last_msg: &self.last_msg,
author,
content: Some(content)
};
return (self.sendmsg)(origin.into(), trigger.1.lock().await.execute(msg, captures).await?)
}
}
self.last_msg.write().await.insert(author.to_string(), content.to_string());
}
Ok(()) Ok(())
} }
pub async fn handle_message(
&self,
origin: &str,
author: &str,
content: &str,
) {
if let Err(e) = self.handle_message_inner(origin, author, content).await {
let _ = (self.sendmsg)(origin.into(), format!("Error: {}", e));
}
}
} }

14
src/commands/debug.rs Normal file
View file

@ -0,0 +1,14 @@
use async_trait::async_trait;
use crate::bot::{Command, Message};
pub struct LastMsg;
#[async_trait]
impl Command for LastMsg {
//noinspection RsNeedlessLifetimes
async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result<String> {
let nick = msg.content.unwrap_or(msg.author);
let lastmsg = msg.last_msg.read().await;
Ok(format!("{}: {:?}", nick, lastmsg.get(nick)))
}
}

View file

@ -1,3 +1,6 @@
pub mod help; pub mod help;
pub mod leek; pub mod leek;
pub mod waifu; pub mod waifu;
pub mod sed;
#[cfg(feature = "debug")]
pub mod debug;

View file

@ -5,6 +5,7 @@ use std::fs::File;
use std::io::Read; use std::io::Read;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use fancy_regex::Regex;
use crate::bot::Bot; use crate::bot::Bot;
use crate::commands::waifu::Waifu; use crate::commands::waifu::Waifu;
@ -12,12 +13,13 @@ use futures_util::stream::StreamExt;
use irc::client::prelude::Config; use irc::client::prelude::Config;
use irc::client::{Client, ClientStream}; use irc::client::{Client, ClientStream};
use irc::proto::{ChannelExt, Command, Prefix}; use irc::proto::{ChannelExt, Command, Prefix};
use rspotify::Credentials;
use tokio::select; use tokio::select;
use tokio::sync::broadcast; use tokio::sync::broadcast;
use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::unbounded_channel;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use crate::commands::help::Help;
use crate::commands::leek::Owo; use crate::commands::leek::Owo;
use crate::commands::sed::Sed;
use crate::config::UberConfig; use crate::config::UberConfig;
use crate::database::{DbExecutor, ExecutorConnection}; use crate::database::{DbExecutor, ExecutorConnection};
@ -34,8 +36,8 @@ async fn terminate_signal() {
let mut sigint = signal(SignalKind::interrupt()).unwrap(); let mut sigint = signal(SignalKind::interrupt()).unwrap();
tracing::debug!("Installed ctrl+c handler"); tracing::debug!("Installed ctrl+c handler");
select! { select! {
_ = sigterm.recv() => return, _ = sigterm.recv() => (),
_ = sigint.recv() => return _ = sigint.recv() => ()
} }
} }
@ -47,7 +49,7 @@ async fn terminate_signal() {
let _ = ctrlc.recv().await; let _ = ctrlc.recv().await;
} }
pub struct AppState<SF: FnMut(String, String) -> anyhow::Result<()>> { pub struct AppState<SF: Fn(String, String) -> anyhow::Result<()>> {
client: Arc<Client>, client: Arc<Client>,
stream: ClientStream, stream: ClientStream,
bot: Bot<SF>, bot: Bot<SF>,
@ -99,6 +101,7 @@ async fn main() -> anyhow::Result<()> {
move |target, msg| Ok(client.send_privmsg(target, msg)?) move |target, msg| Ok(client.send_privmsg(target, msg)?)
}); });
bot.add_command("help".into(), Help);
bot.add_command("waifu".into(), Waifu); bot.add_command("waifu".into(), Waifu);
bot.add_command("owo".into(), Owo); bot.add_command("owo".into(), Owo);
@ -142,7 +145,7 @@ async fn main() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
async fn message_loop<SF: FnMut(String, String) -> anyhow::Result<()>>( async fn message_loop<SF: Fn(String, String) -> anyhow::Result<()>>(
mut state: AppState<SF>, mut state: AppState<SF>,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
while let Some(message) = state.stream.next().await.transpose()? { while let Some(message) = state.stream.next().await.transpose()? {
@ -152,11 +155,7 @@ async fn message_loop<SF: FnMut(String, String) -> anyhow::Result<()>>(
Prefix::Nickname(name, _, _) => Some(&name[..]), Prefix::Nickname(name, _, _) => Some(&name[..]),
_ => None, _ => None,
}) { }) {
if let Err(e) = state.bot.handle_message(origin, author, &content).await { state.bot.handle_message(origin, author, &content).await
state
.client
.send_privmsg(origin, &format!("Error: {}", e))?;
}
} else { } else {
tracing::warn!("Couldn't get the author for a message"); tracing::warn!("Couldn't get the author for a message");
} }