Return whether command handled message

This commit is contained in:
Joscha 2023-02-27 12:41:49 +01:00
parent a6331d50b8
commit 4479126500
12 changed files with 142 additions and 47 deletions

View file

@ -50,12 +50,20 @@ where
B: HasDescriptions + Send,
E: From<conn::Error>,
{
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
if arg.trim().is_empty() {
let reply = self.formulate_reply(ctx, bot);
ctx.reply(msg.id, reply).await?;
Ok(true)
} else {
Ok(false)
}
Ok(())
}
}
@ -77,9 +85,9 @@ where
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
let reply = self.formulate_reply(ctx, bot);
ctx.reply(msg.id, reply).await?;
Ok(())
Ok(true)
}
}

View file

@ -30,11 +30,13 @@ where
msg: &Message,
ctx: &Context,
_bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
if arg.trim().is_empty() {
ctx.reply(msg.id, &self.0).await?;
Ok(true)
} else {
Ok(false)
}
Ok(())
}
}
@ -55,8 +57,8 @@ where
msg: &Message,
ctx: &Context,
_bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
ctx.reply(msg.id, &self.0).await?;
Ok(())
Ok(true)
}
}

View file

@ -24,11 +24,13 @@ where
msg: &Message,
ctx: &Context,
_bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
if arg.trim().is_empty() {
ctx.reply(msg.id, &self.0).await?;
Ok(true)
} else {
Ok(false)
}
Ok(())
}
}
@ -49,8 +51,8 @@ where
msg: &Message,
ctx: &Context,
_bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
ctx.reply(msg.id, &self.0).await?;
Ok(())
Ok(true)
}
}

View file

@ -81,12 +81,20 @@ where
B: HasStartTime + Send,
E: From<conn::Error>,
{
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
if arg.trim().is_empty() {
let reply = self.formulate_reply(ctx, bot, false);
ctx.reply(msg.id, reply).await?;
Ok(true)
} else {
Ok(false)
}
Ok(())
}
}
@ -112,9 +120,9 @@ where
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
let reply = self.formulate_reply(ctx, bot, args.connected);
ctx.reply(msg.id, reply).await?;
Ok(())
Ok(true)
}
}

View file

@ -54,5 +54,11 @@ pub trait Command<B, E> {
None
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E>;
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E>;
}

View file

@ -65,15 +65,21 @@ where
Some(format!("{}{} - {inner}", self.prefix, self.name))
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
// TODO Replace with let-else
let (name, rest) = match parse_command(arg, &self.prefix) {
Some(parsed) => parsed,
None => return Ok(()),
None => return Ok(false),
};
if name != self.name {
return Ok(());
return Ok(false);
}
self.inner.execute(rest, msg, ctx, bot).await
@ -112,22 +118,28 @@ where
Some(format!("{}{} - {inner}", self.prefix, self.name))
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
// TODO Replace with let-else
let (name, rest) = match parse_command(arg, &self.prefix) {
Some(parsed) => parsed,
None => return Ok(()),
None => return Ok(false),
};
if name != self.name {
return Ok(());
return Ok(false);
}
if parse_specific(rest).is_some() {
// The command looks like a specific command. If we treated it like
// a general command match, we would interpret other bots' specific
// commands as general commands.
return Ok(());
return Ok(false);
}
self.inner.execute(rest, msg, ctx, bot).await
@ -167,25 +179,31 @@ where
Some(format!("{}{} @{nick} - {inner}", self.prefix, self.name))
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
// TODO Replace with let-else
let (name, rest) = match parse_command(arg, &self.prefix) {
Some(parsed) => parsed,
None => return Ok(()),
None => return Ok(false),
};
if name != self.name {
return Ok(());
return Ok(false);
}
// TODO Replace with let-else
let (nick, rest) = match parse_specific(rest) {
Some(parsed) => parsed,
None => return Ok(()),
None => return Ok(false),
};
if nick::normalize(nick) != nick::normalize(&ctx.joined.session.name) {
return Ok(());
return Ok(false);
}
self.inner.execute(rest, msg, ctx, bot).await

View file

@ -16,7 +16,7 @@ pub trait ClapCommand<B, E> {
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<(), E>;
) -> Result<bool, E>;
}
/// Parse bash-like quoted arguments separated by whitespace.
@ -110,12 +110,18 @@ where
C::Args::command().get_about().map(|s| format!("{s}"))
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
let mut args = match parse_quoted_args(arg) {
Ok(args) => args,
Err(err) => {
ctx.reply(msg.id, err).await?;
return Ok(());
return Ok(true);
}
};
@ -127,7 +133,7 @@ where
Ok(args) => args,
Err(err) => {
ctx.reply(msg.id, format!("{}", err.render())).await?;
return Ok(());
return Ok(true);
}
};

View file

@ -17,7 +17,13 @@ where
None
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
self.0.execute(arg, msg, ctx, bot).await
}
}

View file

@ -29,11 +29,17 @@ where
Some(format!("{} - {inner}", self.prefix))
}
async fn execute(&self, arg: &str, msg: &Message, ctx: &Context, bot: &mut B) -> Result<(), E> {
async fn execute(
&self,
arg: &str,
msg: &Message,
ctx: &Context,
bot: &mut B,
) -> Result<bool, E> {
if let Some(rest) = arg.trim_start().strip_prefix(&self.prefix) {
self.inner.execute(rest, msg, ctx, bot).await
} else {
Ok(())
Ok(false)
}
}
}

View file

@ -7,11 +7,32 @@ use super::instance::{InstanceConfig, Snapshot};
pub struct Commands<B, E> {
commands: Vec<Box<dyn Command<B, E> + Send + Sync>>,
fallthrough: bool,
}
impl<B, E> Commands<B, E> {
pub fn new() -> Self {
Self { commands: vec![] }
Self {
commands: vec![],
fallthrough: false,
}
}
/// Whether further commands should be executed after a command returns
/// `true`.
///
/// If disabled, commands are run until the first command that returns
/// `true`. If enabled, all commands are run irrespective of their return
/// values.
pub fn fallthrough(&self) -> bool {
self.fallthrough
}
/// Set whether fallthrough is active.
///
/// See [`Self::fallthrough`] for more details.
pub fn set_fallthrough(&mut self, active: bool) {
self.fallthrough = active;
}
pub fn add<C>(&mut self, command: C)
@ -28,21 +49,22 @@ impl<B, E> Commands<B, E> {
.collect::<Vec<_>>()
}
/// Returns `true` if a command was found and executed, `false` otherwise.
/// Returns `true` if one or more commands returned `true`, `false`
/// otherwise.
pub async fn handle_packet(
&self,
config: &InstanceConfig,
packet: &ParsedPacket,
snapshot: &Snapshot,
bot: &mut B,
) -> Result<(), E> {
) -> Result<bool, E> {
let msg = match &packet.content {
Ok(Data::SendEvent(SendEvent(msg))) => msg,
_ => return Ok(()),
_ => return Ok(false),
};
let joined = match &snapshot.state {
conn::State::Joining(_) => return Ok(()),
conn::State::Joining(_) => return Ok(false),
conn::State::Joined(joined) => joined.clone(),
};
@ -52,11 +74,15 @@ impl<B, E> Commands<B, E> {
joined,
};
let mut handled = false;
for command in &self.commands {
command.execute(&msg.content, msg, &ctx, bot).await?;
handled = handled || command.execute(&msg.content, msg, &ctx, bot).await?;
if !self.fallthrough && handled {
break;
}
}
Ok(())
Ok(handled)
}
}