Implement handling messages in Bot
This commit is contained in:
parent
287d2657f3
commit
dcbb530465
|
@ -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 = ""
|
||||||
|
|
64
src/bot.rs
64
src/bot.rs
|
@ -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
14
src/commands/debug.rs
Normal 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)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
19
src/main.rs
19
src/main.rs
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue