use core::future::Future;
use std::{pin::Pin, sync::Arc};
use serenity::all::{
CommandInteraction, ComponentInteraction, Context, CreateCommand, GuildId, Http,
};
use crate::{bot::channel, bot::monitor::Monitor, config::Config, error::Error, shepherd::Live};
#[derive(Clone)]
pub struct State {
pub live: Arc<Live>,
pub config: Arc<Config>,
pub monitor: Arc<Monitor>,
pub board: Option<Arc<channel::Live>>,
}
pub trait Command: Send + Sync {
fn data(&self) -> CreateCommand;
fn name(&self) -> &'static str;
fn run<'a>(
&'a self,
ctx: &'a Context,
interaction: &'a CommandInteraction,
state: &'a State,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'a>>;
fn autocomplete<'a>(
&'a self,
_ctx: &'a Context,
_interaction: &'a CommandInteraction,
_state: &'a State,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
Box::pin(async {})
}
fn handles_button(&self, _custom_id: &str) -> bool {
false
}
fn button<'a>(
&'a self,
_ctx: &'a Context,
_interaction: &'a ComponentInteraction,
_state: &'a State,
) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + 'a>> {
Box::pin(async { Ok(()) })
}
}
#[must_use]
pub fn registry() -> Vec<Box<dyn Command>> {
vec![
Box::new(crate::bot::commands::system::System),
Box::new(crate::bot::commands::shep::ShepCommand),
Box::new(crate::bot::commands::monitor::MonitorCommand),
]
}
pub async fn register(
http: &Http,
guild_id: GuildId,
commands: &[Box<dyn Command>],
) -> Result<Vec<String>, Box<serenity::Error>> {
let payload: Vec<CreateCommand> = commands.iter().map(|command| command.data()).collect();
let registered = guild_id.set_commands(http, payload).await?;
Ok(registered.into_iter().map(|command| command.name).collect())
}
#[cfg(test)]
mod tests {
use serenity::all::Permissions;
use super::*;
#[test]
fn every_registered_command_is_admin_gated() {
let admin = Permissions::ADMINISTRATOR.bits().to_string();
for command in registry() {
let json = serde_json::to_value(command.data()).expect("json");
assert_eq!(
json["default_member_permissions"], admin,
"{} is not permission gated",
json["name"]
);
}
}
#[test]
fn no_two_commands_share_a_name() {
let mut names: Vec<String> = registry()
.iter()
.map(|c| {
serde_json::to_value(c.data()).expect("json")["name"]
.as_str()
.expect("name")
.to_owned()
})
.collect();
names.sort();
let count = names.len();
names.dedup();
assert_eq!(
names.len(),
count,
"a duplicate name silently shadows a command"
);
}
#[test]
fn no_registered_commands_json_carries_a_dash_at_any_depth() {
for command in registry() {
let json = serde_json::to_value(command.data()).expect("json");
crate::test_support::assert_no_dashes_deep(&json);
}
}
#[test]
fn no_registered_command_exceeds_a_registration_cap() {
for command in registry() {
let json = serde_json::to_value(command.data()).expect("json");
crate::test_support::assert_registration_lengths(&json);
}
}
#[test]
fn the_registry_carries_every_command_this_dog_answers() {
let names: Vec<&'static str> = registry().iter().map(|c| c.name()).collect();
assert_eq!(names, vec!["system", "shep", "monitor"]);
}
}