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"
channels = ["#main", "#no-normies"]
mode = "+B"
prefix = "!"
prefix = "u!"
[spotify]
spotify_client_id = ""

View file

@ -2,6 +2,7 @@ use crate::ExecutorConnection;
use async_trait::async_trait;
use fancy_regex::{Captures, Regex};
use std::collections::HashMap;
use tokio::sync::{Mutex, RwLock};
fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) {
if let Some(o) = str.find(' ') {
@ -13,7 +14,7 @@ fn separate_to_space(str: &str, prefix_len: usize) -> (&str, Option<&str>) {
#[async_trait]
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]
@ -23,24 +24,25 @@ pub trait Command {
}
pub struct Message<'a> {
pub last_msg: &'a HashMap<String, String>,
pub last_msg: &'a RwLock<HashMap<String, String>>,
pub author: &'a str,
// in case of triggers, this is always Some(...)
pub content: Option<&'a str>,
}
pub struct Bot<SF: FnMut(String, String) -> anyhow::Result<()>> {
last_msg: HashMap<String, String>,
pub struct Bot<SF: Fn(String, String) -> anyhow::Result<()>> {
last_msg: RwLock<HashMap<String, String>>,
prefix: String,
db: ExecutorConnection,
commands: HashMap<String, Box<dyn Command + Send>>,
triggers: Vec<(Regex, Box<dyn Trigger + Send>)>,
commands: HashMap<String, Box<Mutex<dyn Command + Send>>>,
triggers: Vec<(Regex, Box<Mutex<dyn Trigger + Send>>)>,
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 {
Bot {
last_msg: HashMap::new(),
last_msg: RwLock::new(HashMap::new()),
commands: HashMap::new(),
triggers: Vec::new(),
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) {
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) {
self.triggers.push((regex, Box::new(cmd)));
pub fn add_trigger<C: Trigger + Send + 'static>(&mut self, regex: Regex, cmd: C) {
self.triggers.push((regex, Box::new(Mutex::new(cmd))));
}
pub async fn handle_message(
&mut self,
async fn handle_message_inner(
&self,
origin: &str,
author: &str,
content: &str,
) -> 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(())
}
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 leek;
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::sync::Arc;
use std::thread;
use fancy_regex::Regex;
use crate::bot::Bot;
use crate::commands::waifu::Waifu;
@ -12,12 +13,13 @@ 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 tokio::select;
use tokio::sync::broadcast;
use tokio::sync::mpsc::unbounded_channel;
use tracing_subscriber::EnvFilter;
use crate::commands::help::Help;
use crate::commands::leek::Owo;
use crate::commands::sed::Sed;
use crate::config::UberConfig;
use crate::database::{DbExecutor, ExecutorConnection};
@ -34,8 +36,8 @@ async fn terminate_signal() {
let mut sigint = signal(SignalKind::interrupt()).unwrap();
tracing::debug!("Installed ctrl+c handler");
select! {
_ = sigterm.recv() => return,
_ = sigint.recv() => return
_ = sigterm.recv() => (),
_ = sigint.recv() => ()
}
}
@ -47,7 +49,7 @@ async fn terminate_signal() {
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>,
stream: ClientStream,
bot: Bot<SF>,
@ -99,6 +101,7 @@ async fn main() -> anyhow::Result<()> {
move |target, msg| Ok(client.send_privmsg(target, msg)?)
});
bot.add_command("help".into(), Help);
bot.add_command("waifu".into(), Waifu);
bot.add_command("owo".into(), Owo);
@ -142,7 +145,7 @@ async fn main() -> anyhow::Result<()> {
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>,
) -> anyhow::Result<()> {
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[..]),
_ => None,
}) {
if let Err(e) = state.bot.handle_message(origin, author, &content).await {
state
.client
.send_privmsg(origin, &format!("Error: {}", e))?;
}
state.bot.handle_message(origin, author, &content).await
} else {
tracing::warn!("Couldn't get the author for a message");
}