Remove Bot completely

This commit is contained in:
Joscha 2024-12-28 19:17:44 +01:00
parent dac2fb06d3
commit 4030d8af09
11 changed files with 105 additions and 241 deletions

View file

@ -3,14 +3,13 @@ use std::time::Duration;
use async_trait::async_trait; use async_trait::async_trait;
use euphoxide::api::Message; use euphoxide::api::Message;
use euphoxide_bot::{ use euphoxide_bot::{
bot::Bot, bang::{General, Specific},
command::{ basic::Described,
bang::{General, Specific}, botrulez::{FullHelp, Ping, ShortHelp},
basic::Described, Command, Commands, Context, Info, Propagate,
botrulez::{FullHelp, Ping, ShortHelp},
Command, Commands, Context, Info, Propagate,
},
}; };
use euphoxide_client::MultiClient;
use log::error;
use tokio::sync::mpsc; use tokio::sync::mpsc;
struct Pyramid; struct Pyramid;
@ -26,7 +25,6 @@ impl Command for Pyramid {
_arg: &str, _arg: &str,
msg: &Message, msg: &Message,
ctx: &Context, ctx: &Context,
_bot: &Bot,
) -> euphoxide::Result<Propagate> { ) -> euphoxide::Result<Propagate> {
let mut parent = msg.id; let mut parent = msg.id;
@ -42,7 +40,8 @@ impl Command for Pyramid {
} }
} }
async fn run() -> anyhow::Result<()> { #[tokio::main]
async fn main() {
let (event_tx, mut event_rx) = mpsc::channel(10); let (event_tx, mut event_rx) = mpsc::channel(10);
let commands = Commands::new() let commands = Commands::new()
@ -56,28 +55,24 @@ async fn run() -> anyhow::Result<()> {
"help", "help",
FullHelp::new().with_after("Created using euphoxide."), FullHelp::new().with_after("Created using euphoxide."),
))) )))
.then(General::new("pyramid", Pyramid)); .then(General::new("pyramid", Pyramid))
.build();
let bot: Bot = Bot::new_simple(commands, event_tx); let clients = MultiClient::new(event_tx);
bot.clients clients
.client_builder("test") .client_builder("test")
.with_username("examplebot") .with_username("examplebot")
.build_and_add() .build_and_add()
.await; .await;
while let Some(event) = event_rx.recv().await { while let Some(event) = event_rx.recv().await {
bot.handle_event(event); let commands = commands.clone();
} let clients = clients.clone();
tokio::task::spawn(async move {
Ok(()) if let Err(err) = commands.handle_event(clients, event).await {
} error!("Oops: {err}")
}
#[tokio::main] });
async fn main() {
loop {
if let Err(err) = run().await {
println!("Error while running: {err}");
}
} }
} }

View file

@ -1,55 +0,0 @@
use std::{fmt::Debug, sync::Arc};
use euphoxide_client::{MultiClient, MultiClientConfig, MultiClientEvent};
use log::error;
use tokio::sync::mpsc;
use crate::command::Commands;
#[non_exhaustive]
pub struct Bot<E = euphoxide::Error> {
pub commands: Arc<Commands<E>>,
pub clients: MultiClient,
}
impl Bot {
pub fn new_simple(commands: Commands, event_tx: mpsc::Sender<MultiClientEvent>) -> Self {
Self::new(MultiClientConfig::default(), commands, event_tx)
}
}
impl<E> Bot<E> {
pub fn new(
clients_config: MultiClientConfig,
commands: Commands<E>,
event_tx: mpsc::Sender<MultiClientEvent>,
) -> Self {
Self {
commands: Arc::new(commands),
clients: MultiClient::new_with_config(clients_config, event_tx),
}
}
}
impl<E> Bot<E>
where
E: Debug + 'static,
{
pub fn handle_event(&self, event: MultiClientEvent) {
let bot = self.clone();
tokio::task::spawn(async move {
if let Err(err) = bot.commands.on_event(event, &bot).await {
error!("while handling event: {err:#?}");
}
});
}
}
impl<E> Clone for Bot<E> {
fn clone(&self) -> Self {
Self {
commands: self.commands.clone(),
clients: self.clients.clone(),
}
}
}

View file

