subcommand group syntax

This commit is contained in:
jellywx 2021-09-24 12:55:35 +01:00
parent d84d7ab62b
commit 379e488f7a
6 changed files with 290 additions and 596 deletions

View File

@ -2,6 +2,7 @@ pub mod suffixes {
pub const COMMAND: &str = "COMMAND";
pub const ARG: &str = "ARG";
pub const SUBCOMMAND: &str = "SUBCOMMAND";
pub const SUBCOMMAND_GROUP: &str = "GROUP";
pub const CHECK: &str = "CHECK";
pub const HOOK: &str = "HOOK";
}

View File

@ -36,6 +36,13 @@ macro_rules! match_options {
#[proc_macro_attribute]
pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
enum LastItem {
Fun,
SubFun,
SubGroup,
SubGroupFun,
}
let mut fun = parse_macro_input!(input as CommandFun);
let _name = if !attr.is_empty() {
@ -46,6 +53,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut hooks: Vec<Ident> = Vec::new();
let mut options = Options::new();
let mut last_desc = LastItem::Fun;
for attribute in &fun.attributes {
let span = attribute.span();
@ -56,27 +64,77 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
match name {
"subcommand" => {
options
.subcommands
.push(Subcommand::new(propagate_err!(attributes::parse(values))));
let new_subcommand = Subcommand::new(propagate_err!(attributes::parse(values)));
if let Some(subcommand_group) = options.subcommand_groups.last_mut() {
last_desc = LastItem::SubGroupFun;
subcommand_group.subcommands.push(new_subcommand);
} else {
last_desc = LastItem::SubFun;
options.subcommands.push(new_subcommand);
}
}
"subcommandgroup" => {
let new_group = SubcommandGroup::new(propagate_err!(attributes::parse(values)));
last_desc = LastItem::SubGroup;
options.subcommand_groups.push(new_group);
}
"arg" => {
if let Some(subcommand_group) = options.subcommand_groups.last_mut() {
if let Some(subcommand) = subcommand_group.subcommands.last_mut() {
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
} else {
if let Some(subcommand) = options.subcommands.last_mut() {
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
} else {
options.cmd_args.push(propagate_err!(attributes::parse(values)));
}
}
} else {
if let Some(subcommand) = options.subcommands.last_mut() {
subcommand.cmd_args.push(propagate_err!(attributes::parse(values)));
} else {
options.cmd_args.push(propagate_err!(attributes::parse(values)));
}
}
}
"example" => {
options.examples.push(propagate_err!(attributes::parse(values)));
}
"description" => {
let line: String = propagate_err!(attributes::parse(values));
if let Some(subcommand) = options.subcommands.last_mut() {
util::append_line(&mut subcommand.description, line);
} else {
match last_desc {
LastItem::Fun => {
util::append_line(&mut options.description, line);
}
LastItem::SubFun => {
util::append_line(
&mut options.subcommands.last_mut().unwrap().description,
line,
);
}
LastItem::SubGroup => {
util::append_line(
&mut options.subcommand_groups.last_mut().unwrap().description,
line,
);
}
LastItem::SubGroupFun => {
util::append_line(
&mut options
.subcommand_groups
.last_mut()
.unwrap()
.subcommands
.last_mut()
.unwrap()
.description,
line,
);
}
}
}
"hook" => {
hooks.push(propagate_err!(attributes::parse(values)));
@ -101,120 +159,81 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
supports_dm,
mut cmd_args,
mut subcommands,
mut subcommand_groups,
} = options;
let visibility = fun.visibility;
let name = fun.name.clone();
let body = fun.body;
let n = name.with_suffix(COMMAND);
let cooked = fun.cooked.clone();
let root_ident = name.with_suffix(COMMAND);
let command_path = quote!(crate::framework::Command);
let arg_path = quote!(crate::framework::Arg);
let subcommand_path = ApplicationCommandOptionType::SubCommand;
populate_fut_lifetimes_on_refs(&mut fun.args);
let args = fun.args;
let mut subcommand_group_idents = subcommand_groups
.iter()
.map(|subcommand| {
root_ident
.with_suffix(subcommand.name.replace("-", "_").as_str())
.with_suffix(SUBCOMMAND_GROUP)
})
.collect::<Vec<Ident>>();
let mut subcommand_idents = subcommands
.iter()
.map(|subcommand| {
n.with_suffix(subcommand.name.replace("-", "_").as_str()).with_suffix(SUBCOMMAND)
root_ident
.with_suffix(subcommand.name.replace("-", "_").as_str())
.with_suffix(SUBCOMMAND)
})
.collect::<Vec<Ident>>();
let mut tokens = subcommands
.iter_mut()
.zip(subcommand_idents.iter())
.map(|(subcommand, sc_ident)| {
let arg_idents = subcommand
.cmd_args
.iter()
.map(|arg| {
n.with_suffix(subcommand.name.as_str())
.with_suffix(arg.name.as_str())
.with_suffix(ARG)
})
.collect::<Vec<Ident>>();
let mut tokens = subcommand
.cmd_args
.iter_mut()
.zip(arg_idents.iter())
.map(|(arg, ident)| {
let Arg { name, description, kind, required } = arg;
quote! {
#(#cooked)*
#[allow(missing_docs)]
pub static #ident: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #kind,
required: #required,
options: &[]
};
}
})
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
});
let Subcommand { name, description, .. } = subcommand;
tokens.extend(quote! {
#(#cooked)*
#[allow(missing_docs)]
pub static #sc_ident: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #subcommand_path,
required: false,
options: &[#(&#arg_idents),*],
};
});
tokens
})
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
});
let mut arg_idents = cmd_args
.iter()
.map(|arg| n.with_suffix(arg.name.replace("-", "_").as_str()).with_suffix(ARG))
.map(|arg| root_ident.with_suffix(arg.name.replace("-", "_").as_str()).with_suffix(ARG))
.collect::<Vec<Ident>>();
let arg_tokens = cmd_args
.iter_mut()
.zip(arg_idents.iter())
.map(|(arg, ident)| {
let Arg { name, description, kind, required } = arg;
let mut tokens = quote! {};
quote! {
#(#cooked)*
#[allow(missing_docs)]
pub static #ident: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #kind,
required: #required,
options: &[],
};
}
})
tokens.extend(
subcommand_groups
.iter_mut()
.zip(subcommand_group_idents.iter())
.map(|(group, group_ident)| group.as_tokens(group_ident))
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
});
}),
);
tokens.extend(arg_tokens);
tokens.extend(
subcommands
.iter_mut()
.zip(subcommand_idents.iter())
.map(|(subcommand, sc_ident)| subcommand.as_tokens(sc_ident))
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
}),
);
tokens.extend(
cmd_args.iter_mut().zip(arg_idents.iter()).map(|(arg, ident)| arg.as_tokens(ident)).fold(
quote! {},
|mut a, b| {
a.extend(b);
a
},
),
);
arg_idents.append(&mut subcommand_group_idents);
arg_idents.append(&mut subcommand_idents);
let args = fun.args;
let variant = if args.len() == 2 {
quote!(crate::framework::CommandFnType::Multi)
} else {
@ -230,9 +249,8 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
};
tokens.extend(quote! {
#(#cooked)*
#[allow(missing_docs)]
pub static #n: #command_path = #command_path {
pub static #root_ident: #command_path = #command_path {
fun: #variant(#name),
names: &[#_name, #(#aliases),*],
desc: #description,
@ -243,10 +261,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
args: &[#(&#arg_idents),*],
hooks: &[#(&#hooks),*],
};
});
tokens.extend(quote! {
#(#cooked)*
#[allow(missing_docs)]
#visibility fn #name<'fut> (#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, ()> {
use ::serenity::futures::future::FutureExt;
@ -269,7 +284,6 @@ pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
let fn_name = n.with_suffix(CHECK);
let visibility = fun.visibility;
let cooked = fun.cooked;
let body = fun.body;
let ret = fun.ret;
populate_fut_lifetimes_on_refs(&mut fun.args);
@ -279,7 +293,6 @@ pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
let uuid = Uuid::new_v4().as_u128();
(quote! {
#(#cooked)*
#[allow(missing_docs)]
#visibility fn #fn_name<'fut>(#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, #ret> {
use ::serenity::futures::future::FutureExt;
@ -291,7 +304,6 @@ pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream {
}.boxed()
}
#(#cooked)*
#[allow(missing_docs)]
pub static #name: #hook_path = #hook_path {
fun: #fn_name,

View File

@ -7,7 +7,10 @@ use syn::{
Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility,
};
use crate::util::{Argument, Parenthesised};
use crate::{
consts::{ARG, SUBCOMMAND},
util::{Argument, IdentExt2, Parenthesised},
};
fn parse_argument(arg: FnArg) -> Result<Argument> {
match arg {
@ -38,43 +41,12 @@ fn parse_argument(arg: FnArg) -> Result<Argument> {
}
}
/// Test if the attribute is cooked.
fn is_cooked(attr: &Attribute) -> bool {
const COOKED_ATTRIBUTE_NAMES: &[&str] =
&["cfg", "cfg_attr", "derive", "inline", "allow", "warn", "deny", "forbid"];
COOKED_ATTRIBUTE_NAMES.iter().any(|n| attr.path.is_ident(n))
}
/// Removes cooked attributes from a vector of attributes. Uncooked attributes are left in the vector.
///
/// # Return
///
/// Returns a vector of cooked attributes that have been removed from the input vector.
fn remove_cooked(attrs: &mut Vec<Attribute>) -> Vec<Attribute> {
let mut cooked = Vec::new();
// FIXME: Replace with `Vec::drain_filter` once it is stable.
let mut i = 0;
while i < attrs.len() {
if !is_cooked(&attrs[i]) {
i += 1;
continue;
}
cooked.push(attrs.remove(i));
}
cooked
}
#[derive(Debug)]
pub struct CommandFun {
/// `#[...]`-style attributes.
pub attributes: Vec<Attribute>,
/// Populated cooked attributes. These are attributes outside of the realm of this crate's procedural macros
/// and will appear in generated output.
pub cooked: Vec<Attribute>,
pub visibility: Visibility,
pub name: Ident,
pub args: Vec<Argument>,
@ -84,9 +56,7 @@ pub struct CommandFun {
impl Parse for CommandFun {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let mut attributes = input.call(Attribute::parse_outer)?;
let cooked = remove_cooked(&mut attributes);
let attributes = input.call(Attribute::parse_outer)?;
let visibility = input.parse::<Visibility>()?;
@ -110,16 +80,15 @@ impl Parse for CommandFun {
let args = args.into_iter().map(parse_argument).collect::<Result<Vec<_>>>()?;
Ok(Self { attributes, cooked, visibility, name, args, ret, body })
Ok(Self { attributes, visibility, name, args, ret, body })
}
}
impl ToTokens for CommandFun {
fn to_tokens(&self, stream: &mut TokenStream2) {
let Self { attributes: _, cooked, visibility, name, args, ret, body } = self;
let Self { attributes: _, visibility, name, args, ret, body } = self;
stream.extend(quote! {
#(#cooked)*
#visibility async fn #name (#(#args),*) -> #ret {
#(#body)*
}
@ -193,6 +162,24 @@ pub(crate) struct Arg {
pub required: bool,
}
impl Arg {
pub fn as_tokens(&self, ident: &Ident) -> TokenStream2 {
let arg_path = quote!(crate::framework::Arg);
let Arg { name, description, kind, required } = self;
quote! {
#[allow(missing_docs)]
pub static #ident: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #kind,
required: #required,
options: &[]
};
}
}
}
impl Default for Arg {
fn default() -> Self {
Self {
@ -211,6 +198,44 @@ pub(crate) struct Subcommand {
pub cmd_args: Vec<Arg>,
}
impl Subcommand {
pub fn as_tokens(&mut self, ident: &Ident) -> TokenStream2 {
let arg_path = quote!(crate::framework::Arg);
let subcommand_path = ApplicationCommandOptionType::SubCommand;
let arg_idents = self
.cmd_args
.iter()
.map(|arg| ident.with_suffix(arg.name.as_str()).with_suffix(ARG))
.collect::<Vec<Ident>>();
let mut tokens = self
.cmd_args
.iter_mut()
.zip(arg_idents.iter())
.map(|(arg, ident)| arg.as_tokens(ident))
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
});
let Subcommand { name, description, .. } = self;
tokens.extend(quote! {
#[allow(missing_docs)]
pub static #ident: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #subcommand_path,
required: false,
options: &[#(&#arg_idents),*],
};
});
tokens
}
}
impl Default for Subcommand {
fn default() -> Self {
Self { name: String::new(), description: String::new(), cmd_args: vec![] }
@ -223,6 +248,68 @@ impl Subcommand {
}
}
#[derive(Debug)]
pub(crate) struct SubcommandGroup {
pub name: String,
pub description: String,
pub subcommands: Vec<Subcommand>,
}
impl SubcommandGroup {
pub fn as_tokens(&mut self, ident: &Ident) -> TokenStream2 {
let arg_path = quote!(crate::framework::Arg);
let subcommand_group_path = ApplicationCommandOptionType::SubCommandGroup;
let arg_idents = self
.subcommands
.iter()
.map(|arg| {
ident
.with_suffix(self.name.as_str())
.with_suffix(arg.name.as_str())
.with_suffix(SUBCOMMAND)
})
.collect::<Vec<Ident>>();
let mut tokens = self
.subcommands
.iter_mut()
.zip(arg_idents.iter())
.map(|(subcommand, ident)| subcommand.as_tokens(ident))
.fold(quote! {}, |mut a, b| {
a.extend(b);
a
});
let SubcommandGroup { name, description, .. } = self;
tokens.extend(quote! {
#[allow(missing_docs)]
pub static #ident: #arg_path = #arg_path {
name: #name,
description: #description,
kind: #subcommand_group_path,
required: false,
options: &[#(&#arg_idents),*],
};
});
tokens
}
}
impl Default for SubcommandGroup {
fn default() -> Self {
Self { name: String::new(), description: String::new(), subcommands: vec![] }
}
}
impl SubcommandGroup {
pub(crate) fn new(name: String) -> Self {
Self { name, ..Default::default() }
}
}
#[derive(Debug, Default)]
pub(crate) struct Options {
pub aliases: Vec<String>,
@ -233,6 +320,7 @@ pub(crate) struct Options {
pub supports_dm: bool,
pub cmd_args: Vec<Arg>,
pub subcommands: Vec<Subcommand>,
pub subcommand_groups: Vec<SubcommandGroup>,
}
impl Options {

View File

@ -1,4 +1,4 @@
pub mod info_cmds;
pub mod moderation_cmds;
pub mod reminder_cmds;
//pub mod todo_cmds;
pub mod todo_cmds;

View File

@ -1,443 +1,44 @@
use std::{convert::TryFrom, fmt};
use regex_command_attr::command;
use serenity::{
async_trait,
client::Context,
constants::MESSAGE_CODE_LIMIT,
model::{
channel::Message,
id::{ChannelId, GuildId, UserId},
},
};
use sqlx::MySqlPool;
use serenity::client::Context;
use crate::{
command_help, get_ctx_data,
models::{user_data::UserData, CtxData},
};
use crate::framework::{CommandInvoke, CommandOptions};
#[derive(Debug)]
struct TodoNotFound;
impl std::error::Error for TodoNotFound {}
impl fmt::Display for TodoNotFound {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Todo not found")
}
}
struct Todo {
id: u32,
value: String,
}
struct TodoTarget {
user: UserId,
guild: Option<GuildId>,
channel: Option<ChannelId>,
}
impl TodoTarget {
pub fn command(&self, subcommand_opt: Option<SubCommand>) -> String {
let context = if self.channel.is_some() {
"channel"
} else if self.guild.is_some() {
"guild"
} else {
"user"
};
if let Some(subcommand) = subcommand_opt {
format!("todo {} {}", context, subcommand.to_string())
} else {
format!("todo {}", context)
}
}
pub fn name(&self) -> String {
if self.channel.is_some() {
"Channel"
} else if self.guild.is_some() {
"Guild"
} else {
"User"
}
.to_string()
}
pub async fn view(
&self,
pool: MySqlPool,
) -> Result<Vec<Todo>, Box<dyn std::error::Error + Send + Sync>> {
Ok(if let Some(cid) = self.channel {
sqlx::query_as!(
Todo,
"
SELECT id, value FROM todos WHERE channel_id = (SELECT id FROM channels WHERE channel = ?)
",
cid.as_u64()
)
.fetch_all(&pool)
.await?
} else if let Some(gid) = self.guild {
sqlx::query_as!(
Todo,
"
SELECT id, value FROM todos WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND channel_id IS NULL
",
gid.as_u64()
)
.fetch_all(&pool)
.await?
} else {
sqlx::query_as!(
Todo,
"
SELECT id, value FROM todos WHERE user_id = (SELECT id FROM users WHERE user = ?) AND guild_id IS NULL
",
self.user.as_u64()
)
.fetch_all(&pool)
.await?
})
}
pub async fn add(
&self,
value: String,
pool: MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if let (Some(cid), Some(gid)) = (self.channel, self.guild) {
sqlx::query!(
"
INSERT INTO todos (user_id, guild_id, channel_id, value) VALUES (
(SELECT id FROM users WHERE user = ?),
(SELECT id FROM guilds WHERE guild = ?),
(SELECT id FROM channels WHERE channel = ?),
?
)
",
self.user.as_u64(),
gid.as_u64(),
cid.as_u64(),
value
)
.execute(&pool)
.await?;
} else if let Some(gid) = self.guild {
sqlx::query!(
"
INSERT INTO todos (user_id, guild_id, value) VALUES (
(SELECT id FROM users WHERE user = ?),
(SELECT id FROM guilds WHERE guild = ?),
?
)
",
self.user.as_u64(),
gid.as_u64(),
value
)
.execute(&pool)
.await?;
} else {
sqlx::query!(
"
INSERT INTO todos (user_id, value) VALUES (
(SELECT id FROM users WHERE user = ?),
?
)
",
self.user.as_u64(),
value
)
.execute(&pool)
.await?;
}
Ok(())
}
pub async fn remove(
&self,
num: usize,
pool: &MySqlPool,
) -> Result<Todo, Box<dyn std::error::Error + Sync + Send>> {
let todos = self.view(pool.clone()).await?;
if let Some(removal_todo) = todos.get(num) {
let deleting = sqlx::query_as!(
Todo,
"
SELECT id, value FROM todos WHERE id = ?
",
removal_todo.id
)
.fetch_one(&pool.clone())
.await?;
sqlx::query!(
"
DELETE FROM todos WHERE id = ?
",
removal_todo.id
)
.execute(pool)
.await?;
Ok(deleting)
} else {
Err(Box::new(TodoNotFound))
}
}
pub async fn clear(
&self,
pool: &MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Sync + Send>> {
if let Some(cid) = self.channel {
sqlx::query!(
"
DELETE FROM todos WHERE channel_id = (SELECT id FROM channels WHERE channel = ?)
",
cid.as_u64()
)
.execute(pool)
.await?;
} else if let Some(gid) = self.guild {
sqlx::query!(
"
DELETE FROM todos WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND channel_id IS NULL
",
gid.as_u64()
)
.execute(pool)
.await?;
} else {
sqlx::query!(
"
DELETE FROM todos WHERE user_id = (SELECT id FROM users WHERE user = ?) AND guild_id IS NULL
",
self.user.as_u64()
)
.execute(pool)
.await?;
}
Ok(())
}
async fn execute(&self, ctx: &Context, msg: &Message, subcommand: SubCommand, extra: String) {
let (pool, lm) = get_ctx_data(&ctx).await;
let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap();
let prefix = ctx.prefix(msg.guild_id).await;
match subcommand {
SubCommand::View => {
let todo_items = self.view(pool).await.unwrap();
let mut todo_groups = vec!["".to_string()];
let mut char_count = 0;
todo_items.iter().enumerate().for_each(|(count, todo)| {
let display = format!("{}: {}\n", count + 1, todo.value);
if char_count + display.len() > MESSAGE_CODE_LIMIT as usize {
char_count = display.len();
todo_groups.push(display);
} else {
char_count += display.len();
let last_group = todo_groups.pop().unwrap();
todo_groups.push(format!("{}{}", last_group, display));
}
});
for group in todo_groups {
let _ = msg
.channel_id
.send_message(&ctx, |m| {
m.embed(|e| e.title(format!("{} Todo", self.name())).description(group))
})
.await;
}
}
SubCommand::Add => {
let content = lm
.get(&user_data.language, "todo/added")
.replacen("{name}", &extra, 1);
self.add(extra, pool).await.unwrap();
let _ = msg.channel_id.say(&ctx, content).await;
}
SubCommand::Remove => {
if let Ok(num) = extra.parse::<usize>() {
if let Ok(todo) = self.remove(num - 1, &pool).await {
let content = lm.get(&user_data.language, "todo/removed").replacen(
"{}",
&todo.value,
1,
);
let _ = msg.channel_id.say(&ctx, content).await;
} else {
let _ = msg
.channel_id
.say(&ctx, lm.get(&user_data.language, "todo/error_index"))
.await;
}
} else {
let content = lm
.get(&user_data.language, "todo/error_value")
.replacen("{prefix}", &prefix, 1)
.replacen("{command}", &self.command(Some(subcommand)), 1);
let _ = msg.channel_id.say(&ctx, content).await;
}
}
SubCommand::Clear => {
self.clear(&pool).await.unwrap();
let content = lm.get(&user_data.language, "todo/cleared");
let _ = msg.channel_id.say(&ctx, content).await;
}
}
}
}
enum SubCommand {
View,
Add,
Remove,
Clear,
}
impl TryFrom<Option<&str>> for SubCommand {
type Error = ();
fn try_from(value: Option<&str>) -> Result<Self, Self::Error> {
match value {
Some("add") => Ok(SubCommand::Add),
Some("remove") => Ok(SubCommand::Remove),
Some("clear") => Ok(SubCommand::Clear),
None | Some("") => Ok(SubCommand::View),
Some(_unrecognised) => Err(()),
}
}
}
impl ToString for SubCommand {
fn to_string(&self) -> String {
match self {
SubCommand::View => "",
SubCommand::Add => "add",
SubCommand::Remove => "remove",
SubCommand::Clear => "clear",
}
.to_string()
}
}
#[async_trait]
trait Execute {
async fn execute(self, ctx: &Context, msg: &Message, extra: String, target: TodoTarget);
}
#[async_trait]
impl Execute for Result<SubCommand, ()> {
async fn execute(self, ctx: &Context, msg: &Message, extra: String, target: TodoTarget) {
if let Ok(subcommand) = self {
target.execute(ctx, msg, subcommand, extra).await;
} else {
show_help(ctx, msg, Some(target)).await;
}
}
}
#[command("todo")]
async fn todo_user(ctx: &Context, msg: &Message, args: String) {
let mut split = args.split(' ');
let target = TodoTarget {
user: msg.author.id,
guild: None,
channel: None,
};
let subcommand_opt = SubCommand::try_from(split.next());
subcommand_opt
.execute(ctx, msg, split.collect::<Vec<&str>>().join(" "), target)
.await;
}
#[command("todoc")]
#[supports_dm(false)]
#[permission_level(Managed)]
async fn todo_channel(ctx: &Context, msg: &Message, args: String) {
let mut split = args.split(' ');
let target = TodoTarget {
user: msg.author.id,
guild: msg.guild_id,
channel: Some(msg.channel_id),
};
let subcommand_opt = SubCommand::try_from(split.next());
subcommand_opt
.execute(ctx, msg, split.collect::<Vec<&str>>().join(" "), target)
.await;
}
#[command("todos")]
#[supports_dm(false)]
#[permission_level(Managed)]
async fn todo_guild(ctx: &Context, msg: &Message, args: String) {
let mut split = args.split(' ');
let target = TodoTarget {
user: msg.author.id,
guild: msg.guild_id,
channel: None,
};
let subcommand_opt = SubCommand::try_from(split.next());
subcommand_opt
.execute(ctx, msg, split.collect::<Vec<&str>>().join(" "), target)
.await;
}
async fn show_help(ctx: &Context, msg: &Message, target: Option<TodoTarget>) {
let (pool, lm) = get_ctx_data(&ctx).await;
let language = UserData::language_of(&msg.author, &pool);
let prefix = ctx.prefix(msg.guild_id);
let command = match target {
None => "todo",
Some(t) => {
if t.channel.is_some() {
"todoc"
} else if t.guild.is_some() {
"todos"
} else {
"todo"
}
}
};
command_help(ctx, msg, lm, &prefix.await, &language.await, command).await;
}
#[command]
#[description("Manage todo lists")]
#[subcommandgroup("server")]
#[description("Manage the server todo list")]
#[subcommand("add")]
#[description("Add an item to the server todo list")]
#[arg(
name = "task",
description = "The task to add to the todo list",
kind = "String",
required = true
)]
#[subcommand("view")]
#[description("View and remove from the server todo list")]
#[subcommandgroup("channel")]
#[description("Manage the channel todo list")]
#[subcommand("add")]
#[description("Add to the channel todo list")]
#[arg(
name = "task",
description = "The task to add to the todo list",
kind = "String",
required = true
)]
#[subcommand("view")]
#[description("View and remove from the channel todo list")]
#[subcommandgroup("user")]
#[description("Manage your personal todo list")]
#[subcommand("add")]
#[description("Add to your personal todo list")]
#[arg(
name = "task",
description = "The task to add to the todo list",
kind = "String",
required = true
)]
#[subcommand("view")]
#[description("View and remove from your personal todo list")]
async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {}

View File

@ -36,7 +36,7 @@ use sqlx::mysql::MySqlPool;
use tokio::sync::RwLock;
use crate::{
commands::{info_cmds, moderation_cmds, reminder_cmds},
commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
component_models::ComponentDataModel,
consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
framework::RegexFramework,
@ -318,16 +318,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.add_command(&reminder_cmds::PAUSE_COMMAND)
.add_command(&reminder_cmds::OFFSET_COMMAND)
.add_command(&reminder_cmds::NUDGE_COMMAND)
/*
// to-do commands
.add_command("todo", &todo_cmds::TODO_USER_COMMAND)
.add_command("todo user", &todo_cmds::TODO_USER_COMMAND)
.add_command("todoc", &todo_cmds::TODO_CHANNEL_COMMAND)
.add_command("todo channel", &todo_cmds::TODO_CHANNEL_COMMAND)
.add_command("todos", &todo_cmds::TODO_GUILD_COMMAND)
.add_command("todo server", &todo_cmds::TODO_GUILD_COMMAND)
.add_command("todo guild", &todo_cmds::TODO_GUILD_COMMAND)
*/
.add_command(&todo_cmds::TODO_COMMAND)
// moderation commands
.add_command(&moderation_cmds::BLACKLIST_COMMAND)
.add_command(&moderation_cmds::RESTRICT_COMMAND)