@ -4,7 +4,7 @@ pub mod botrulez;
#[cfg(feature = "clap")] #[cfg(feature = "clap")]
pub mod clap; pub mod clap;
use std::future::Future; use std::{future::Future, sync::Arc};
use async_trait::async_trait; use async_trait::async_trait;
use euphoxide::{ use euphoxide::{
@ -14,18 +14,18 @@ use euphoxide::{
state::{Joined, State}, state::{Joined, State},
}, },
}; };
use euphoxide_client::{Client, MultiClientEvent}; use euphoxide_client::{Client, MultiClient, MultiClientEvent};
use crate::bot::Bot;
#[non_exhaustive] #[non_exhaustive]
pub struct Context { pub struct Context<E = euphoxide::Error> {
pub commands: Arc<Commands<E>>,
pub clients: MultiClient,
pub client: Client, pub client: Client,
pub conn: ClientConnHandle, pub conn: ClientConnHandle,
pub joined: Joined, pub joined: Joined,
} }
impl Context { impl<E> Context<E> {
pub async fn send( pub async fn send(
&self, &self,
content: impl ToString, content: impl ToString,
@ -111,17 +111,11 @@ pub enum Propagate {
#[allow(unused_variables)] #[allow(unused_variables)]
#[async_trait] #[async_trait]
pub trait Command<E = euphoxide::Error> { pub trait Command<E = euphoxide::Error> {
fn info(&self, ctx: &Context) -> Info { fn info(&self, ctx: &Context<E>) -> Info {
Info::default() Info::default()
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E>;
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E>;
} }
pub struct Commands<E = euphoxide::Error> { pub struct Commands<E = euphoxide::Error> {
@ -142,14 +136,44 @@ impl<E> Commands<E> {
self self
} }
pub fn infos(&self, ctx: &Context) -> Vec<Info> { pub fn build(self) -> Arc<Self> {
Arc::new(self)
}
pub fn infos(&self, ctx: &Context<E>) -> Vec<Info> {
self.commands.iter().map(|c| c.info(ctx)).collect() self.commands.iter().map(|c| c.info(ctx)).collect()
} }
pub(crate) async fn on_event( pub async fn handle_message(
&self, self: Arc<Self>,
clients: MultiClient,
client: Client,
conn: ClientConnHandle,
joined: Joined,
msg: &Message,
) -> Result<Propagate, E> {
let ctx = Context {
commands: self.clone(),
clients,
client,
conn,
joined,
};
for command in &self.commands {
let propagate = command.execute(&msg.content, msg, &ctx).await?;
if propagate == Propagate::No {
return Ok(Propagate::No);
}
}
Ok(Propagate::Yes)
}
pub async fn handle_event(
self: Arc<Self>,
clients: MultiClient,
event: MultiClientEvent, event: MultiClientEvent,
bot: &Bot<E>,
) -> Result<Propagate, E> { ) -> Result<Propagate, E> {
let MultiClientEvent::Packet { let MultiClientEvent::Packet {
client, client,
@ -169,20 +193,8 @@ impl<E> Commands<E> {
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
}; };
let ctx = Context { self.handle_message(clients, client, conn, joined, msg)
client, .await
conn,
joined,
};
for command in &self.commands {
let propagate = command.execute(&msg.content, msg, &ctx, bot).await?;
if propagate == Propagate::No {
return Ok(Propagate::No);
}
}
Ok(Propagate::Yes)
} }
} }

View file

@ -3,8 +3,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use euphoxide::{api::Message, nick}; use euphoxide::{api::Message, nick};
use crate::bot::Bot;
use super::{Command, Context, Info, Propagate}; use super::{Command, Context, Info, Propagate};
// TODO Don't ignore leading whitespace? // TODO Don't ignore leading whitespace?
@ -51,19 +49,13 @@ impl<E, C> Command<E> for Global<C>
where where
C: Command<E> + Sync, C: Command<E> + Sync,
{ {
fn info(&self, ctx: &Context) -> Info { fn info(&self, ctx: &Context<E>) -> Info {
self.inner self.inner
.info(ctx) .info(ctx)
.with_prepended_trigger(format!("{}{}", self.prefix, self.name)) .with_prepended_trigger(format!("{}{}", self.prefix, self.name))
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
let Some((name, rest)) = parse_prefix_initiated(arg, &self.prefix) else { let Some((name, rest)) = parse_prefix_initiated(arg, &self.prefix) else {
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
}; };
@ -72,7 +64,7 @@ where
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
} }
self.inner.execute(rest, msg, ctx, bot).await self.inner.execute(rest, msg, ctx).await
} }
} }
@ -102,19 +94,13 @@ impl<E, C> Command<E> for General<C>
where where
C: Command<E> + Sync, C: Command<E> + Sync,
{ {
fn info(&self, ctx: &Context) -> Info { fn info(&self, ctx: &Context<E>) -> Info {
self.inner self.inner
.info(ctx) .info(ctx)
.with_prepended_trigger(format!("{}{}", self.prefix, self.name)) .with_prepended_trigger(format!("{}{}", self.prefix, self.name))
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
let Some((name, rest)) = parse_prefix_initiated(arg, &self.prefix) else { let Some((name, rest)) = parse_prefix_initiated(arg, &self.prefix) else {
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
}; };
@ -130,7 +116,7 @@ where
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
} }
self.inner.execute(rest, msg, ctx, bot).await self.inner.execute(rest, msg, ctx).await
} }
} }
@ -160,20 +146,14 @@ impl<E, C> Command<E> for Specific<C>
where where
C: Command<E> + Sync, C: Command<E> + Sync,
{ {
fn info(&self, ctx: &Context) -> Info { fn info(&self, ctx: &Context<E>) -> Info {
let nick = nick::mention(&ctx.joined.session.name); let nick = nick::mention(&ctx.joined.session.name);
self.inner self.inner
.info(ctx) .info(ctx)
.with_prepended_trigger(format!("{}{} @{nick}", self.prefix, self.name)) .with_prepended_trigger(format!("{}{} @{nick}", self.prefix, self.name))
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
let Some((name, rest)) = parse_prefix_initiated(arg, &self.prefix) else { let Some((name, rest)) = parse_prefix_initiated(arg, &self.prefix) else {
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
}; };
@ -190,7 +170,7 @@ where
return Ok(Propagate::Yes); return Ok(Propagate::Yes);
} }
self.inner.execute(rest, msg, ctx, bot).await self.inner.execute(rest, msg, ctx).await
} }
} }

View file

@ -3,8 +3,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use euphoxide::api::Message; use euphoxide::api::Message;
use crate::bot::Bot;
use super::{Command, Context, Info, Propagate}; use super::{Command, Context, Info, Propagate};
/// Rewrite or hide command info. /// Rewrite or hide command info.
@ -55,7 +53,7 @@ impl<E, C> Command<E> for Described<C>
where where
C: Command<E> + Sync, C: Command<E> + Sync,
{ {
fn info(&self, ctx: &Context) -> Info { fn info(&self, ctx: &Context<E>) -> Info {
let info = self.inner.info(ctx); let info = self.inner.info(ctx);
Info { Info {
trigger: self.trigger.clone().unwrap_or(info.trigger), trigger: self.trigger.clone().unwrap_or(info.trigger),
@ -63,14 +61,8 @@ where
} }
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self, self.inner.execute(arg, msg, ctx).await
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
self.inner.execute(arg, msg, ctx, bot).await
} }
} }
@ -93,19 +85,13 @@ impl<E, C> Command<E> for Prefixed<C>
where where
C: Command<E> + Sync, C: Command<E> + Sync,
{ {
fn info(&self, ctx: &Context) -> Info { fn info(&self, ctx: &Context<E>) -> Info {
self.inner.info(ctx).with_prepended_trigger(&self.prefix) self.inner.info(ctx).with_prepended_trigger(&self.prefix)
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
if let Some(rest) = arg.trim_start().strip_prefix(&self.prefix) { if let Some(rest) = arg.trim_start().strip_prefix(&self.prefix) {
self.inner.execute(rest, msg, ctx, bot).await self.inner.execute(rest, msg, ctx).await
} else { } else {
Ok(Propagate::Yes) Ok(Propagate::Yes)
} }

View file

@ -5,10 +5,7 @@ use euphoxide::api::Message;
#[cfg(feature = "clap")] #[cfg(feature = "clap")]
use crate::command::clap::ClapCommand; use crate::command::clap::ClapCommand;
use crate::{ use crate::command::{Command, Context, Propagate};
bot::Bot,
command::{Command, Context, Propagate},
};
#[derive(Default)] #[derive(Default)]
pub struct FullHelp { pub struct FullHelp {
@ -31,7 +28,7 @@ impl FullHelp {
self self
} }
fn formulate_reply<E>(&self, ctx: &Context, bot: &Bot<E>) -> String { fn formulate_reply<E>(&self, ctx: &Context<E>) -> String {
let mut result = String::new(); let mut result = String::new();
if !self.before.is_empty() { if !self.before.is_empty() {
@ -39,7 +36,7 @@ impl FullHelp {
result.push('\n'); result.push('\n');
} }
for info in bot.commands.infos(ctx) { for info in ctx.commands.infos(ctx) {
if let Some(trigger) = &info.trigger { if let Some(trigger) = &info.trigger {
result.push_str(trigger); result.push_str(trigger);
if let Some(description) = &info.description { if let Some(description) = &info.description {
@ -64,15 +61,9 @@ impl<E> Command<E> for FullHelp
where where
E: From<euphoxide::Error>, E: From<euphoxide::Error>,
{ {
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
if arg.trim().is_empty() { if arg.trim().is_empty() {
let reply = self.formulate_reply(ctx, bot); let reply = self.formulate_reply(ctx);
ctx.reply_only(msg.id, reply).await?; ctx.reply_only(msg.id, reply).await?;
Ok(Propagate::No) Ok(Propagate::No)
} else { } else {
@ -98,10 +89,9 @@ where
&self, &self,
_args: Self::Args, _args: Self::Args,
msg: &Message, msg: &Message,
ctx: &Context, ctx: &Context<E>,
bot: &Bot<E>,
) -> Result<Propagate, E> { ) -> Result<Propagate, E> {
let reply = self.formulate_reply(ctx, bot); let reply = self.formulate_reply(ctx);
ctx.reply_only(msg.id, reply).await?; ctx.reply_only(msg.id, reply).await?;
Ok(Propagate::No) Ok(Propagate::No)
} }

View file

@ -5,10 +5,7 @@ use euphoxide::api::Message;
#[cfg(feature = "clap")] #[cfg(feature = "clap")]
use crate::command::clap::ClapCommand; use crate::command::clap::ClapCommand;
use crate::{ use crate::command::{Command, Context, Propagate};
bot::Bot,
command::{Command, Context, Propagate},
};
pub struct Ping(pub String); pub struct Ping(pub String);
@ -29,13 +26,7 @@ impl<E> Command<E> for Ping
where where
E: From<euphoxide::Error>, E: From<euphoxide::Error>,
{ {
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
_bot: &Bot<E>,
) -> Result<Propagate, E> {
if arg.trim().is_empty() { if arg.trim().is_empty() {
ctx.reply_only(msg.id, &self.0).await?; ctx.reply_only(msg.id, &self.0).await?;
Ok(Propagate::No) Ok(Propagate::No)
@ -62,8 +53,7 @@ where
&self, &self,
_args: Self::Args, _args: Self::Args,
msg: &Message, msg: &Message,
ctx: &Context, ctx: &Context<E>,
_bot: &Bot<E>,
) -> Result<Propagate, E> { ) -> Result<Propagate, E> {
ctx.reply_only(msg.id, &self.0).await?; ctx.reply_only(msg.id, &self.0).await?;
Ok(Propagate::No) Ok(Propagate::No)

View file

@ -5,10 +5,7 @@ use euphoxide::api::Message;
#[cfg(feature = "clap")] #[cfg(feature = "clap")]
use crate::command::clap::ClapCommand; use crate::command::clap::ClapCommand;
use crate::{ use crate::command::{Command, Context, Propagate};
bot::Bot,
command::{Command, Context, Propagate},
};
pub struct ShortHelp(pub String); pub struct ShortHelp(pub String);
@ -23,13 +20,7 @@ impl<E> Command<E> for ShortHelp
where where
E: From<euphoxide::Error>, E: From<euphoxide::Error>,
{ {
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
_bot: &Bot<E>,
) -> Result<Propagate, E> {
if arg.trim().is_empty() { if arg.trim().is_empty() {
ctx.reply_only(msg.id, &self.0).await?; ctx.reply_only(msg.id, &self.0).await?;
Ok(Propagate::No) Ok(Propagate::No)
@ -56,8 +47,7 @@ where
&self, &self,
_args: Self::Args, _args: Self::Args,
msg: &Message, msg: &Message,
ctx: &Context, ctx: &Context<E>,
_bot: &Bot<E>,
) -> Result<Propagate, E> { ) -> Result<Propagate, E> {
ctx.reply_only(msg.id, &self.0).await?; ctx.reply_only(msg.id, &self.0).await?;
Ok(Propagate::No) Ok(Propagate::No)

View file

@ -6,10 +6,7 @@ use jiff::{Span, Timestamp, Unit};
#[cfg(feature = "clap")] #[cfg(feature = "clap")]
use crate::command::clap::ClapCommand; use crate::command::clap::ClapCommand;
use crate::{ use crate::command::{Command, Context, Propagate};
bot::Bot,
command::{Command, Context, Propagate},
};
pub fn format_time(t: Timestamp) -> String { pub fn format_time(t: Timestamp) -> String {
t.strftime("%Y-%m-%d %H:%M:%S UTC").to_string() t.strftime("%Y-%m-%d %H:%M:%S UTC").to_string()
@ -62,14 +59,8 @@ pub trait HasStartTime {
} }
impl Uptime { impl Uptime {
fn formulate_reply<E>( fn formulate_reply<E>(&self, ctx: &Context<E>, joined: bool, connected: bool) -> String {
&self, let start = ctx.clients.start_time();
ctx: &Context,
bot: &Bot<E>,
joined: bool,
connected: bool,
) -> String {
let start = bot.clients.start_time();
let now = Timestamp::now(); let now = Timestamp::now();
let mut reply = format!( let mut reply = format!(
@ -105,15 +96,9 @@ impl<E> Command<E> for Uptime
where where
E: From<euphoxide::Error>, E: From<euphoxide::Error>,
{ {
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
if arg.trim().is_empty() { if arg.trim().is_empty() {
let reply = self.formulate_reply(ctx, bot, false, false); let reply = self.formulate_reply(ctx, false, false);
ctx.reply_only(msg.id, reply).await?; ctx.reply_only(msg.id, reply).await?;
Ok(Propagate::No) Ok(Propagate::No)
} else { } else {
@ -146,10 +131,9 @@ where
&self, &self,
args: Self::Args, args: Self::Args,
msg: &Message, msg: &Message,
ctx: &Context, ctx: &Context<E>,
bot: &Bot<E>,
) -> Result<Propagate, E> { ) -> Result<Propagate, E> {
let reply = self.formulate_reply(ctx, bot, args.present, args.connected); let reply = self.formulate_reply(ctx, args.present, args.connected);
ctx.reply_only(msg.id, reply).await?; ctx.reply_only(msg.id, reply).await?;
Ok(Propagate::No) Ok(Propagate::No)
} }

View file

@ -4,8 +4,6 @@ use async_trait::async_trait;
use clap::{CommandFactory, Parser}; use clap::{CommandFactory, Parser};
use euphoxide::api::Message; use euphoxide::api::Message;
use crate::bot::Bot;
use super::{Command, Context, Info, Propagate}; use super::{Command, Context, Info, Propagate};
#[async_trait] #[async_trait]
@ -16,8 +14,7 @@ pub trait ClapCommand<E> {
&self, &self,
args: Self::Args, args: Self::Args,
msg: &Message, msg: &Message,
ctx: &Context, ctx: &Context<E>,
bot: &Bot<E>,
) -> Result<Propagate, E>; ) -> Result<Propagate, E>;
} }
@ -107,20 +104,14 @@ where
C: ClapCommand<E> + Sync, C: ClapCommand<E> + Sync,
C::Args: Parser + Send, C::Args: Parser + Send,
{ {
fn info(&self, _ctx: &Context) -> Info { fn info(&self, _ctx: &Context<E>) -> Info {
Info { Info {
description: C::Args::command().get_about().map(|s| s.to_string()), description: C::Args::command().get_about().map(|s| s.to_string()),
..Info::default() ..Info::default()
} }
} }
async fn execute( async fn execute(&self, arg: &str, msg: &Message, ctx: &Context<E>) -> Result<Propagate, E> {
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &Bot<E>,
) -> Result<Propagate, E> {
let mut args = match parse_quoted_args(arg) { let mut args = match parse_quoted_args(arg) {
Ok(args) => args, Ok(args) => args,
Err(err) => { Err(err) => {
@ -141,7 +132,7 @@ where
} }
}; };
self.0.execute(args, msg, ctx, bot).await self.0.execute(args, msg, ctx).await
} }
} }

View file

@ -1,2 +1,3 @@
pub mod bot; mod command;
pub mod command;
pub use self::command::*;