aboutsummaryrefslogtreecommitdiff
path: root/src/modules/commands
diff options
context:
space:
mode:
authorFuwn <[email protected]>2020-10-26 19:03:53 -0700
committerFuwn <[email protected]>2020-10-26 19:03:53 -0700
commit9742614a1dc4699c1f2c69d923d402237672335d (patch)
treea49f7d834372f37cef06b30a28ff1b40bdfaa079 /src/modules/commands
parentCreate README.md (diff)
downloaddep-core-next-9742614a1dc4699c1f2c69d923d402237672335d.tar.xz
dep-core-next-9742614a1dc4699c1f2c69d923d402237672335d.zip
repo: push main from local to remote
Diffstat (limited to 'src/modules/commands')
-rw-r--r--src/modules/commands/.todo52
-rw-r--r--src/modules/commands/admins/config.rs756
-rw-r--r--src/modules/commands/admins/ignore.rs168
-rw-r--r--src/modules/commands/admins/management.rs317
-rw-r--r--src/modules/commands/admins/mod.rs85
-rw-r--r--src/modules/commands/admins/premium.rs165
-rw-r--r--src/modules/commands/admins/roles.rs160
-rw-r--r--src/modules/commands/admins/tests.rs77
-rw-r--r--src/modules/commands/general/animals.rs183
-rw-r--r--src/modules/commands/general/fun.rs301
-rw-r--r--src/modules/commands/general/misc.rs320
-rw-r--r--src/modules/commands/general/mod.rs122
-rw-r--r--src/modules/commands/general/nsfw.rs50
-rw-r--r--src/modules/commands/general/roles.rs278
-rw-r--r--src/modules/commands/general/tags.rs168
-rw-r--r--src/modules/commands/general/utilities.rs761
-rw-r--r--src/modules/commands/mod.rs4
-rw-r--r--src/modules/commands/mods/hackbans.rs117
-rw-r--r--src/modules/commands/mods/info.rs49
-rw-r--r--src/modules/commands/mods/kickbans.rs97
-rw-r--r--src/modules/commands/mods/mod.rs80
-rw-r--r--src/modules/commands/mods/mute.rs179
-rw-r--r--src/modules/commands/mods/notes.rs108
-rw-r--r--src/modules/commands/mods/roles.rs338
-rw-r--r--src/modules/commands/mods/watchlist.rs113
-rw-r--r--src/modules/commands/owners/log.rs27
-rw-r--r--src/modules/commands/owners/mod.rs14
-rw-r--r--src/modules/commands/owners/premium.rs76
28 files changed, 5165 insertions, 0 deletions
diff --git a/src/modules/commands/.todo b/src/modules/commands/.todo
new file mode 100644
index 0000000..487377b
--- /dev/null
+++ b/src/modules/commands/.todo
@@ -0,0 +1,52 @@
+Key:
+ * - Basically in.
+ / - Maybe...
+
+Commands:
+ Anime:
+ - [/] Darling
+ - [*] Waifu
+ Bot:
+ - [*] Donate
+ - [*] Suggest
+ Emma:
+ - [/] Fan Art
+ - [/] Ugly Cat
+ - [/] Verify
+ Fun:
+ - [/] Advice
+ - [/] Date Fact
+ - [/] Day Fact
+ - [/] FML
+ - [/] Fact
+ - [/] GitHub Zen
+ - [/] Insult
+ - [/] Minecraft Server Status
+ - [/] Number Fact
+ - [/] Onion
+ - [/] Spoiler
+ - [/] Year Fact
+ Moderation:
+ - [ ] Slow-mode
+ NSFW:
+ - [ ] Danbooru
+ - [ ] Gelbooru
+ - [ ] Rule 34
+ Management:
+ - [ ] DM
+ - [/] Database
+ - [/] IP
+ - [ ] Leave Server
+ - [ ] Reload
+ - [/] Server Count
+ - [/] Status
+ - [/] Username
+ Reaction:
+ - [/] List
+ - [/] New
+ - [/] Remove
+ Server:
+ - [ ] Goodbye
+ - [ ] Oldest Member
+ - [/] Poll
+ - [ ] Random Member
diff --git a/src/modules/commands/admins/config.rs b/src/modules/commands/admins/config.rs
new file mode 100644
index 0000000..a26fc3d
--- /dev/null
+++ b/src/modules/commands/admins/config.rs
@@ -0,0 +1,756 @@
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct ConfigRaw;
+impl Command for ConfigRaw {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Oh, you want it, raw...".to_string()),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ message.channel_id.say(format!("{:?}", guild_data))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigList;
+impl Command for ConfigList {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Yeah, sure, here's my config.".to_string()),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(*colours::MAIN)
+ .description(format!("{}", guild_data))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigPrefix;
+impl Command for ConfigPrefix {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Change it, go ahead.".to_string()),
+ usage: Some("<prefix>".to_string()),
+ example: Some("!!".to_string()),
+ min_args: Some(1),
+ max_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let pre = args.single::<String>()?;
+ guild_data.prefix = pre;
+ match db.update_guild(guild_id.0 as i64, guild_data) {
+ Ok(guild) => {
+ message.channel_id.say(format!("Set prefix to {}", guild.prefix))?;
+ },
+ Err(_) => {
+ message.channel_id.say("Failed to change prefix")?;
+ },
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigAutorole;
+impl Command for ConfigAutorole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Want to mess with my auto-role settings ? (A role must be provided for `add` or `remove`.)".to_string()),
+ usage: Some("<add|remove|enable|disable> <role_resolvable|_>".to_string()),
+ example: Some("add member".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "add" => {
+ match parse_role(val.to_string(), guild_id) {
+ Some((role_id, role)) => {
+ guild_data.autoroles.push(role_id.0 as i64);
+ val = format!("{} ({})", role.name, role_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that role.")?;
+ return Ok(())
+ },
+ }
+ },
+ "remove" => {
+ match parse_role(val.to_string(), guild_id) {
+ Some((role_id, role)) => {
+ guild_data.autoroles.retain(|e| *e != role_id.0 as i64);
+ val = format!("{} ({})", role.name, role_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that role.")?;
+ return Ok(())
+ },
+ }
+ },
+ "enable" => {
+ guild_data.autorole = true;
+ },
+ "disable" => {
+ guild_data.autorole = false;
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `add`, `remove`, `enable`, `disable`. For more information see `help config autorole`")?;
+ return Ok(())
+ },
+ }
+ let guild = db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Autorole Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ if val.is_empty() { guild.autorole.to_string() } else { val } ,
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigAdmin;
+impl Command for ConfigAdmin {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Who do you want me to consider an admin ?".to_string()),
+ usage: Some("<add|remove> <role_resolvable>".to_string()),
+ example: Some("add admin".to_string()),
+ min_args: Some(2),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "add" => {
+ match parse_role(val.to_string(), guild_id) {
+ Some((role_id, role)) => {
+ guild_data.admin_roles.push(role_id.0 as i64);
+ val = format!("{} ({})", role.name, role_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that role.")?;
+ return Ok(())
+ },
+ }
+ },
+ "remove" => {
+ match parse_role(val.to_string(), guild_id) {
+ Some((role_id, role)) => {
+ guild_data.admin_roles.retain(|e| *e != role_id.0 as i64);
+ val = format!("{} ({})", role.name, role_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that role.")?;
+ return Ok(())
+ },
+ }
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `add`, `remove`. For more information see `help config admin`")?;
+ return Ok(())
+ },
+ }
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Admin Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ val,
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigMod;
+impl Command for ConfigMod {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Welcome to moderation !".to_string()),
+ usage: Some("<add|remove> <role_resolvable>".to_string()),
+ example: Some("add staff".to_string()),
+ min_args: Some(2),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "add" => {
+ match parse_role(val.to_string(), guild_id) {
+ Some((role_id, role)) => {
+ guild_data.mod_roles.push(role_id.0 as i64);
+ val = format!("{} ({})", role.name, role_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that role.")?;
+ return Ok(())
+ },
+ }
+ },
+ "remove" => {
+ match parse_role(val.to_string(), guild_id) {
+ Some((role_id, role)) => {
+ guild_data.mod_roles.retain(|e| *e != role_id.0 as i64);
+ val = format!("{} ({})", role.name, role_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that role.")?;
+ return Ok(())
+ },
+ }
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `add`, `remove`. For more information see `help config mod`")?;
+ return Ok(())
+ },
+ }
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Mod Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ val,
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigAudit;
+impl Command for ConfigAudit {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Amping up security around here ? Change audit log settings. (A channel must be provided for `channel`.)".to_string()),
+ usage: Some("<enable|disable|channel> <channel_resolvable>".to_string()),
+ example: Some("channel #audit-logs".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.audit = true;
+ },
+ "disable" => {
+ guild_data.audit = false;
+ },
+ "channel" => {
+ match parse_channel(val.to_string(), guild_id) {
+ Some((channel_id, channel)) => {
+ guild_data.audit_channel = channel_id.0 as i64;
+ val = format!("{} ({})", channel.name, channel_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that channel.")?;
+ return Ok(())
+ },
+ }
+ },
+ "threshold" => {
+ match val.parse::<i16>() {
+ Ok(th) => {
+ guild_data.audit_threshold = th;
+ val = th.to_string();
+ },
+ Err(_) => { message.channel_id.say("Please input a number as the threshold")?; }
+ }
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`, `channel`, `threshold`. For more information see `help config audit`")?;
+ return Ok(())
+ },
+ }
+ let guild = db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Audit Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ if val.is_empty() { format!("{}", guild.audit) } else { val },
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigModlog;
+impl Command for ConfigModlog {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Moderation log settings, move along. (A channel must be provided for `channel`.)".to_string()),
+ usage: Some("<enable|disable|channel> <channel_resolvable>".to_string()),
+ example: Some("channel #mod-logs".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.modlog = true;
+ },
+ "disable" => {
+ guild_data.modlog = false;
+ },
+ "channel" => {
+ match parse_channel(val.to_string(), guild_id) {
+ Some((channel_id, channel)) => {
+ guild_data.modlog_channel = channel_id.0 as i64;
+ val = format!("{} ({})", channel.name, channel_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that channel.")?;
+ return Ok(())
+ },
+ }
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`, `channel`. For more information see `help config modlog`")?;
+ return Ok(())
+ },
+ }
+ let guild = db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Modlog Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ if val.is_empty() { guild.modlog.to_string() } else { val },
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigWelcome;
+impl Command for ConfigWelcome {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Change welcome message settings.\nOption is one of enable, disable, channel, message, type and the respective values should be none, none, channel_resolvable, desired message.\nType designates if the message is plain or embed. Anything other than embed will result in plain.".to_string()),
+ desc: Some("Want a sick, custom welcome message !?\n\nOptions:\n`enable` - none.\n`disable` - none.\n`channel` - `channel_resolvable` (Mentionable).\n`message` - Your desired welcome message.\n`type` - `plain` or `embed`.".to_string()),
+ usage: Some("<option> <value>".to_string()),
+ example: Some("message Hey {user} ! Welcome to {guild} !".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.welcome = true;
+ },
+ "disable" => {
+ guild_data.welcome = false;
+ },
+ "channel" => {
+ match parse_channel(val.to_string(), guild_id) {
+ Some((channel_id, channel)) => {
+ guild_data.welcome_channel = channel_id.0 as i64;
+ val = format!("{} ({})", channel.name, channel_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that channel.")?;
+ return Ok(())
+ },
+ }
+ },
+ "message" => {
+ guild_data.welcome_message = val.to_string();
+ },
+ "type" => {
+ guild_data.welcome_type = val.to_string();
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`, `channel`, `message`, `type`. For more information see `help config welcome`")?;
+ return Ok(())
+ },
+ }
+ let guild = db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Welcome Configuration Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ if val.is_empty() { guild.welcome.to_string() } else { val },
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigGoodbye;
+impl Command for ConfigGoodbye {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Change welcome message settings.\nOption is one of enable, disable, channel, message, type and the respective values should be none, none, channel_resolvable, desired message.\nType designates if the message is plain or embed. Anything other than embed will result in plain.".to_string()),
+ desc: Some("People leaving you ? At least make it cool and custom.\n\nOptions:\n`enable` - none.\n`disable` - none.\n`channel` - `channel_resolvable` (Mentionable).\n`message` - Your desired welcome message.\n`type` - `plain` or `embed`.".to_string()),
+ usage: Some("<option> <value>".to_string()),
+ example: Some("message {user} has left {guild}...".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.welcome = true;
+ },
+ "disable" => {
+ guild_data.welcome = false;
+ },
+ "channel" => {
+ match parse_channel(val.to_string(), guild_id) {
+ Some((channel_id, channel)) => {
+ guild_data.welcome_channel = channel_id.0 as i64;
+ val = format!("{} ({})", channel.name, channel_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that channel.")?;
+ return Ok(())
+ },
+ }
+ },
+ "message" => {
+ guild_data.welcome_message = val.to_string();
+ },
+ "type" => {
+ guild_data.welcome_type = val.to_string();
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`, `channel`, `message`, `type`. For more information see `help config goodbye`")?;
+ return Ok(())
+ },
+ }
+ let guild = db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Goodbye Configuration Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ if val.is_empty() { guild.welcome.to_string() } else { val },
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigIntroduction;
+impl Command for ConfigIntroduction {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Change introduction message settings. This is exactly like welcome: `help config welcome` for more info. This is a premium only feature related to the Register command.".to_string()),
+ usage: Some("<option> <value>".to_string()),
+ example: Some("message Hey there {user}, mind introducting yourself?".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.introduction = true;
+ },
+ "disable" => {
+ guild_data.introduction = false;
+ },
+ "channel" => {
+ match parse_channel(val.to_string(), guild_id) {
+ Some((channel_id, channel)) => {
+ guild_data.introduction_channel = channel_id.0 as i64;
+ val = format!("{} ({})", channel.name, channel_id.0);
+ },
+ None => {
+ message.channel_id.say("I couldn't find that channel.")?;
+ return Ok(())
+ },
+ }
+ },
+ "message" => {
+ guild_data.introduction_message = val.to_string();
+ },
+ "type" => {
+ guild_data.introduction_type = val.to_string();
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`, `channel`, `message`, `type`. For more information see `help config introduction`")?;
+ return Ok(())
+ },
+ }
+ let guild = db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Introduction Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ if val.is_empty() { guild.introduction.to_string() } else { val },
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigCommands;
+impl Command for ConfigCommands {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Find something annoying ? Disable it ! (A command name must be provided.)".to_string()),
+ usage: Some("<enable|disable> <command_name>".to_string()),
+ example: Some("disable e621".to_string()),
+ min_args: Some(2),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.commands.retain(|e| *e != val);
+ },
+ "disable" => {
+ if !val.starts_with("conf") {
+ guild_data.commands.push(val.clone());
+ } else {
+ message.channel_id.say("Config commands cannot be disabled.")?;
+ return Ok(());
+ }
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`. For more information see `help config command`")?;
+ return Ok(())
+ },
+ }
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Command Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ val,
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct ConfigLogs;
+impl Command for ConfigLogs {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Change which log messages are disabled. A log type must be provided.".to_string()),
+ usage: Some("<enable|disable|types> [type]".to_string()),
+ example: Some("disable message_edit".to_string()),
+ min_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let op = args.single::<String>().unwrap_or(String::new());
+ let val = args.rest().to_string();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ guild_data.logging.retain(|e| *e != val);
+ },
+ "disable" => {
+ if LOG_TYPES.contains(&val.as_str()) {
+ guild_data.logging.push(val.clone());
+ } else {
+ message.channel_id.say("Invalid log type. See `config log types` for valid types.")?;
+ return Ok(());
+ }
+ },
+ "types" => {
+ message.channel_id.say(LOG_TYPES.iter()
+ .map(|e| format!("`{}`", e))
+ .collect::<Vec<String>>()
+ .join(", "))?;
+ return Ok(());
+ },
+ _ => {
+ message.channel_id.say("I didn't understand that option. Valid options are: `enable`, `disable`. For more information see `help config log`")?;
+ return Ok(())
+ },
+ }
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Config Log Summary")
+ .colour(*colours::MAIN)
+ .description(format!("**Operation:** {}\n**Value:** {}",
+ op,
+ val,
+ ))
+ ))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/admins/ignore.rs b/src/modules/commands/admins/ignore.rs
new file mode 100644
index 0000000..efafdac
--- /dev/null
+++ b/src/modules/commands/admins/ignore.rs
@@ -0,0 +1,168 @@
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct IgnoreAdd;
+impl Command for IgnoreAdd {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Want me to ignore a channel ? Don't worry, I ain't seen a thing.".to_string()),
+ usage: Some("<channel_resolvable>".to_string()),
+ example: Some("#general".to_string()),
+ min_args: Some(1),
+ max_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ if let Some((channel_id, channel)) = parse_channel(args.full().to_string(), guild_id) {
+ if !guild_data.ignored_channels.contains(&(channel_id.0 as i64)) {
+ guild_data.ignored_channels.push(channel_id.0 as i64);
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.say(format!(
+ "I will now ignore messages in {}",
+ channel.name
+ ))?;
+ } else {
+ message.channel_id.say("That channel is already being ignored.")?;
+ }
+ } else {
+ message.channel_id.say("I couldn't find that channel.")?;
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct IgnoreRemove;
+impl Command for IgnoreRemove {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Finally, I can see.".to_string()),
+ usage: Some("<channel_resolvable>".to_string()),
+ example: Some("#general".to_string()),
+ min_args: Some(1),
+ max_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ if let Some((channel_id, channel)) = parse_channel(args.full().to_string(), guild_id) {
+ if guild_data.ignored_channels.contains(&(channel_id.0 as i64)) {
+ guild_data.ignored_channels.retain(|e| *e != channel_id.0 as i64);
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.say(format!(
+ "I will no longer ignore messages in {}",
+ channel.name
+ ))?;
+ } else {
+ message.channel_id.say("That channel isn't being ignored.")?;
+ }
+ } else {
+ message.channel_id.say("I couldn't find that channel.")?;
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct IgnoreList;
+impl Command for IgnoreList {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("You want ME to tell YOU want channels I'm ignoring ?.".to_string()),
+ required_permissions: Permissions::MANAGE_GUILD,
+ max_args: Some(0),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ if !guild_data.ignored_channels.is_empty() {
+ let channel_out = guild_data.ignored_channels.clone()
+ .iter()
+ .map(|c| format!("<#{}>", c))
+ .collect::<Vec<String>>()
+ .join("\n");
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Ignored Channels")
+ .description(channel_out)
+ .colour(*colours::MAIN)
+ ))?;
+ } else {
+ message.channel_id.say("I'm not ignoring any channels.")?;
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct IgnoreLevel;
+impl Command for IgnoreLevel {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Change which ranks can bypass ignored channels, epic.\n\nValues:\n`4` = Bot Owner, `3` = Guild Owner, `2` = Guild Admin, `1` = Guild Mod, `0` = Everyone.".to_string()),
+ usage: Some("<0..4>".to_string()),
+ example: Some("2".to_string()),
+ min_args: Some(1),
+ max_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ match args.single::<i16>() {
+ Ok(level) => {
+ guild_data.ignore_level = level;
+ db.update_guild(guild_id.0 as i64, guild_data)?;
+ message.channel_id.say(format!("Successfully set ignore level to {}", level))?;
+ },
+ Err(_) => {
+ message.channel_id.say("Please enter an integer between 0 and 4.")?;
+ },
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/admins/management.rs b/src/modules/commands/admins/management.rs
new file mode 100644
index 0000000..cad1365
--- /dev/null
+++ b/src/modules/commands/admins/management.rs
@@ -0,0 +1,317 @@
+use chrono::Utc;
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::builder::GetMessages;
+use serenity::CACHE;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::{
+ Message,
+ PermissionOverwrite,
+ PermissionOverwriteType
+};
+use serenity::model::id::{
+ ChannelId,
+ GuildId,
+ MessageId
+};
+use serenity::model::Permissions;
+use serenity::prelude::{
+ Context,
+ Mentionable
+};
+use std::sync::Arc;
+
+pub struct Prune;
+impl Command for Prune {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Bulk delete messages. Filter is one of bot, attachment, !pin, mention, or a user_resolvable.\n`bot` will prune only messages from bots.\n`attachment` will prune only messages with attachments.\n`!pin` will prune all but pinned messages.\n`mention` will prune only messages that mention a user or everyone.\nMentioning a user will prune only that user's messages.".to_string()),
+ desc: Some("Someone causing havoc ?\n\nFilters:\n`bot` - Prune messages from bots.\n`attachment` - Prune message with attachments.\n`!pin` - Prune messages, excluding pinned messages.\n`mention` - Prune messages that mention a user(s).\n`user` - Prune messages that were sent by that user.".to_string()),
+ usage: Some("<count> [filter]".to_string()),
+ example: Some("20 bot".to_string()),
+ min_args: Some(1),
+ aliases: vec!["purge", "clear"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ message.delete()?;
+ if let Some(guild_id) = message.guild_id {
+ let count = args.single::<usize>().unwrap_or(0);
+ if count<=1000 {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ let fsel = args.single::<String>().unwrap_or(String::new());
+ let mut filter = get_filter(fsel, guild_id);
+ let mut deletions = message.channel_id.messages(|_| re_retriever(u64::min(100, count as u64)))?;
+ let mut next_deletions;
+ let mut deleted_messages = Vec::new();
+ while deleted_messages.len() < count {
+ next_deletions = message.channel_id
+ .messages(|_| be_retriever(deletions[deletions.len() - 1].id, u64::min(100, (count - deleted_messages.len()) as u64)))
+ .ok();
+ deletions.retain(|m| filter(m) && is_deletable(m));
+ match (deletions.len(), count - deleted_messages.len()) {
+ (n,_) if n <= 0 => { break; },
+ (n,c) if n > c => {
+ deletions.truncate(c);
+ },
+ _ => (),
+ }
+ match message.channel_id.delete_messages(&deletions) {
+ Ok(_) => {
+ deleted_messages.append(&mut deletions);
+ deletions = match next_deletions {
+ Some(s) => if s.is_empty() { break; } else { s },
+ None => { break; }
+ }
+ },
+ Err(why) => {
+ error!("PRUNE ERROR: {:?}", why);
+ break;
+ },
+ }
+ }
+ if deleted_messages.len() > 0 {
+ if guild_data.modlog {
+ let channel = {
+ let cache = CACHE.read();
+ cache.guild_channel(message.channel_id)
+ };
+ ChannelId(guild_data.modlog_channel as u64).send_message(|m| m
+ .embed(|e| e
+ .title("Messages Pruned")
+ .description(format!(
+ "**Count:** {}\n**Moderator:** {} ({})\n**Channel:** {}"
+ ,deleted_messages.len()
+ ,message.author.mention()
+ ,message.author.tag()
+ ,match channel {
+ Some(ch) => {
+ let ch = ch.read();
+ format!(
+ "{} ({})",
+ ch.mention(),
+ ch.name)
+ },
+ None => message.channel_id.0.to_string(),
+ }))
+ .timestamp(now!())
+ .colour(*colours::RED)
+ ))?;
+ } else {
+ message.channel_id.say(format!("Pruned {} message!", deleted_messages.len()))?;
+ }
+ if guild_data.audit {
+ deleted_messages.reverse();
+ let prune_log = deleted_messages.iter()
+ .map(|m| format!(
+ "[{}] {} ({}): {}"
+ ,m.timestamp.with_timezone(&Utc).format("%F %T")
+ ,m.author.tag()
+ ,m.author.id.0
+ ,m.content_safe()
+ ))
+ .collect::<Vec<String>>()
+ .join("\r\n");
+ ChannelId(guild_data.audit_channel as u64).send_files(vec![
+ (prune_log.as_bytes()
+ ,format!("prune-log-{}.txt", Utc::now().format("%FT%T")).as_str())]
+ ,|m| m)?;
+ }
+ } else {
+ message.channel_id.say("I wasn't able to delete any messages.")?;
+ }
+ } else {
+ message.channel_id.say("Please enter a number no greater than 1000.")?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct Clean; // Cleanup
+impl Command for Clean {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Cleans up all commands and responses for Wisp sent in the past 10 minutes in the current channel.".to_string()),
+ desc: Some("Need to tidy up ? This'll delete all of Wisp's messages from the last ten minutes.".to_string()),
+ required_permissions: Permissions::MANAGE_GUILD,
+ max_args: Some(0),
+ aliases: vec!["cleanup"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ let user = CACHE.read().user.clone();
+ let mut deletions = message.channel_id.messages(|_| re_retriever(100))?;
+ let mut next_deletions;
+ let mut num_del = 0;
+ message.delete()?;
+ loop {
+ deletions.retain(|m|
+ (Utc::now() - m.timestamp.with_timezone(&Utc)).num_seconds() <= 10*MIN as i64
+ && (m.author.id == user.id
+ || m.content.starts_with(&guild_data.prefix)
+ || m.content.starts_with(&user.mention()))
+ );
+ let len = deletions.len(); // mut
+ if len<=0 { break; }
+ next_deletions = message.channel_id.messages(|_| be_retriever(deletions[0].id, 100)).ok();
+ match message.channel_id.delete_messages(deletions) {
+ Ok(_) => {
+ num_del += len;
+ deletions = match next_deletions {
+ Some(s) => s,
+ None => { break; },
+ }
+ },
+ Err(why) => {
+ error!("{:?}", why);
+ break;
+ },
+ }
+ }
+ if num_del > 0 {
+ if guild_data.modlog {
+ let channel = {
+ let cache = CACHE.read();
+ cache.guild_channel(message.channel_id)
+ };
+ ChannelId(guild_data.modlog_channel as u64).send_message(|m| m
+ .embed(|e| e
+ .title("Messages Pruned")
+ .description(format!("**Count:** {}\n**Moderator:** {} ({})\n**Channel:** {}",
+ num_del,
+ message.author.mention(),
+ message.author.tag(),
+ match channel {
+ Some(ch) => {
+ let ch = ch.read();
+ format!(
+ "{} ({})",
+ ch.mention(),
+ ch.name)
+ },
+ None => message.channel_id.0.to_string(),
+ }))
+ .timestamp(now!())
+ .colour(*colours::RED)
+ ))?;
+ } else {
+ message.channel_id.say(format!("Pruned {} message!", num_del))?;
+ }
+ } else {
+ message.channel_id.say("I wasn't able to delete any messages.")?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct SetupMute;
+impl Command for SetupMute {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Sets up mute for the server. This command requires the Manage Channels and Manage Roles permissions. It creates the Muted role if it doesn't exist, then iterates through every channel and category to disable Send Messages, Speak, and Add Reactions. Add `bypass` as an arg to skip permission setting.".to_string()),
+ max_args: Some(1),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let guild = {
+ let cache = CACHE.read();
+ cache.guild(guild_id)
+ };
+ if let Some(guild_lock) = guild {
+ let guild = guild_lock.read().clone();
+ let mut guild_data = db.get_guild(guild_id.0 as i64)?;
+ let bypass = args.single::<String>().unwrap_or("".to_string());
+ let mute_role = match guild.roles.values().find(|e| e.name.to_lowercase() == "muted") {
+ Some(role) => role.clone(),
+ None => {
+ message.channel_id.say("Role `Muted` created")?;
+ guild.create_role(|r| r.name("Muted"))?
+ },
+ };
+ if bypass != "bypass" {
+ let allow = Permissions::empty();
+ let deny = Permissions::SEND_MESSAGES | Permissions::ADD_REACTIONS | Permissions::SPEAK;
+ let overwrite = PermissionOverwrite {
+ allow,
+ deny,
+ kind: PermissionOverwriteType::Role(mute_role.id),
+ };
+ for channel in guild.channels.values() {
+ let channel = channel.read(); // mut
+ channel.create_permission(&overwrite)?;
+ }
+ }
+ guild_data.mute_setup = true;
+ db.update_guild(guild.id.0 as i64, guild_data)?;
+ message.channel_id.say(format!("Setup permissions for {} channels.", guild.channels.len()))?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+// Helper functions for commands::prune
+fn re_retriever(limit: u64) -> GetMessages {
+ GetMessages::default()
+ .limit(limit)
+}
+
+fn be_retriever(id: MessageId, limit: u64) -> GetMessages {
+ GetMessages::default()
+ .before(id)
+ .limit(limit)
+}
+
+fn is_deletable(message: &Message) -> bool {
+ let now = Utc::now()
+ .timestamp();
+ let then = message.timestamp
+ .with_timezone(&Utc)
+ .timestamp();
+ now - then < (WEEK as i64)*2
+}
+
+fn get_filter(input: String, guild_id: GuildId) -> Box<dyn FnMut(&Message) -> bool> { // + dyn
+ match input.as_str() {
+ "bot" => Box::new(|m| m.author.bot),
+ "mention" => Box::new(|m| !m.mentions.is_empty() || m.mention_everyone),
+ "attachment" => Box::new(|m| !m.attachments.is_empty()),
+ "!pin" => Box::new(|m| !m.pinned),
+ _ => {
+ match parse_user(input.to_string(), guild_id) {
+ Some((user_id, _)) => {
+ Box::new(move |m| m.author.id == user_id)
+ },
+ None => {
+ Box::new(|_| true)
+ },
+ }
+ },
+ }
+}
diff --git a/src/modules/commands/admins/mod.rs b/src/modules/commands/admins/mod.rs
new file mode 100644
index 0000000..441ecab
--- /dev/null
+++ b/src/modules/commands/admins/mod.rs
@@ -0,0 +1,85 @@
+pub mod config;
+pub mod ignore;
+pub mod management;
+// pub mod premium;
+// pub mod roles;
+pub mod tests;
+
+use self::config::*;
+use self::ignore::*;
+use self::management::*;
+// use self::premium::*;
+// use self::roles::*;
+use self::tests::*;
+use serenity::framework::standard::CreateGroup;
+
+pub fn init_config() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .guild_only(true)
+ .prefixes(vec!["config", "cfg"])
+ .default_cmd(ConfigList)
+ .cmd("admin", ConfigAdmin)
+ .cmd("audit", ConfigAudit)
+ .cmd("autorole", ConfigAutorole)
+ .cmd("cmd", ConfigCommands)
+ // .cmd("goodbye", ConfigGoodbye)
+ // .cmd("introduction", ConfigIntroduction)
+ .cmd("log", ConfigLogs)
+ .cmd("list", ConfigList)
+ .cmd("mod", ConfigMod)
+ .cmd("modlog", ConfigModlog)
+ .cmd("prefix", ConfigPrefix)
+ .cmd("raw", ConfigRaw)
+ .cmd("welcome", ConfigWelcome)
+}
+
+pub fn init_ignore() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .prefix("ignore")
+ .default_cmd(IgnoreList)
+ .cmd("add", IgnoreAdd)
+ .cmd("remove", IgnoreRemove)
+ .cmd("list", IgnoreList)
+ .cmd("level", IgnoreLevel)
+}
+
+pub fn init_management() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .cmd("clean", Clean)
+ .cmd("prune", Prune)
+ // .cmd("setup", SetupMute)
+}
+
+/* pub fn init_premium() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .prefixes(vec!["p", "premium", "prem"])
+ .cmd("register_member", PRegisterMember)
+ .cmd("register_cooldown", PRegisterCooldown)
+ .cmd("register_duration", PRegisterDuration)
+ .cmd("register_roles", PRegisterRestrictions)
+} */
+
+/* pub fn init_roles() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .guild_only(true)
+ .cmd("csr", CreateSelfRole)
+ .cmd("dsr", DeleteSelfRole)
+ .cmd("esr", EditSelfRole)
+} */
+
+pub fn init_tests() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .prefix("test")
+ .cmd("welcome", TestWelcome)
+ // .cmd("intro", TestIntro)
+}
diff --git a/src/modules/commands/admins/premium.rs b/src/modules/commands/admins/premium.rs
new file mode 100644
index 0000000..b6859bd
--- /dev/null
+++ b/src/modules/commands/admins/premium.rs
@@ -0,0 +1,165 @@
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct PRegisterMember;
+impl Command for PRegisterMember {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Set the member role used by register. This role is automatically either after cooldown, if cooldown is set, or right away.".to_string()),
+ usage: Some("<role_resolvable>".to_string()),
+ example: Some("member".to_string()),
+ aliases: vec!["reg_m", "reg_member"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut settings = db.get_premium(guild_id.0 as i64)?;
+ if let Some((role_id, role)) = parse_role(args.full().to_string(), guild_id) {
+ settings.register_member_role = Some(role_id.0 as i64);
+ db.update_premium(guild_id.0 as i64, settings)?;
+ message.channel_id.say(format!("Set member role to {}", role.name))?;
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct PRegisterCooldown;
+impl Command for PRegisterCooldown {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Set the cooldown role used by register. This is applied automatically before member and removed after cooldown_duration".to_string()),
+ usage: Some("<role_resolvable>".to_string()),
+ example: Some("cooldown".to_string()),
+ aliases: vec!["reg_c", "reg_cooldown"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut settings = db.get_premium(guild_id.0 as i64)?;
+ if let Some((role_id, role)) = parse_role(args.full().to_string(), guild_id) {
+ settings.register_cooldown_role = Some(role_id.0 as i64);
+ db.update_premium(guild_id.0 as i64, settings)?;
+ message.channel_id.say(format!("Set cooldown role to {}", role.name))?;
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct PRegisterDuration;
+impl Command for PRegisterDuration {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Set the duration cooldown is applied for. Default value is 24 hours.".to_string()),
+ usage: Some("<time_resolvable>".to_string()),
+ example: Some("24h".to_string()),
+ aliases: vec!["reg_dur", "reg_duration"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut settings = db.get_premium(guild_id.0 as i64)?;
+ if let Ok(dur) = args.full().parse::<String>() {
+ let dur = hrtime_to_seconds(dur);
+ settings.register_cooldown_duration = Some(dur as i32);
+ db.update_premium(guild_id.0 as i64, settings)?;
+ message.channel_id.say(format!("Set duration of cooldown to {}", seconds_to_hrtime(dur as usize)))?;
+ }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct PRegisterRestrictions;
+impl Command for PRegisterRestrictions {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Manage the roles people on cooldown cannot self-assign. These are also ignored in register command usage. Valid options: `add`, `remove`, `set`".to_string()),
+ usage: Some("<option> [values]".to_string()),
+ example: Some("set selfies, nsfw".to_string()),
+ aliases: vec!["reg_roles", "reg_restrict"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let op = args.single::<String>().unwrap_or(String::new());
+ let mut sec = "";
+ let mut val = String::new();
+ let mut settings = db.get_premium(guild_id.0 as i64)?;
+ match op.as_str() {
+ "add" => {
+ if let Some((role_id, role)) = parse_role(args.rest().to_string(), guild_id) {
+ settings.cooldown_restricted_roles.push(role_id.0 as i64);
+ sec = "Added";
+ val = role.name;
+ }
+ },
+ "remove" => {
+ if let Some((role_id, role)) = parse_role(args.rest().to_string(), guild_id) {
+ settings.cooldown_restricted_roles.push(role_id.0 as i64);
+ sec = "Removed";
+ val = role.name;
+ }
+ },
+ "set" => {
+ let list = args.rest().split(",").map(|s| s.trim().to_string());
+ let mut roles = Vec::new();
+ let mut role_names = Vec::new();
+ for role in list {
+ if let Some((role_id, role)) = parse_role(role, guild_id) {
+ roles.push(role_id.0 as i64);
+ role_names.push(role.name);
+ }
+ }
+ settings.cooldown_restricted_roles = roles;
+ sec = "Set to";
+ val = role_names.join(", ");
+ },
+ _ => { message.channel_id.say("I didn't understand that option. Valid options are: `add`, `remove`, `set`. For more information see `help p reg_roles`")?; },
+ }
+ db.update_premium(guild_id.0 as i64, settings)?;
+ message.channel_id.say(format!("Successfully modified restricted roles. {} {}", sec, val))?;
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/admins/roles.rs b/src/modules/commands/admins/roles.rs
new file mode 100644
index 0000000..bbe2195
--- /dev/null
+++ b/src/modules/commands/admins/roles.rs
@@ -0,0 +1,160 @@
+se crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct CreateSelfRole;
+impl Command for CreateSelfRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Create a self role from a discord role. Also optionally takes a category and/or aliases.".to_string()),
+ usage: Some("<role_resolvable> [/c category] [/a aliases as CSV]".to_string()),
+ example: Some("NSFW /c Opt-in /a porn, lewd".to_string()),
+ aliases: vec!["createselfrole".to_string()],
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let switches = get_switches(args
+ .full()
+ .to_string());
+ let backup = String::new();
+ let rest = switches
+ .get("rest")
+ .unwrap_or(&backup);
+ if let Some((role_id, role)) = parse_role(rest.clone(), guild_id) {
+ let category = switches
+ .get("c")
+ .cloned();
+ let aliases: Option<Vec<String>> = switches
+ .get("a")
+ .map(|s| s
+ .split(",")
+ .map(|c| c
+ .trim()
+ .to_string()
+ .to_lowercase())
+ .collect());
+ let data = db.new_role(
+ role_id.0 as i64,
+ guild_id.0 as i64,
+ category,
+ aliases)?;
+ message.channel_id.say(format!(
+ "Successfully added role {} to category {} {}"
+ ,role.name
+ ,data.category
+ ,if !data.aliases.is_empty() {
+ format!("with aliases {}", data.aliases.join(","))
+ } else {
+ String::new()
+ }
+ ))?;
+ } else { message.channel_id.say("I couldn't find that role.")?; }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct DeleteSelfRole;
+impl Command for DeleteSelfRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Delete a self role.".to_string()),
+ usage: Some("<role_resolvable>".to_string()),
+ example: Some("NSFW".to_string()),
+ aliases: vec!["deleteselfrole".to_string()],
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some((role_id, role)) = parse_role(args.full().to_string(), guild_id) {
+ db.del_role(role_id.0 as i64, guild_id.0 as i64)?;
+ message.channel_id.say(format!("Successfully deleted role {}", role.name))?;
+ } else { message.channel_id.say("I couldn't find that role.")?; }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
+
+pub struct EditSelfRole;
+impl Command for EditSelfRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Edit a self role. Optionally takes a category and/or aliases. This operation is lazy and won't change anything you don't specify. Replace switch tells the bot to override aliases instead of append.".to_string()),
+ usage: Some("<role_resolvable> [/c category] [/a aliases as CSV] [/replace]".to_string()),
+ example: Some("NSFW /c Opt-in /a porn, lewd /replace".to_string()),
+ aliases: vec!["editselfrole".to_string()],
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let switches = get_switches(args.full().to_string());
+ let backup = String::new();
+ let rest = switches.get("rest").unwrap_or(&backup);
+ if let Some((role_id, d_role)) = parse_role(rest.clone(), guild_id) {
+ let category = switches
+ .get("c")
+ .cloned();
+ let aliases: Option<Vec<String>> = switches
+ .get("a")
+ .map(|s| s
+ .split(",")
+ .map(|c| c
+ .trim()
+ .to_string()
+ .to_lowercase())
+ .collect());
+ let mut role = db.get_role(role_id.0 as i64, guild_id.0 as i64)?;
+ if let Some(s) = category { role.category = s; }
+ if let Some(mut a) = aliases {
+ match switches.get("replace") {
+ Some(_) => { role.aliases = a; },
+ None => { role.aliases.append(&mut a); },
+ }
+ }
+ let data = db.update_role(role_id.0 as i64, guild_id.0 as i64, role)?;
+ message.channel_id.say(format!("Successfully update role {} in category {} {}",
+ d_role.name,
+ data.category,
+ if !data.aliases.is_empty() {
+ format!("with aliases {}", data.aliases.join(","))
+ } else {
+ String::new()
+ }
+ ))?;
+ } else { message.channel_id.say("I couldn't find that role.")?; }
+ } else {
+ failed!(GUILDID_FAIL);
+ }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/admins/tests.rs b/src/modules/commands/admins/tests.rs
new file mode 100644
index 0000000..d4c8692
--- /dev/null
+++ b/src/modules/commands/admins/tests.rs
@@ -0,0 +1,77 @@
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::id::ChannelId;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct TestWelcome;
+impl Command for TestWelcome {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Feeling superstitious ? I'll check if your welcome configuration is correct.".to_string()),
+ max_args: Some(0),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some(member) = message.member() {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ if guild_data.welcome {
+ let channel = ChannelId(guild_data.welcome_channel as u64);
+ if guild_data.welcome_type.as_str() == "embed" {
+ send_welcome_embed(guild_data.welcome_message, &member, channel)?;
+ } else {
+ channel.say(parse_welcome_items(guild_data.welcome_message, &member))?;
+ }
+ }
+ } else { failed!(MEMBER_FAIL); }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct TestIntro;
+impl Command for TestIntro {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Generates an introduction message to test your current setup.".to_string()),
+ aliases: vec!["introduction"].iter().map(|e| e.to_string()).collect(),
+ max_args: Some(0),
+ required_permissions: Permissions::MANAGE_GUILD,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some(member) = message.member() {
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ if guild_data.welcome {
+ let channel = ChannelId(guild_data.introduction_channel as u64);
+ if guild_data.introduction_type.as_str() == "embed" {
+ send_welcome_embed(guild_data.introduction_message, &member, channel)?;
+ } else {
+ channel.say(parse_welcome_items(guild_data.introduction_message, &member))?;
+ }
+ }
+ } else { failed!(MEMBER_FAIL); }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/general/animals.rs b/src/modules/commands/general/animals.rs
new file mode 100644
index 0000000..42817ab
--- /dev/null
+++ b/src/modules/commands/general/animals.rs
@@ -0,0 +1,183 @@
+// use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::model::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+// pub struct Bunny;
+// impl Command for Bunny {
+// fn options(&self) -> Arc<CommandOptions> {
+// let default = CommandOptions::default();
+// let options = CommandOptions {
+// desc: Some("One bouncy animal for you.".to_string()),
+// ..default
+// };
+// Arc::new(options)
+// }
+
+// fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+// let data = ctx.data.lock();
+// if let Some(api) = data.get::<ApiClient>() {
+// let res = api.bunny()?;
+// message.channel_id.send_message(|m| m
+// .embed(|e| e
+// .image(&res.media[0])
+// ))?;
+// } else { failed!(API_FAIL); }
+
+// Ok(())
+// }
+// }
+
+pub struct Cat;
+impl Command for Cat {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Enjoy a random cat.".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.cat()?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image(res.file)
+ ))?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
+
+pub struct Dog;
+impl Command for Dog {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Cats aren't enough for you ? Have a dog !".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.dog()?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image(res.message)
+ ))?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
+
+pub struct Duck;
+impl Command for Duck {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Have a duck, pog.".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.duck()?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image(res.url)
+ ))?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
+
+pub struct Fox;
+impl Command for Fox {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("A fox, thats it.".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.fox()?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image(res.image)
+ ))?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
+
+pub struct Owl;
+impl Command for Owl {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Why an owl ?".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.owl()?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image(res.image)
+ ))?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
+
+pub struct UglyCat;
+impl Command for UglyCat {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("UGLY CAT !".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image("https://i.pinimg.com/originals/4d/19/0f/4d190f1307b35e7155bb4b898e19d545.jpg")
+ ))?;
+
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/general/fun.rs b/src/modules/commands/general/fun.rs
new file mode 100644
index 0000000..79f37c9
--- /dev/null
+++ b/src/modules/commands/general/fun.rs
@@ -0,0 +1,301 @@
+use crate::core::model::ApiClient;
+use crate::core::colours;
+use crate::core::consts::*;
+use rand::prelude::*;
+use regex::Regex;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+lazy_static! {
+ static ref DICE_MATCH: Regex = Regex::new(r"(?P<count>\d+)d?(?P<sides>\d*)").expect("Failed to create Regex");
+}
+
+pub struct Clapify;
+impl Command for Clapify {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Clap.. Clap... Clap....".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let to_say = args;
+ let clapped = to_say.replace(" ", "🙏");
+ message.channel_id.say(clapped)?;
+
+ Ok(())
+ }
+}
+
+pub struct CoinFlip;
+impl Command for CoinFlip {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("A simple game, one in the chamber, who gets splattered ?".to_string()),
+ aliases: vec!["flipcoin"].iter().map(|e| e.to_string()).collect(),
+ // usage: Some("[tags]".to_string()),
+ // example: Some("minecraft".to_string()),
+ // aliases: vec!["furry"].iter().map(|e| e.to_string()).collect(),
+ // owner_privileges: false,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, _args: Args) -> Result<(), CommandError> {
+ let random = thread_rng().gen_bool(5.4);
+ // TODO: Make this an embed eventually.
+ if random {
+ message.channel_id.say("Looks like a heads to me !")?;
+ } else {
+ message.channel_id.say("Tails seems to be the winner.")?;
+ }
+
+ Ok(())
+ }
+}
+
+// TODO: eval expressions such as "2d10 + 5"
+pub struct Dice;
+impl Command for Dice {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Toss em up. (Defaults to 6-sided.)".to_string()),
+ usage: Some("<Nd>[X]".to_string()),
+ example: Some("2d10".to_string()),
+ aliases: vec!["roll"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let expr = args.single::<String>().unwrap_or(String::new());
+ if let Some(caps) = DICE_MATCH.captures(expr.as_str()) {
+ let count: u32 = caps["count"].parse().unwrap_or(1);
+ let sides: u32 = caps["sides"].parse().unwrap_or(6);
+ if count > 0 && count <= 1000 {
+ if sides > 0 && sides <= 100 {
+ let mut total = 0;
+ for _ in 1..&count+1 {
+ let r = thread_rng().gen_range(1,&sides+1);
+ total += r;
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(*colours::MAIN)
+ .field(format!("{} 🎲 [1-{}]", count, sides), format!("You rolled {}", total), true)
+ ))?;
+ } else { message.channel_id.say("Sides out of bounds. Max: 100")?; }
+ } else { message.channel_id.say("Count out of bounds. Max: 1000")?; }
+ } else { message.channel_id.say("Sorry, I didn't understand your input.")?; }
+ Ok(())
+ }
+}
+
+pub struct EightBall;
+impl Command for EightBall {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("You have a lot of trust seen as you've put your faith in me.".to_string()),
+ aliases: vec!["8b"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _args: Args) -> Result<(), CommandError> {
+ let random_side = thread_rng().gen_range(1, 20);
+
+ let responses = vec![
+ "As I see it, yes.",
+ "Ask again later.",
+ "Better not tell you now.",
+ "Cannot predict now.",
+ "Concentrate and ask again.",
+ "Don't count on it.",
+ "It is certain.",
+ "It is decidedly so.",
+ "Most likely.",
+ "My reply is no.",
+ "My sources say no.",
+ "Outlook not so good.",
+ "Outlook good.",
+ "Reply hazy, try again.",
+ "Signs point to yes.",
+ "Very doubtful.",
+ "Without a doubt.",
+ "Yes.",
+ "Yes - definetely.",
+ "You may rely on it."
+ ];
+
+ message.channel_id.say(responses[random_side])?;
+
+ Ok(())
+ }
+}
+
+pub struct PayRespects;
+impl Command for PayRespects {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Press F to Pay Respects".to_string()),
+ // usage: Some("[tags]".to_string()),
+ // example: Some("minecraft".to_string()),
+ aliases: vec!["payrespects"].iter().map(|e| e.to_string()).collect(),
+ // owner_privileges: false,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, _args: Args) -> Result<(), CommandError> {
+ message.channel_id.say("Press F to Pay Respects")?.react("🇫")?;
+
+ Ok(())
+ }
+}
+
+pub struct Opinion;
+impl Command for Opinion {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Yeah I'll give you my opinion, just don't take me seriously.".to_string()),
+ usage: Some("[something you want my opinion on]".to_string()),
+ example: Some("onions".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let random = thread_rng().gen_bool(5.4);
+ // TODO: Make this an embed eventually.
+ if random {
+ message.channel_id.say(format!(
+ "{:?} ? Yeah, I'll give it a thumbs up.", args.to_string()))?;
+ } else {
+ message.channel_id.say(format!(
+ "{:?} ? That's a thumbs down from me.", args.to_string()))?;
+ }
+
+ Ok(())
+ }
+}
+
+pub struct Rate;
+impl Command for Rate {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("I'll rate it, don't take it seriously though.".to_string()),
+ usage: Some("[something you want my rating on]".to_string()),
+ example: Some("salads".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let random = thread_rng().gen_range(1, 10);
+ // TODO: Make this an embed eventually.
+ message.channel_id.say(format!(
+ "I'll give {:?} a {} out of 10.", args.to_string(), random))?;
+
+ Ok(())
+ }
+}
+
+pub struct RussianRoulette;
+impl Command for RussianRoulette {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("A simple game, one in the chamber, who gets splattered ?".to_string()),
+ // usage: Some("[tags]".to_string()),
+ // example: Some("minecraft".to_string()),
+ // aliases: vec!["furry"].iter().map(|e| e.to_string()).collect(),
+ // owner_privileges: false,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, _args: Args) -> Result<(), CommandError> {
+ let random = thread_rng().gen_range(1, 6);
+ if random == 4 {
+ message.channel_id.say("Boom ! Better luck next time...")?;
+ } else {
+ message.channel_id.say("Click ! You survived, for now...")?;
+ }
+
+ Ok(())
+ }
+}
+
+pub struct Uwufy;
+impl Command for Uwufy {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Can't get enough of that \"uwu\" goodness ?".to_string()),
+ aliases: vec!["owofy"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let to_say = args;
+
+ let r_re = Regex::new(r"/(?:l|r)/g").unwrap();
+ let l_re = Regex::new(r"/(?:L|R)/g").unwrap();
+ let face_re = Regex::new(r"/!+/g").unwrap();
+
+ let replaced1 = r_re.replace_all(&to_say, "w");
+ let replaced2 = l_re.replace_all(&replaced1, "W");
+ let uwufied = face_re.replace_all(&replaced2, " >w< ");
+
+ message.channel_id.say(uwufied)?;
+
+ Ok(())
+ }
+}
+
+pub struct YoMomma;
+impl Command for YoMomma {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Yo momma so...".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.yo_momma()?;
+ message.channel_id.say(res.joke)?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/general/misc.rs b/src/modules/commands/general/misc.rs
new file mode 100644
index 0000000..f342cd0
--- /dev/null
+++ b/src/modules/commands/general/misc.rs
@@ -0,0 +1,320 @@
+// use chrono::Utc;
+use crate::core::colours;
+use crate::core::consts::*;
+// use crate::core::consts::DB as db;
+use crate::core::model::*;
+// use crate::core::utils::*;
+// use forecast::Icon::*;
+// use forecast::Units;
+// use rand::prelude::*;
+// use serenity::CACHE;
+// use serenity::client::bridge::gateway::ShardId;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::{
+ channel::Message,
+ // guild::Role,
+};
+use serenity::prelude::{
+ Context,
+ // Mentionable
+};
+// use std::f64::NAN;
+use std::sync::Arc;
+// use sys_info;
+// use sysinfo::{
+// ProcessExt,
+// SystemExt,
+// System,
+// get_current_pid
+// };
+
+pub struct Anime;
+impl Command for Anime {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Want to share an anime ? Results provided by kitsu.io.".to_string()),
+ usage: Some("<anime title>".to_string()),
+ example: Some("darling in the franxx".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ use kitsu::model::Status::*;
+ let data = ctx.data.lock();
+ message.channel_id.broadcast_typing()?;
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.anime(args.full())?;
+ if let Some(anime) = res.data.first() {
+ let status = match anime.attributes.status {
+ Some(stat) => { match stat {
+ Current => "Current",
+ Finished => "Complete",
+ TBA => "To Be Announced",
+ Unreleased => "Unreleased",
+ Upcoming => "Upcoming",
+ }},
+ None => "Status Not Found",
+ };
+ let cover_url = match anime.attributes.cover_image.clone() {
+ Some(cover) => { match cover.original {
+ Some(url) => url,
+ None => String::new(),
+ }},
+ None => String::new(),
+ };
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title(anime.attributes.canonical_title.clone())
+ .url(anime.url())
+ .description(format!("{}\n\n{}\n**Score:** {}\n**Status:** {}",
+ anime.attributes.synopsis,
+ if let Some(count) = anime.attributes.episode_count {
+ let mut out = format!("**Episodes:** {}", count);
+ if let Some(length) = anime.attributes.episode_length {
+ out.push_str(format!(" ({} min/ep)", length).as_str());
+ }
+ out
+ } else { String::from("Episode Information Not Found") },
+ anime.attributes.average_rating.clone().unwrap_or(String::from("Not Found")),
+ status
+ ))
+ .thumbnail(cover_url)
+ .colour(*colours::MAIN)
+ ))?;
+ }
+ } else { failed!(API_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct DadJoke;
+impl Command for DadJoke {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Who would voluntarily want a dad joke...".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.joke()?;
+ message.channel_id.say(res)?;
+ } else { failed!(API_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct Douse;
+impl Command for Douse {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(*colours::MAIN)
+ .image("https://i.pinimg.com/originals/6a/c8/26/6ac826e3d0cbd64eb4f42c12a73fcdb8.gif")
+ ))?;
+
+ Ok(())
+ }
+}
+
+pub struct Manga;
+impl Command for Manga {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Need info about a manga ? Results provided by kitsu.io".to_string()),
+ usage: Some("<anime title>".to_string()),
+ example: Some("deathnote".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ use kitsu::model::Status::*;
+ let data = ctx.data.lock();
+ message.channel_id.broadcast_typing()?;
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.manga(args.full())?;
+ if let Some(manga) = res.data.first() {
+ let status = match manga.attributes.status {
+ Some(stat) => { match stat {
+ Current => "Current",
+ Finished => "Complete",
+ TBA => "To Be Announced",
+ Unreleased => "Unreleased",
+ Upcoming => "Upcoming",
+ }},
+ None => "Status Not Found",
+ };
+ let cover_url = match manga.attributes.cover_image.clone() {
+ Some(cover) => { match cover.original {
+ Some(url) => url,
+ None => String::new(),
+ }},
+ None => String::new(),
+ };
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title(manga.attributes.canonical_title.clone())
+ .url(manga.url())
+ .description(format!("{}\n\n**Volumes:** {}\n**Chapters:** {}\n**Score:** {}\n**Status:** {}",
+ manga.attributes.synopsis,
+ manga.attributes.volume_count.map_or(String::from("Not Found"), |count| count.to_string()),
+ manga.attributes.chapter_count.map_or(String::from("Not Found"), |count| count.to_string()),
+ manga.attributes.average_rating.clone().unwrap_or(String::from("Not Found")),
+ status
+ ))
+ .thumbnail(cover_url)
+ .colour(*colours::MAIN)
+ ))?;
+ }
+ } else { failed!(API_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct UrbanDictionary; // Urban
+impl Command for UrbanDictionary {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Hmm, be responsible.".to_string()),
+ usage: Some(r#"<"term"> [count]"#.to_string()),
+ example: Some(r#""boku no pico" 5"#.to_string()),
+ aliases: vec!["urban", "ud", "urbandict"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let api = {
+ let data = ctx.data.lock();
+ data.get::<ApiClient>().cloned()
+ };
+ if let Some(api) = api {
+ let term = args.single_quoted::<String>().unwrap_or(String::new());
+ let res = api.urban(term.as_str())?;
+ if !res.definitions.is_empty() {
+ let count = args.single::<u32>().unwrap_or(1);
+ let mut tags: Vec<String> = Vec::new();
+ if let Some(res_tags) = &res.tags {
+ tags = res_tags.clone();
+ tags.sort();
+ tags.dedup();
+ }
+ if count == 1 {
+ let item = &res.definitions[0];
+ let tags_list = {
+ let list = tags.iter().map(|t| "#".to_string()+t).collect::<Vec<String>>().join(", ");
+ if !list.is_empty() {
+ list
+ } else {
+ "None".to_string()
+ }
+ };
+ let definition = {
+ let mut i = item.definition.clone();
+ if i.len() > 1000 {
+ i.truncate(997);
+ i += "...";
+ }
+ i
+ };
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(*colours::MAIN)
+ .field(format!(r#"Definition of "{}" by {}"#, item.word, item.author), &item.permalink, false)
+ .field("Thumbs Up", &item.thumbs_up, true)
+ .field("Thumbs Down", &item.thumbs_down, true)
+ .field("Definition", definition, false)
+ .field("Example", &item.example, false)
+ .field("Tags", tags_list, false)
+ ))?;
+ } else {
+ let mut list = res.definitions;
+ list.truncate(count as usize);
+ let list = list.iter()
+ .map(|c| format!(r#""{}" by {}: {}"#, c.word, c.author, c.permalink))
+ .collect::<Vec<String>>()
+ .join("\n");
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title(format!("Top {} results for {}", count, term))
+ .description(list)
+ .colour(*colours::MAIN)
+ ))?;
+ }
+ }
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
+
+// pub struct Waifu;
+// impl Command for Waifu {
+// fn options(&self) -> Arc<CommandOptions> {
+// let default = CommandOptions::default();
+// let options = CommandOptions {
+// desc: Some("I'll give you random waifu and backstory generated by a neural network, so don't get too attached.".to_string()),
+// ..default
+// };
+// Arc::new(options)
+// }
+
+// fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+// let data = ctx.data.lock();
+// if let Some(api) = data.get::<ApiClient>() {
+// let res = api.waifu_backstory()?;
+// message.channel_id.send_message(|m| m
+// .embed(|e| e
+// // .image(res.image)
+// .description(res)
+// ))?;
+// } else { failed!(API_FAIL); }
+
+// Ok(())
+// }
+// }
+
+// pub struct DateFact;
+// impl Command for DateFact {
+// fn options(&self) -> Arc<CommandOptions> {
+// let default = CommandOptions::default();
+// let options = CommandOptions {
+// desc: Some("A fun fact about a fun date.".to_string()),
+// ..default
+// };
+// Arc::new(options)
+// }
+
+// fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+// let to_say = args;
+
+// message.channel_id.say(uwufied)?;
+
+// Ok(())
+// }
+// }
diff --git a/src/modules/commands/general/mod.rs b/src/modules/commands/general/mod.rs
new file mode 100644
index 0000000..acff760
--- /dev/null
+++ b/src/modules/commands/general/mod.rs
@@ -0,0 +1,122 @@
+pub mod misc;
+pub mod nsfw;
+// pub mod roles;
+// pub mod tags;
+pub mod fun;
+pub mod utilities;
+pub mod animals;
+
+use self::misc::*;
+use self::nsfw::*;
+// use self::roles::*;
+// use self::tags::*;
+use self::fun::*;
+use self::utilities::*;
+use self::animals::*;
+use serenity::framework::standard::CreateGroup;
+
+pub fn init_animals() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ // .cmd("bunny", Bunny) // TODO: Add bunny
+ .cmd("cat", Cat)
+ .cmd("dog", Dog)
+ .cmd("duck", Duck)
+ .cmd("fox", Fox)
+ .cmd("owl", Owl)
+ .cmd("uglycat", UglyCat)
+}
+
+pub fn init_anime() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .cmd("anime", Anime)
+ .cmd("manga", Manga)
+ // .cmd("waifu", Waifu)
+}
+
+pub fn init_fun() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .cmd("clapify", Clapify)
+ .cmd("joke", DadJoke)
+ // .cmd("opinion", Opinion)
+ .cmd("rate", Rate)
+ .cmd("urbandictionary", UrbanDictionary)
+ .cmd("f", PayRespects)
+ .cmd("8ball", EightBall)
+ .cmd("uwufy", Uwufy)
+ .cmd("yomomma", YoMomma)
+}
+
+/* pub fn init_misc() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+} */
+
+pub fn init_minigames() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ // .cmd("coinflip", CoinFlip)
+ .cmd("dice", Dice)
+ .cmd("russianroulette", RussianRoulette)
+}
+
+pub fn init_nsfw() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .check(|_,message,_,_| {
+ if let Ok(channel) = message.channel_id.to_channel() {
+ if channel.is_nsfw() {
+ true
+ } else {
+ check_error!(message.channel_id.say("Woah there, you can only use this command in NSFW marked channels !"));
+ false
+ }
+ } else {
+ check_error!(message.channel_id.say("Weird, I can't get the channel information, I can't tell if it's NSFW."));
+ false
+ }})
+ .cmd("e621", Furry)
+}
+
+/* pub fn init_roles() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .guild_only(true)
+ .cmd("role", AddSelfRole)
+ .cmd("derole", RemoveSelfRole)
+ .cmd("roles", ListSelfRoles)
+}
+
+pub fn init_tags() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .guild_only(true)
+ .prefix("tag")
+ .default_cmd(TagSingle)
+ .cmd("show", TagSingle)
+ .cmd("add", TagAdd)
+ .cmd("del", TagRemove)
+ .cmd("edit", TagEdit)
+ .cmd("list", TagList)
+} */
+
+pub fn init_utilities() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(true)
+ .cmd("id", UserId)
+ .cmd("membercount", MemberCount)
+ .cmd("pfp", PFP)
+ .cmd("ping", Ping)
+ .cmd("prefix", Prefix)
+ .cmd("remind", Reminder)
+ .cmd("role", RoleInfo)
+ .cmd("server", ServerInfo)
+ // .cmd("stats", Stats)
+ // .cmd("tags", TagList)
+ .cmd("time", Time)
+ .cmd("user", UserInfo)
+ // .cmd("weather", Weather)
+ .cmd("wisp", Wisp)
+}
diff --git a/src/modules/commands/general/nsfw.rs b/src/modules/commands/general/nsfw.rs
new file mode 100644
index 0000000..1a699d0
--- /dev/null
+++ b/src/modules/commands/general/nsfw.rs
@@ -0,0 +1,50 @@
+use crate::core::model::ApiClient;
+use crate::core::consts::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct Furry;
+impl Command for Furry {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("I see your a individual of culture...".to_string()),
+ usage: Some("[tags]".to_string()),
+ example: Some("minecraft".to_string()),
+ aliases: vec!["furry"].iter().map(|e| e.to_string()).collect(),
+ owner_privileges: false,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ message.channel_id.broadcast_typing()?;
+ if let Some(api) = data.get::<ApiClient>() {
+ let res = api.furry(args.full(), 1)?;
+ let post = &res[0];
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .image(&post.file_url)
+ .description(format!("**Tags:** {}\n**Post:** [{}]({})\n**Artist:** {}\n**Score::** {}",
+ &post.tags.replace("_", "\\_"),
+ &post.id,
+ format!("https://e621.net/post/show/{}", &post.id),
+ &post.artist[0],
+ &post.score
+ )
+ )
+ ))?;
+ } else { failed!(API_FAIL); }
+
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/general/roles.rs b/src/modules/commands/general/roles.rs
new file mode 100644
index 0000000..b6cd240
--- /dev/null
+++ b/src/modules/commands/general/roles.rs
@@ -0,0 +1,278 @@
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use fuzzy_match::fuzzy_match;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::guild::Role;
+use serenity::model::id::RoleId;
+use serenity::prelude::Context;
+use std::collections::BTreeMap;
+use std::sync::Arc;
+
+pub struct AddSelfRole;
+impl Command for AddSelfRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Add roles to yourself provided they are on the self role list.".to_string()),
+ usage: Some("<role_resolvables as CSV>".to_string()),
+ example: Some("red, green".to_string()),
+ aliases: vec!["addselfrole", "asr"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some(mut member) = message.member() {
+ let roles = db.get_roles(guild_id.0 as i64)?;
+ let (restricted_roles, has_cooldown) = match db.get_premium(guild_id.0 as i64) {
+ Ok(data) => {
+ let has_cooldown = if let Some(cooldown_role) = data.register_cooldown_role {
+ member.roles.contains(&RoleId(cooldown_role as u64))
+ } else {
+ false
+ };
+ (data.cooldown_restricted_roles, has_cooldown)
+ },
+ Err(_) => {
+ (Vec::new(), false)
+ },
+ };
+ if !roles.is_empty() {
+ let list = args.rest().split(",").map(|s| s.trim().to_string());
+ let mut to_add = Vec::new();
+ let mut failed = Vec::new();
+ let role_names = roles.iter().filter_map(|r| match RoleId(r.id as u64).to_role_cached() {
+ Some(role) => Some(role.clone()),
+ None => None,
+ }).collect::<Vec<Role>>();
+ for r1 in list {
+ if let Some((r, r2)) = parse_role(r1.clone(), guild_id) {
+ if has_cooldown && restricted_roles.contains(&(r.0 as i64)) {
+ failed.push(format!("{} is not available on cooldown", r2.name));
+ continue;
+ }
+ if let Some(_) = roles.iter().find(|e| e.id == r.0 as i64) {
+ to_add.push(r);
+ } else { failed.push(format!("{} is a role, but it isn't self-assignable", r2.name)); }
+ } else if let Some(i) = roles.iter().position(|r| r.aliases.contains(&r1.to_lowercase())) {
+ if has_cooldown && restricted_roles.contains(&(roles[i].id)) {
+ failed.push(format!("{} is not available on cooldown", match RoleId(roles[i].id as u64).to_role_cached() {
+ Some(role) => role.name,
+ None => roles[i].id.to_string(),
+ }));
+ continue;
+ }
+ to_add.push(RoleId(roles[i].id as u64));
+ } else {
+ failed.push(format!("Failed to find match \"{}\". {}", r1,
+ if let Some(i) = fuzzy_match(&r1, role_names.iter().enumerate().map(|(i,r)| (r.name.as_str(), i)).collect()) {
+ let ref val = role_names[i];
+ format!("Closest match: {}", val.name.clone())
+ } else { String::new() }
+ ));
+ }
+ }
+ for (i, role_id) in to_add.clone().iter().enumerate() {
+ if member.roles.contains(role_id) {
+ to_add.remove(i);
+ failed.push(format!("You already have {}", match role_names.iter().find(|r| &r.id == role_id) {
+ Some(s) => s.name.clone(),
+ None => role_id.0.to_string(),
+ }));
+ }
+ if let Err(_) = member.add_role(*role_id) {
+ to_add.remove(i);
+ failed.push(format!("Failed to add {}", match role_names.iter().find(|r| &r.id == role_id) {
+ Some(s) => s.name.clone(),
+ None => role_id.0.to_string(),
+ }));
+ };
+ }
+ let mut fields = Vec::new();
+ if !to_add.is_empty() {
+ fields.push(("Added Roles", to_add.iter().filter_map(|r| match r.to_role_cached() {
+ Some(r) => Some(r.name.clone()),
+ None => None,
+ }).collect::<Vec<String>>().join("\n").to_string(), false));
+ }
+ if !failed.is_empty() {
+ fields.push(("Failed to Add", failed.join("\n"), false));
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Add Self Role Summary")
+ .fields(fields)
+ .colour(member.colour().unwrap_or(*colours::GREEN))
+ ))?;
+ } else {
+ message.channel_id.say("There are no self roles.")?;
+ }
+ } else { failed!(MEMBER_FAIL); }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct RemoveSelfRole;
+impl Command for RemoveSelfRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Remove roles from yourself provided they are on the self role list.".to_string()),
+ usage: Some("<role_resolvables as CSV>".to_string()),
+ example: Some("red, green".to_string()),
+ aliases: vec!["removeselfrole", "rsr"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some(mut member) = message.member() {
+ let roles = db.get_roles(guild_id.0 as i64)?;
+ if !roles.is_empty() {
+ let list = args.rest().split(",").map(|s| s.trim().to_string());
+ let mut to_remove = Vec::new();
+ let mut failed = Vec::new();
+ let role_names = roles.iter().filter_map(|r| match RoleId(r.id as u64).to_role_cached() {
+ Some(role) => Some(role.clone()),
+ None => None,
+ }).collect::<Vec<Role>>();
+ for r1 in list {
+ if let Some((r, r2)) = parse_role(r1.clone(), guild_id) {
+ if let Some(_) = roles.iter().find(|e| e.id == r.0 as i64) {
+ to_remove.push(r);
+ } else { failed.push(format!("{} is a role, but it isn't self-assignable", r2.name)); }
+ } else if let Some(i) = roles.iter().position(|r| r.aliases.contains(&r1.to_lowercase())) {
+ to_remove.push(RoleId(roles[i].id as u64));
+ } else {
+ failed.push(format!("Failed to find match \"{}\". {}", r1,
+ if let Some(i) = fuzzy_match(&r1, role_names.iter().enumerate().map(|(i,r)| (r.name.as_str(), i)).collect()) {
+ let ref val = role_names[i];
+ format!("Closest match: {}", val.name.clone())
+ } else { String::new() }
+ ));
+ }
+ }
+ for (i, role_id) in to_remove.clone().iter().enumerate() {
+ if !member.roles.contains(role_id) {
+ to_remove.remove(i);
+ failed.push(format!("You don't have {}", match role_names.iter().find(|r| &r.id == role_id) {
+ Some(s) => s.name.clone(),
+ None => role_id.0.to_string(),
+ }));
+ }
+ if let Err(_) = member.remove_role(*role_id) {
+ to_remove.remove(i);
+ failed.push(format!("Failed to remove {}", match role_names.iter().find(|r| &r.id == role_id) {
+ Some(s) => s.name.clone(),
+ None => role_id.0.to_string(),
+ }));
+ };
+ }
+ let mut fields = Vec::new();
+ if !to_remove.is_empty() {
+ fields.push(("Removed Roles", to_remove.iter().filter_map(|r| match r.to_role_cached() {
+ Some(r) => Some(r.name.clone()),
+ None => None,
+ }).collect::<Vec<String>>().join("\n").to_string(), false));
+ }
+ if !failed.is_empty() {
+ fields.push(("Failed to Remove", failed.join("\n"), false));
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Remove Self Role Summary")
+ .fields(fields)
+ .colour(member.colour().unwrap_or(*colours::RED))
+ ))?;
+ } else {
+ message.channel_id.say("There are no self roles.")?;
+ }
+ } else { failed!(MEMBER_FAIL); }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct ListSelfRoles;
+impl Command for ListSelfRoles {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("List all the self roles for the current server. Optionally, you can view a single category.".to_string()),
+ usage: Some("[category]".to_string()),
+ aliases: vec!["listselfroles", "lsr"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let mut roles = db.get_roles(guild_id.0 as i64)?;
+ if !roles.is_empty() {
+ if args.is_empty() {
+ let mut map: BTreeMap<String, Vec<String>> = BTreeMap::new();
+ for role in roles.iter() {
+ match RoleId(role.id as u64).to_role_cached() {
+ Some(r) => {
+ map.entry(role.category.clone()).or_insert(Vec::new()).push(r.name);
+ },
+ None => {
+ // Clean up roles that don't exist
+ db.del_role(role.id, guild_id.0 as i64)?;
+ },
+ }
+ }
+ let mut fields = Vec::new();
+ for (key, val) in map.iter_mut() {
+ val.sort();
+ fields.push((key, val.join("\n"), true));
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Self Roles")
+ .fields(fields)
+ .colour(*colours::MAIN)
+ ))?;
+ } else {
+ let category = args.full().to_string();
+ roles.retain(|e| *e.category.to_lowercase() == category.to_lowercase());
+ if !roles.is_empty() {
+ let mut roles = roles
+ .iter()
+ .map(|e| match RoleId(e.id as u64).to_role_cached() {
+ Some(r) => r.name,
+ None => e.id.to_string(),
+ })
+ .collect::<Vec<String>>();
+ roles.sort();
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title(category)
+ .description(roles.join("\n"))
+ .colour(*colours::MAIN)
+ ))?;
+ } else {
+ message.channel_id.say(format!("The category `{}` does not exist.", category))?;
+ }
+ }
+ } else {
+ message.channel_id.say("There are no self roles.")?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/general/tags.rs b/src/modules/commands/general/tags.rs
new file mode 100644
index 0000000..fc364c0
--- /dev/null
+++ b/src/modules/commands/general/tags.rs
@@ -0,0 +1,168 @@
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use fuzzy_match::algorithms::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::prelude::Context;
+use std::cmp::Ordering;
+use std::sync::Arc;
+
+pub struct TagList;
+impl Command for TagList {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Alias to `tag list`".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let tags = db.get_tags(guild_id.0 as i64)?;
+ if !tags.is_empty() {
+ message.channel_id.say(tags.iter().map(|e| e.name.as_str()).collect::<Vec<&str>>().join("\n"))?;
+ } else {
+ message.channel_id.say("No tags founds.")?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct TagSingle;
+impl Command for TagSingle {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("View a tag.".to_string()),
+ usage: Some("<tag name>".to_string()),
+ example: Some("foobar".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let tag_input = args.full().trim().to_string();
+ let tags = db.get_tags(guild_id.0 as i64)?;
+ if !tags.is_empty() {
+ if let Some(tag) = tags.iter().find(|e| e.name == tag_input) {
+ message.channel_id.say(&tag.data)?;
+ } else {
+ let mut sdc = SorensenDice::new();
+ let mut matches = Vec::new();
+ for tag in tags.iter() {
+ let dist = sdc.get_similarity(tag.name.as_str(), &tag_input);
+ matches.push((tag, dist));
+ }
+ matches.retain(|e| e.1 > 0.2);
+ matches.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(Ordering::Equal));
+ matches.truncate(5);
+ let matches = matches.iter().map(|e| e.0.name.clone()).collect::<Vec<String>>();
+ message.channel_id.say(format!("No tag found. Did you mean...\n{}", matches.join("\n")))?;
+ }
+ } else { message.channel_id.say("There are no tags yet.")?; }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct TagAdd;
+impl Command for TagAdd {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Create a new tag.".to_string()),
+ usage: Some("<tag name, quoted> <tag value>".to_string()),
+ example: Some(r#""my new tag" look, I made a tag!"#.to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let tag_input = args.single_quoted::<String>()?;
+ let value = args.rest().to_string();
+ let tag = db.new_tag(message.author.id.0 as i64, guild_id.0 as i64, tag_input.clone(), value)?;
+ message.channel_id.say(format!("Successfully created tag `{}`", tag.name))?;
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct TagRemove;
+impl Command for TagRemove {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Delete a tag.".to_string()),
+ usage: Some("<tag name>".to_string()),
+ example: Some("foobar".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let tag_input = args.single_quoted::<String>()?;
+ let tag = db.get_tag(guild_id.0 as i64, tag_input.clone())?;
+ let check = guild_id
+ .member(message.author.id)
+ .and_then(|m| m
+ .permissions()
+ .map(|p| p
+ .manage_messages()))
+ .unwrap_or(false);
+ if message.author.id.0 as i64 == tag.author || check {
+ let tag = db.del_tag(guild_id.0 as i64, tag_input.clone())?;
+ message.channel_id.say(format!("Successfully deleted tag `{}`", tag.name))?;
+ } else { message.channel_id.say("You must own this tag in order to delete it.")?; }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct TagEdit;
+impl Command for TagEdit {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Edit a tag. Only works if you are the author.".to_string()),
+ usage: Some("<tag name, quoted> <new value>".to_string()),
+ example: Some(r#""my edited tag" I had to edit this tag"#.to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let tag_input = args.single_quoted::<String>()?;
+ let value = args.rest().to_string();
+ let mut tag = db.get_tag(guild_id.0 as i64, tag_input.clone())?;
+ let check = guild_id
+ .member(message.author.id)
+ .and_then(|m| m
+ .permissions()
+ .map(|p| p
+ .manage_messages()))
+ .unwrap_or(false);
+ if message.author.id.0 as i64 == tag.author || check {
+ tag.data = value.clone();
+ let t = db.update_tag(guild_id.0 as i64, tag_input.clone(), tag)?;
+ message.channel_id.say(format!("Successfully edited tag `{}`", t.name))?;
+ } else { message.channel_id.say("You must own this tag in order to edit it.")?; }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/general/utilities.rs b/src/modules/commands/general/utilities.rs
new file mode 100644
index 0000000..fc6fc36
--- /dev/null
+++ b/src/modules/commands/general/utilities.rs
@@ -0,0 +1,761 @@
+use chrono::Utc;
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::model::*;
+use crate::core::utils::*;
+// use forecast::Icon::*;
+// use forecast::Units;
+// use rand::prelude::*;
+use serenity::CACHE;
+use serenity::client::bridge::gateway::ShardId;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::{
+ channel::Message,
+ guild::Role,
+};
+use serenity::prelude::{
+ Context,
+ Mentionable
+};
+// use std::f64::NAN;
+use std::sync::Arc;
+use sys_info;
+use sysinfo::{
+ ProcessExt,
+ SystemExt,
+ System,
+ get_current_pid
+};
+
+// TODO: Get member count working.
+pub struct MemberCount;
+impl Command for MemberCount {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Wanna check how many cool people are a part of your server ?".to_string()),
+ guild_only: true,
+ aliases: vec!["mc"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, _args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let member_count = CACHE.read().guild(guild_id).unwrap().read().member_count;
+ message.channel_id.say(format!(
+ "There are {:?} members in this guild !", member_count))?;
+ }
+
+ Ok(())
+ }
+}
+
+pub struct UserId;
+impl Command for UserId {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Need your ID ?".to_string()),
+ usage: Some("[user_resolvable]".to_string()),
+ example: Some("@fun".to_string()),
+ guild_only: true,
+ aliases: vec!["userid", "uid"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some((id,_)) = parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ message.channel_id.say(format!("{}", id.0))?;
+ } else {
+ message.channel_id.say("I couldn't find that user.")?;
+ }
+ }
+
+ Ok(())
+ }
+}
+
+pub struct PFP;
+impl Command for PFP {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Need a close up ? No creeping ! (Defaults to the author of the command.)".to_string()),
+ usage: Some("[user_resolvable]".to_string()),
+ example: Some("@fun".to_string()),
+ guild_only: true,
+ aliases: vec!["avi", "avatar"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _ctx: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let (user, member) = match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((id, member)) => (id.to_user()?, member),
+ None => (message.author.clone(), message.member().ok_or("Failed to get member.")?),
+ };
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(member.colour().unwrap_or(*colours::MAIN))
+ .image(user.face())
+ ))?;
+ }
+ Ok(())
+ }
+}
+
+pub struct Ping;
+impl Command for Ping {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("No, I will not play ping-pong with you...".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ let mut lat = 0;
+ if let Some(sm_lock) = data.get::<SerenityShardManager>() {
+ let sm = sm_lock.lock();
+ let runners = sm.runners.lock();
+ if let Some(shard_runner) = runners.get(&ShardId(ctx.shard_id)) {
+ if let Some(la) = shard_runner.latency {
+ lat = la.as_secs() as u32 + la.subsec_millis();
+ }
+ }
+ }
+ let mut m = message.channel_id.send_message(|m| m.embed(|e| e.title("Pong!")))?;
+ let t = m.timestamp.timestamp_millis() - message.timestamp.timestamp_millis();
+ m.edit(|m| m.embed(|e| e
+ .title("Pong!")
+ .description(format!("**Shard Latency:** {}\n**Response Time:** {} ms", if lat==0 { String::from("Failed to retrieve") } else { format!("{} ms", lat) }, t))
+ .colour(*colours::MAIN)
+ ))?;
+ Ok(())
+ }
+}
+
+pub struct Prefix;
+impl Command for Prefix {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Need the prefix of the current guild ?".to_string()),
+ guild_only: true,
+ aliases: vec!["pre"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Ok(settings) = db.get_guild(guild_id.0 as i64) {
+ message.channel_id.say(format!("The prefix for this guild is `{}`", settings.prefix))?;
+ } else {
+ message.channel_id.say("Failed to get guild data.")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub struct Reminder;
+impl Command for Reminder {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Like a calender, but cooler. (Sent to whatever channel the reminder was created in.)".to_string()),
+ usage: Some("<reminder text> </t time_resolvable>".to_string()),
+ example: Some("do the thing /t 1 day 10 min 25 s".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(tc_lock) = data.get::<TC>() {
+ let tc = tc_lock.lock();
+ let channel_id = message.channel_id;
+ let user_id = message.author.id;
+ let switches = get_switches(args.rest().to_string());
+ let reminder = match switches.get("rest") {
+ Some(s) => s.clone(),
+ None => String::new(),
+ };
+ let start_time = Utc::now().timestamp();
+ let dur = hrtime_to_seconds(match switches.get("t") {
+ Some(s) => s.clone(),
+ None => String::new(),
+ });
+ if dur>0 {
+ let end_time = start_time + dur;
+ let reminder_fmt = format!("REMINDER||{}||{}||{}||{}", channel_id.0, user_id.0, dur, reminder); // mut
+ db.new_timer(start_time, end_time, reminder_fmt.clone())?;
+ tc.request();
+ message.channel_id.say(format!("Got it! I'll remind you to {} in {}",
+ reminder,
+ seconds_to_hrtime(dur as usize)
+ ))?;
+ } else {
+ message.channel_id.say("Sorry, I wasn't able to find a time there. Make sure you to add `/t time_resolvable` after your reminder text.")?;
+ }
+ } else { failed!(TC_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct RoleInfo;
+impl Command for RoleInfo {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Mods lacking ? Don't worry, I got you.".to_string()),
+ usage: Some("<role_resolvable>".to_string()),
+ example: Some("@example role".to_string()),
+ guild_only: true,
+ aliases: vec!["roleinfo", "ri", "rinfo"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_role(args.rest().to_string(), guild_id) {
+ Some((role_id, role)) => {
+ let role_data = db.get_role(role_id.0 as i64, guild_id.0 as i64).ok();
+ let mut fields = vec![
+ ("Name", role.name.clone(), true),
+ ("ID", role_id.0.to_string(), true),
+ ("Hex", format!("#{}", role.colour.hex()), true),
+ ("Hoisted", String::from(if role.hoist { "Yes" } else { "No" }), true),
+ ("Mentionable", String::from(if role.mentionable { "Yes" } else { "No" }), true),
+ ("Position", role.position.to_string(), true),
+ ];
+ match role_data {
+ Some(r) => {
+ fields.push(("Self Assignable", String::from("Yes"), true));
+ if !r.aliases.is_empty() {
+ fields.push(("Self Role Aliases", r.aliases.join(", "), true));
+ }
+ },
+ None => {
+ fields.push(("Self Assignable", String::from("No"), true));
+ }
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .thumbnail(format!("https://www.colorhexa.com/{}.png", role.colour.hex().to_lowercase()))
+ .colour(role.colour)
+ .fields(fields)
+ ))?;
+ },
+ None => { message.channel_id.say("Unable to find that role.")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct ServerInfo;
+impl Command for ServerInfo {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Need some quick info about the current guild ?".to_string()),
+ guild_only: true,
+ aliases: vec!["serverinfo", "si", "sinfo"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ use serenity::model::channel::ChannelType::*;
+ use serenity::model::user::OnlineStatus::*;
+
+ let switches = get_switches(args.full().to_string());
+ let g = match switches.get("rest") {
+ Some(s) => {
+ if let Some((_, lock)) = parse_guild(s.to_string()) {
+ Some(lock)
+ } else {
+ None
+ }
+ },
+ None => message.guild()
+ };
+ if let Some(guild_lock) = g {
+ let guild = guild_lock.read().clone();
+ match switches.get("roles") {
+ None => {
+ let mut channels = (0,0,0);
+ for (_, channel_lock) in guild.channels.iter() {
+ let channel = channel_lock.read(); // mut
+ match channel.kind {
+ Text => { channels.0 += 1; },
+ Voice => { channels.1 += 1; },
+ Category => { channels.2 += 1; },
+ Group => {},
+ Private => {},
+ }
+ }
+ let mut members = (0,0,0);
+ for (user_id, _) in guild.members.iter() {
+ match user_id.to_user() {
+ Ok(u) => {
+ if u.bot {
+ members.1 += 1;
+ } else {
+ members.0 += 1;
+ }
+ },
+ Err(_) => {},
+ }
+ }
+ for (_, presence) in guild.presences.iter() {
+ match presence.status {
+ DoNotDisturb => { members.2 += 1; },
+ Idle => { members.2 += 1; },
+ Invisible => {},
+ Offline => {},
+ Online => { members.2 += 1; },
+ }
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .thumbnail(guild.icon_url().unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()))
+ .color(*colours::MAIN)
+ .field("ID", guild.id, true)
+ .field("Name", &guild.name, true)
+ .field("Owner", guild.owner_id.mention(), true)
+ .field("Region", guild.region, true)
+ .field(format!("Channels [{}]", guild.channels.len()), format!("Categories: {}\nText: {}\nVoice: {}", channels.2, channels.0, channels.1), true)
+ .field(format!("Members [{}/{}]", members.2, guild.members.len()), format!("Humans: {}\nBots: {}", members.0, members.1), true)
+ .field("Created", guild.id.created_at().format("%a, %d %h %Y @ %H:%M:%S").to_string(), false)
+ .field("Roles", guild.roles.len(), true)
+ .field("Emojis", guild.emojis.len(), true)
+ .title(guild.name)
+ ))?;
+ },
+ Some(_) => {
+ let mut roles_raw = guild.roles.values().collect::<Vec<&Role>>();
+ roles_raw.sort_by(|a, b| b.position.cmp(&a.position));
+ let roles = roles_raw.iter().map(|e| e.name.clone()).collect::<Vec<String>>();
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title(format!("Roles for {}. Count: {}", guild.name, roles.len()))
+ .description(roles.join("\n"))
+ .colour(*colours::BLUE)
+ ))?;
+ },
+ }
+ } else { message.channel_id.say("Could not find that guild.")?; }
+ Ok(())
+ }
+}
+
+pub struct Stats;
+impl Command for Stats {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("You wanna get to know me more ? How sweet.".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let (cached_guilds
+ ,cached_channels
+ ,cached_users
+ ,cached_messages) = {
+ let cache = CACHE.read();
+ (cache.guilds.len()
+ ,cache.channels.len()
+ ,cache.users.len()
+ ,cache.messages.values()
+ .fold(0, |a,m| {
+ a + m.len()
+ }))
+ };
+ let (db_guilds
+ ,db_users
+ ,db_notes
+ ,db_roles
+ ,db_timers
+ ,db_cases
+ ,db_tags
+ ,db_hackbans
+ ,db_premium) = {
+ (db.count_guilds().unwrap_or(-1)
+ ,db.count_users().unwrap_or(-1)
+ ,db.count_notes().unwrap_or(-1)
+ ,db.count_roles().unwrap_or(-1)
+ ,db.count_timers().unwrap_or(-1)
+ ,db.count_cases().unwrap_or(-1)
+ ,db.count_tags().unwrap_or(-1)
+ ,db.count_hackbans().unwrap_or(-1)
+ ,db.count_premium().unwrap_or(-1)
+ )
+ };
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Bot Stats")
+ .field("Cache", format!(
+ "Guilds: {}\nChannels: {}\nUsers: {}\nMessages: {}"
+ ,cached_guilds
+ ,cached_channels
+ ,cached_users
+ ,cached_messages
+ ), false)
+ .field("Database", format!(
+ "Guilds: {}\nUsers: {}\nNotes: {}\nSelf Roles: {}\nTimers: {}\nCases: {}\nTags: {}\nHackbans: {}\nPremium Guilds: {}"
+ ,db_guilds
+ ,db_users
+ ,db_notes
+ ,db_roles
+ ,db_timers
+ ,db_cases
+ ,db_tags
+ ,db_hackbans
+ ,db_premium
+ ), false)
+ .field("More coming soon", "...", false)
+ .colour(*colours::MAIN)
+ .timestamp(now!())
+ ))?;
+ Ok(())
+ }
+}
+
+pub struct Time; // Now
+impl Command for Time {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Don't have a clock ? (Optionally, you can provide an amount of hours to offset the time by.".to_string()),
+ usage: Some("[hour]".to_string()),
+ example: Some("-5".to_string()),
+ aliases: vec!["now"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ use chrono::offset::FixedOffset;
+ let utc = Utc::now();
+ let datetime = match args.single::<i32>() {
+ Ok(data) => {
+ let tz = FixedOffset::east(data * 3600);
+ utc.with_timezone(&tz)
+ },
+ Err(_) => {
+ let tz = FixedOffset::east(0);
+ utc.with_timezone(&tz)
+ },
+ };
+
+ let time = datetime.format("%H:%M").to_string();
+ let date = datetime.format("%A %e %B %Y").to_string();
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(*colours::MAIN)
+ .description(format!("**Time:** {}\n**Date:** {}\n**Timezone:** UTC{}", time, date, datetime.timezone()))
+ ))?;
+ Ok(())
+ }
+}
+
+pub struct UserInfo;
+impl Command for UserInfo {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Yea I'll bring something up for you, just don't be a creep. (Defaults to the author of the command.)".to_string()),
+ usage: Some("[user_resolvable]".to_string()),
+ example: Some("@fun".to_string()),
+ guild_only: true,
+ aliases: vec!["userinfo", "ui", "uinfo", "whois"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let (user, member) = match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((id, member)) => (id.to_user()?, member),
+ None => (message.author.clone(), message.member().ok_or("Failed to get member.")?),
+ };
+ let user_data = db.get_user(user.id.0 as i64, guild_id.0 as i64)?;
+ let mut roles = member.roles.iter()
+ .map(|c| match c.to_role_cached() {
+ Some(r) => r.name,
+ None => c.0.to_string(),
+ })
+ .collect::<Vec<String>>();
+ roles.sort();
+ let roles = if roles.is_empty() {
+ "None".to_string()
+ } else {
+ roles.join(", ")
+ };
+ let dates = format!(
+ "Created: {}\nJoined: {}{}",
+ user.created_at()
+ .format("%a, %d %h %Y @ %T")
+ .to_string(),
+ member.joined_at
+ .and_then(|t| Some(t.with_timezone(&Utc)))
+ .unwrap_or(Utc::now())
+ .format("%a, %d %h %Y @ %T")
+ .to_string(),
+ user_data.registered.map_or(String::new(), |r| {
+ format!("\nRegistered: {}", r
+ .format("%a, %d %h %Y @ %T")
+ .to_string())
+ })
+ );
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(member.colour().unwrap_or(*colours::MAIN))
+ .thumbnail(user.face())
+ .title(&user.tag())
+ .field("ID", user.id, true)
+ .field("Mention", user.mention(), true)
+ .field("Nickname", member.display_name().into_owned(), true)
+ .field("Dates", dates, false)
+ .field(format!("Roles [{}]", member.roles.len()), roles, false)
+ ))?;
+ }
+ Ok(())
+ }
+}
+
+/* pub struct Weather;
+impl Command for Weather {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ bucket: Some("weather".to_string()),
+ desc: Some("Check on the current weather at a given city. By default this will use the units used at that location, but units can be manually selected. Options are si, us, uk, ca".to_string()),
+ usage: Some("<city name> [/unit]".to_string()),
+ example: Some("london /us".to_string()),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ let data = ctx.data.lock();
+ if let Some(api) = data.get::<ApiClient>() {
+ let switches = get_switches(args.full().to_string());
+ let rest = switches.get("rest");
+ // TODO: Refactor this
+ let mut units = Units::Auto;
+ if switches.len() > 1 {
+ switches.keys().for_each(|k| {
+ match k.as_str() {
+ "uk" => { units = Units::UK; },
+ "c" | "ca" => { units = Units::CA; },
+ "si" => { units = Units::SI; },
+ "us" => { units = Units::Imperial; },
+ _ => {},
+ }
+ });
+ }
+ message.channel_id.broadcast_typing()?;
+ if let Some(loc) = rest {
+ match api.weather(loc, units) {
+ Some((city_info, Ok(body))) => {
+ if let Some(current) = body.currently {
+ if let Some(daily_data) = body.daily {
+ let daily = &daily_data.data[0];
+ let temp = current.temperature.unwrap_or(NAN);
+ let temp_high = current.temperature_high.unwrap_or(daily.temperature_high.unwrap_or(NAN));
+ let temp_low = current.temperature_low.unwrap_or(daily.temperature_low.unwrap_or(NAN));
+ let feels_like = current.apparent_temperature.unwrap_or(NAN);
+ let wind = current.wind_speed.unwrap_or(NAN);
+ let visi = current.visibility.unwrap_or(NAN);
+ let pressure = current.pressure.unwrap_or(NAN);
+ let humidity = current.humidity.unwrap_or(NAN)*100.0;
+ let icon = match current.icon {
+ Some(ic) => {
+ match ic {
+ ClearDay => "The sky is clear",
+ ClearNight => "The sky is clear",
+ Rain => "It is raining",
+ Snow => "It is snowing",
+ Sleet => "It is sleeting",
+ Wind => "It is windy",
+ Fog => "It is foggy",
+ Cloudy => "The sky is cloudy",
+ PartlyCloudyDay => "The sky is partly cloudy",
+ PartlyCloudyNight => "The sky is partly cloudy",
+ Hail => "It is hailing",
+ Thunderstorm => "There is a thunderstorm",
+ Tornado => "There is a tornado",
+ }
+ },
+ None => "The sky is clear",
+ };
+ let response_units = body.flags.and_then(|e| Some(e.units)).unwrap_or(Units::Imperial);
+ let (temp_unit, speed_unit, dist_unit) = match response_units {
+ Units::SI => { ("C", "m/s", "km") },
+ Units::CA => { ("C", "kmph", "km") },
+ Units::UK => { ("C", "mph", "mi") },
+ _ => { ("F", "mph", "mi") },
+ };
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title(format!("Weather in {}", city_info))
+ .description(format!("_It is currently **{}°{temp}** with wind of **{} {speed}** making it feel like **{}°{temp}**. {} with a visibility of about **{} {dist}**._",
+ temp,
+ wind,
+ feels_like,
+ icon,
+ visi,
+ temp = temp_unit,
+ speed = speed_unit,
+ dist = dist_unit
+ ))
+ .field("Temperature", format!(
+ "Current: **{}°{temp}**\nLow/High: **{}°{temp} / {}°{temp}**",
+ temp,
+ temp_low,
+ temp_high,
+ temp = temp_unit
+ ), true)
+ .field("Wind Chill", format!(
+ "Feels Like: **{}°{temp}**\nWind Speed: **{} {speed}**",
+ feels_like,
+ wind,
+ temp = temp_unit,
+ speed = speed_unit
+ ), true)
+ .field("Atmosphere", format!(
+ "Humidity: **{}%**\nPressure: **{} mbar**",
+ humidity,
+ pressure,
+ ), true)
+ .colour(*colours::MAIN)
+ .timestamp(now!())
+ .footer(|f| f.text("Forecast by Dark Sky"))
+ ))?;
+ }
+ }
+ },
+ Some((_, Err(why))) => {
+ message.channel_id.say(format!("Something went wrong while getting the forecast.\n{}", why))?;
+ },
+ None => {
+ message.channel_id.say("An error occurred while resolving the location.")?;
+ },
+ }
+ } else { message.channel_id.say("Please enter a location.")?; }
+ } else { failed!(API_FAIL); }
+ Ok(())
+ }
+} */
+
+pub struct Wisp; // BotInfo
+impl Command for Wisp {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Information about me !".to_string()),
+ usage: Some("".to_string()),
+ aliases: vec!["botinfo", "bi", "binfo", "bot", "invite"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ use serenity::builder::CreateEmbed;
+
+ let data = ctx.data.lock();
+ // let (guild_count, shard_count, thumbnail) = {
+ // let cache = CACHE.read();
+ // (cache.guilds.len(), cache.shard_count, cache.user.face())
+ // };
+ let thumbnail = CACHE.read().user.face();
+ let owner = data.get::<Owner>().expect("Failed to get owner").to_user()?;
+ let sys = System::new();
+ let embed = CreateEmbed::default()
+ // .description("Hi! I'm Wisp, a general purpose bot created in [Rust](http://www.rust-lang.org/) using [Serenity](https://github.com/serenity-rs/serenity).")
+ .description("Hey ! I'm Wisp, I was written in [Rust](https://www.rust-lang.org/) using [Serenity](https://github.com/serenity-rs/serenity).")
+ .field("Owner/ Developer", format!(
+ "Name: {}\nID: {}"
+ ,owner.tag()
+ ,owner.id)
+ ,true)
+ // .field("Links", format!(
+ // "[Support Server]({})\n[Invite]({})\n[GitLab]({})\n[Patreon]({})"
+ // ,SUPPORT_SERV_INVITE
+ // ,BOT_INVITE
+ // ,GITLAB_LINK
+ // ,PATREON_LINK)
+ // ,true)
+ .field("Useful Links", format!(
+ // "[Support Server]({})\n[Invite]({})\n[GitLab]({})\n[Patreon]({})"
+ "[Invite]({})"
+ // ,SUPPORT_SERV_INVITE
+ ,BOT_INVITE)
+ // ,GITLAB_LINK
+ // ,PATREON_LINK)
+ ,true) // true
+ // .field("Counts", format!(
+ // "Guilds: {}\nShards: {}"
+ // ,guild_count
+ // ,shard_count)
+ // ,false)
+ .footer(|f| f
+ .text("Want to request a feature ? Submit one over at [email protected] !"))
+ .thumbnail(thumbnail)
+ .colour(*colours::MAIN);
+ if let Some(process) = sys.get_process(get_current_pid()) {
+ message.channel_id.send_message(|m| m
+ .embed(|_| embed
+ .field("System Info", format!(
+ "Type: {} {}\nUptime: {}"
+ ,sys_info::os_type().unwrap_or(String::from("OS Not Found"))
+ ,sys_info::os_release().unwrap_or(String::from("Release Not Found"))
+ ,seconds_to_hrtime(sys.get_uptime() as usize))
+ ,false)
+ .field("Process Info", format!(
+ "Memory Usage: {} MB\nCPU Usage {}%\nUptime: Temporarily Disabled"
+ ,process.memory()/1000 // convert to MB
+ ,(process.cpu_usage()*100.0).round()/100.0 // round to 2 decimals
+ /* ,seconds_to_hrtime((sys.get_uptime() - process.start_time()) as usize) */)
+ ,true)
+ ))?;
+ } else {
+ message.channel_id.send_message(|m| m
+ .embed(|_| embed
+ ))?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/mod.rs b/src/modules/commands/mod.rs
new file mode 100644
index 0000000..76dca00
--- /dev/null
+++ b/src/modules/commands/mod.rs
@@ -0,0 +1,4 @@
+pub mod admins;
+pub mod general;
+pub mod mods;
+pub mod owners;
diff --git a/src/modules/commands/mods/hackbans.rs b/src/modules/commands/mods/hackbans.rs
new file mode 100644
index 0000000..5cf8030
--- /dev/null
+++ b/src/modules/commands/mods/hackbans.rs
@@ -0,0 +1,117 @@
+use crate::core::colours;
+use crate::core::consts::DB as db;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::id::UserId;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct HackbanAdd;
+impl Command for HackbanAdd {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Adds a user to the hackban list. Users on this list will be banned on joining.".to_string()),
+ desc: Some("This ? It'll add someone to the \"hackban\" list, basically, they'll be banned upon joining.".to_string()),
+ usage: Some("<user_id> [reason]".to_string()),
+ example: Some("217348698294714370 spamming images in general".to_string()),
+ required_permissions: Permissions::BAN_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let guild_id = message.guild_id.unwrap();
+ let hackbans = db.get_hackbans(guild_id.0 as i64)?;
+ let user_id = UserId(args.single::<u64>()?);
+ match hackbans.iter().find(|e| e.id as u64 == user_id.0) {
+ Some(_) => { message.channel_id.say("User is already hackbanned.")?; },
+ None => {
+ let reason = args.single::<String>().ok();
+ db.new_hackban(user_id.0 as i64, guild_id.0 as i64, reason.clone())?;
+ message.channel_id.say(format!(
+ "Added {} to the hackban list{}",
+ user_id.0,
+ match reason {
+ Some(r) => format!(" with reason `{}`", r),
+ None => String::new(),
+ }
+ ))?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub struct HackbanRemove;
+impl Command for HackbanRemove {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Feeling forgiveful ? Take someone off the \"hackban\" list !".to_string()),
+ usage: Some("<user_id>".to_string()),
+ example: Some("217348698294714370".to_string()),
+ required_permissions: Permissions::BAN_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let guild_id = message.guild_id.unwrap();
+ let hackbans = db.get_hackbans(guild_id.0 as i64)?;
+ let user_id = UserId(args.single::<u64>()?);
+ match hackbans.iter().find(|e| e.id as u64 == user_id.0) {
+ None => { message.channel_id.say("User isn't hackbanned.")?; },
+ Some(_) => {
+ db.del_hackban(user_id.0 as i64, guild_id.0 as i64)?;
+ message.channel_id.say(format!(
+ "Removed {} from the hackban list",
+ user_id.0
+ ))?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub struct HackbanList;
+impl Command for HackbanList {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Lets users on the hackban list along with their reasons, if provided.".to_string()),
+ desc: Some("Checking up ? See who's on the \"hackban\" list.".to_string()),
+ required_permissions: Permissions::BAN_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ let guild_id = message.guild_id.unwrap();
+ let hackbans = db.get_hackbans(guild_id.0 as i64)?;
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Hackbans")
+ .description(
+ hackbans.iter().cloned().map(|e| format!(
+ "{}{}",
+ e.id,
+ format!(": `{}`", e.reason.unwrap_or(String::new()))
+ ))
+ .collect::<Vec<String>>()
+ .join("\n")
+ )
+ .colour(*colours::MAIN)
+ ))?;
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/mods/info.rs b/src/modules/commands/mods/info.rs
new file mode 100644
index 0000000..11c2119
--- /dev/null
+++ b/src/modules/commands/mods/info.rs
@@ -0,0 +1,49 @@
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct ModInfo;
+impl Command for ModInfo {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Someone acting up ? Look em' up.".to_string()),
+ usage: Some("<user_resolvable>".to_string()),
+ example: Some("@fun".to_string()),
+ aliases: vec!["mi", "minfo"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user_id, _)) => {
+ let user = db.get_user(user_id.0 as i64, guild_id.0 as i64)?;
+ let cases = db.get_cases(user_id.0 as i64, guild_id.0 as i64)?;
+ let case_fmt = cases.iter().map(|c| format!("Type: {}\nModerator: {}\nTimestamp: {}", c.casetype, c.moderator, c.timestamp)).collect::<Vec<String>>().join("\n");
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Moderator info")
+ .field("Watchlist", { if user.watchlist { "Yes" } else { "No" } }, false)
+ .field("Cases", if case_fmt.is_empty() { "None" } else { case_fmt.as_str() }, false)
+ ))?;
+ },
+ None => { message.channel_id.say("I couldn't find that user.")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/mods/kickbans.rs b/src/modules/commands/mods/kickbans.rs
new file mode 100644
index 0000000..ca26350
--- /dev/null
+++ b/src/modules/commands/mods/kickbans.rs
@@ -0,0 +1,97 @@
+use crate::core::consts::DB as db;
+use crate::core::utils::parse_user;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+// TODO: add case entry on successful kick/ban
+
+pub struct BanUser;
+impl Command for BanUser {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Bans a user. If unable to resolve to a bannable user, the user is hackbanned instead.".to_string()),
+ desc: Some("Wield this with power... (If I am unable to get the user, they will be \"hackbanned\" instead)".to_string()),
+ usage: Some("<user_resolvable> [time_resolvable] [reason]".to_string()),
+ example: Some("217348698294714370 spamming images in general".to_string()),
+ required_permissions: Permissions::BAN_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let guild_id = message.guild_id.ok_or("Failed to get guild_id")?;
+ let arg = args.single::<String>()?;
+ if let Some((_, member)) = parse_user(arg.clone(), guild_id) {
+ let (days, reason) = {
+ match args.single_quoted_n::<u8>().ok() {
+ None => {
+ (None, Some(args.rest()))
+ },
+ days => {
+ args.skip();
+ (days, Some(args.rest()))
+ }
+ }
+ };
+ match (days, reason) {
+ (Some(d), Some(r)) => { member.ban(&(d,r))?; },
+ (Some(d), None) => { member.ban(&d)?; },
+ (None, Some(r)) => { member.ban(&r)?; },
+ (None, None) => { member.ban(&0)?; },
+ }
+ message.channel_id.say(format!(
+ "Banned {} successfully{}."
+ ,member.user.read().tag()
+ ,if let Some(r) = reason { format!("with reason {}", r) } else { "".to_string() }
+ ))?;
+ } else {
+ if let Ok(id) = arg.parse::<i64>() {
+ db.new_hackban(id, guild_id.0 as i64, args.single::<String>().ok())?;
+ } else {
+ message.channel_id.say("User does not exist in guild and argument is not a valid ID.")?;
+ }
+ }
+ Ok(())
+ }
+}
+
+pub struct KickUser;
+impl Command for KickUser {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ // desc: Some("Kicks a user. Reason is discarded due to a limitation in Serenity but will be implemented at a later date.".to_string()),
+ desc: Some("Want to temporarily set-back someone ? (Reason is currentaly disabled.)".to_string()),
+ usage: Some("<user_resolvable> [reason]".to_string()),
+ example: Some("217348698294714370 spamming images in general".to_string()),
+ required_permissions: Permissions::KICK_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let guild_id = message.guild_id.ok_or("Failed to get guild_id")?;
+ if let Some((_, member)) = parse_user(args.single::<String>()?, guild_id) {
+ let _reason = args.rest();
+ member.kick()?;
+ message.channel_id.say(format!(
+ "Kicked {} successfully."
+ ,member.user.read().tag()
+ ))?;
+ } else {
+ message.channel_id.say("User does not exist in guild.")?;
+ }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/mods/mod.rs b/src/modules/commands/mods/mod.rs
new file mode 100644
index 0000000..8a852cf
--- /dev/null
+++ b/src/modules/commands/mods/mod.rs
@@ -0,0 +1,80 @@
+// pub mod hackbans;
+// pub mod info;
+pub mod kickbans;
+// pub mod mute;
+pub mod notes;
+// pub mod roles;
+pub mod watchlist;
+
+// use self::hackbans::*;
+// use self::info::*;
+use self::kickbans::*;
+// use self::mute::*;
+use self::notes::*;
+// use self::roles::*;
+use self::watchlist::*;
+use serenity::framework::standard::CreateGroup;
+
+/* pub fn init_hackbans() -> CreateGroup {
+ CreateGroup::default()
+ .prefixes(vec!["hackban", "hb"])
+ .guild_only(true)
+ .help_available(true)
+ .cmd("add", HackbanAdd)
+ .cmd("remove", HackbanRemove)
+ .cmd("list", HackbanList)
+} */
+
+/* pub fn init_info() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .cmd("modinfo", ModInfo)
+} */
+
+pub fn init_kickbans() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .cmd("kick", KickUser)
+ .cmd("ban", BanUser)
+}
+
+/* pub fn init_mute() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .cmd("mute", Mute)
+ .cmd("unmute", Unmute)
+} */
+
+pub fn init_notes() -> CreateGroup {
+ CreateGroup::default()
+ .prefix("note")
+ .guild_only(true)
+ .help_available(true)
+ .cmd("add", NoteAdd)
+ .cmd("delete", NoteRemove)
+ .cmd("list", NoteList)
+}
+
+/* pub fn init_roles() -> CreateGroup {
+ CreateGroup::default()
+ .guild_only(true)
+ .help_available(true)
+ .cmd("register", Register)
+ .cmd("addrole", AddRole)
+ .cmd("removerole", RemoveRole)
+ .cmd("rolecolour", RoleColour)
+} */
+
+pub fn init_watchlist() -> CreateGroup {
+ CreateGroup::default()
+ .prefixes(vec!["watchlist", "wl"])
+ .guild_only(true)
+ .help_available(true)
+ .default_cmd(WatchlistList)
+ .cmd("add", WatchlistAdd)
+ .cmd("delete", WatchlistRemove)
+ .cmd("list", WatchlistList)
+}
diff --git a/src/modules/commands/mods/mute.rs b/src/modules/commands/mods/mute.rs
new file mode 100644
index 0000000..12ee36d
--- /dev/null
+++ b/src/modules/commands/mods/mute.rs
@@ -0,0 +1,179 @@
+use chrono::Utc;
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::model::TC;
+use crate::core::utils::*;
+use serenity::builder::CreateMessage;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::id::ChannelId;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct Mute;
+impl Command for Mute {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Someone being annoyin ? Mute em' ! (Optionally, include a reason and/ or time for the mute to expire).".to_string()),
+ usage: Some("<user_resolvable> [/t time] [/r reason]".to_string()),
+ example: Some("@fun /t 1day /r spam".to_string()),
+ required_permissions: Permissions::MANAGE_ROLES | Permissions::MUTE_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_lock) = message.guild() {
+ let guild = {
+ guild_lock.read().clone()
+ };
+ if let Some((_, mut member)) = parse_user(args.single::<String>().unwrap_or(String::new()), guild.id) {
+ let guild_data = db.get_guild(guild.id.0 as i64)?;
+ if guild_data.mute_setup {
+ let switches = get_switches(args.rest().to_string());
+ let time = match switches.get("t") {
+ Some(s) => hrtime_to_seconds(s.clone()),
+ None => 0,
+ };
+ let reason = match switches.get("r") {
+ Some(s) => s.clone(),
+ None => String::new(),
+ };
+ if let Some(mute_role) = guild.roles.values().find(|e| e.name.to_lowercase() == "muted") {
+ if member.roles.contains(&mute_role.id) {
+ message.channel_id.say("Member already muted.")?;
+ } else {
+ member.add_role(mute_role)?;
+ let user = {
+ member.user.read().clone()
+ };
+ let case = db.new_case(user.id.0 as i64, guild.id.0 as i64, "Mute".to_string(), Some(reason.clone()), message.author.id.0 as i64)?;
+ let mut description = format!(
+ "**User:** {} ({})\n**Moderator:** {} ({})"
+ ,user.tag()
+ ,user.id.0
+ ,message.author.tag()
+ ,message.author.id.0);
+ if time != 0 {
+ let data = ctx.data.lock();
+ if let Some(tc_lock) = data.get::<TC>() {
+ let tc = tc_lock.lock();
+ let data = format!("UNMUTE||{}||{}||{}||{}||{}||{}",
+ user.id.0,
+ guild.id.0,
+ mute_role.id.0,
+ if guild_data.modlog && guild_data.modlog_channel > 0 {
+ guild_data.modlog_channel
+ } else { message.channel_id.0 as i64 },
+ time,
+ case.id);
+ let start_time = Utc::now().timestamp();
+ let end_time = start_time + time;
+ db.new_timer(start_time, end_time, data)?;
+ tc.request();
+ description = format!(
+ "{}\n**Duration:** {}"
+ ,description
+ ,seconds_to_hrtime(time as usize));
+ } else {
+ message.channel_id.say("Something went wrong with the timer.")?;
+ }
+ }
+ if !reason.is_empty() {
+ description = format!(
+ "{}\n**Reason:** {}"
+ ,description
+ ,reason.to_string());
+ }
+ let response = CreateMessage::default()
+ .embed(|e| e
+ .title("Member Muted")
+ .colour(*colours::RED)
+ .description(description)
+ .timestamp(now!()));
+
+ if guild_data.modlog && guild_data.modlog_channel > 0 {
+ let channel = ChannelId(guild_data.modlog_channel as u64);
+ channel.send_message(|_| response)?;
+ } else {
+ message.channel_id.send_message(|_| response)?;
+ }
+ }
+ } else { message.channel_id.say("No mute role")?; }
+ } else {
+ message.channel_id.say("Please run `setup` before using this command. Without it, muting may not work right.")?;
+ }
+ } else { message.channel_id.say("I couldn't find that user.")?; }
+ } else { failed!(GUILD_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct Unmute;
+impl Command for Unmute {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("How nice, you'll unmute them !".to_string()),
+ usage: Some("<user_resolvable>".to_string()),
+ example: Some("@fun".to_string()),
+ required_permissions: Permissions::MANAGE_ROLES | Permissions::MUTE_MEMBERS,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_lock) = message.guild() {
+ let guild = {
+ guild_lock.read().clone()
+ };
+ if let Some((_, mut member)) = parse_user(args.single::<String>().unwrap_or(String::new()), guild.id) {
+ let guild_data = db.get_guild(guild.id.0 as i64)?;
+ if guild_data.mute_setup {
+ if let Some(mute_role) = guild.roles.values().find(|e| e.name.to_lowercase() == "muted") {
+ let user = {
+ member.user.read().clone()
+ };
+ let mut description = format!(
+ "**User:** {} ({})\n**Moderator:** {} ({})"
+ ,user.tag()
+ ,user.id.0
+ ,message.author.tag()
+ ,message.author.id.0);
+ let response = CreateMessage::default()
+ .embed(|e| e
+ .title("Member Unmuted")
+ .colour(*colours::GREEN)
+ .description(description)
+ .timestamp(now!()));
+
+ if member.roles.contains(&mute_role.id) {
+ member.remove_role(mute_role)?;
+ if guild_data.modlog && guild_data.modlog_channel > 0 {
+ let channel = ChannelId(guild_data.modlog_channel as u64);
+ channel.send_message(|_| response)?;
+ } else {
+ message.channel_id.send_message(|_| response)?;
+ }
+ } else {
+ message.channel_id.say("Member was not muted.")?;
+ }
+ } else { message.channel_id.say("No mute role")?; }
+ } else {
+ message.channel_id.say("Please run `setup` before using this command. Without it, muting may not work right.")?;
+ }
+ } else { message.channel_id.say("I couldn't find that user.")?; }
+ } else { failed!(GUILD_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/mods/notes.rs b/src/modules/commands/mods/notes.rs
new file mode 100644
index 0000000..495e93d
--- /dev/null
+++ b/src/modules/commands/mods/notes.rs
@@ -0,0 +1,108 @@
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct NoteAdd;
+impl Command for NoteAdd {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Of the forgetful kind ? Add a note to someone !".to_string()),
+ usage: Some("<user_resolvable> <note>".to_string()),
+ example: Some("@fun test note".to_string()),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user,_)) => {
+ let note = args.rest().to_string();
+ let data = db.new_note(user.0 as i64, guild_id.0 as i64, note, message.author.id.0 as i64)?;
+ message.channel_id.say(format!("Added note `{}`.", data.note))?;
+ },
+ None => { message.channel_id.say("I couldn't find that user")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct NoteRemove;
+impl Command for NoteRemove {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Oh, you remembered...".to_string()),
+ usage: Some("<user_resolvable> <index>".to_string()),
+ example: Some("@fun 3".to_string()),
+ aliases: vec!["del", "rm"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user,_)) => {
+ let index = args.single::<i32>().unwrap_or(0);
+ let data = db.del_note(index, user.0 as i64, guild_id.0 as i64)?;
+ message.channel_id.say(format!("Deleted note `{}`.", data))?;
+ },
+ None => { message.channel_id.say("I couldn't find that user")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct NoteList;
+impl Command for NoteList {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Checking in ? See all the notes on someone.".to_string()),
+ usage: Some("<user_resolvable>".to_string()),
+ example: Some("@fun".to_string()),
+ aliases: vec!["ls"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user_id, member)) => {
+ let notes = db.get_notes(user_id.0 as i64, guild_id.0 as i64)?;
+ let notes_fmt = notes.iter().map(|n| n.to_string()).collect::<Vec<String>>().join("\n\n");
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .colour(*colours::MAIN)
+ .title(format!("Notes for {}", member.display_name().into_owned()))
+ .description(notes_fmt)
+ ))?;
+ },
+ None => { message.channel_id.say("I couldn't find that user")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/mods/roles.rs b/src/modules/commands/mods/roles.rs
new file mode 100644
index 0000000..1d5e8d5
--- /dev/null
+++ b/src/modules/commands/mods/roles.rs
@@ -0,0 +1,338 @@
+use chrono::Utc;
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::model::TC;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::id::{
+ ChannelId,
+ RoleId
+};
+use serenity::model::guild::Member;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct Register;
+impl Command for Register {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("A premium command that adds roles to a user (from the self roles list only), and depending on the settings for the command, will apply either a member role or a cooldown role with a timer. When the timer ends, cooldown is removed and member is added. In order for the switch to occur automatically, this command must be used. See the premium commands for more information on configuring this command.".to_string()),
+ usage: Some("<user_resolvable> <role_resolvables as CSV>".to_string()),
+ example: Some("@fun gamer, techie".to_string()),
+ aliases: vec!["reg"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_ROLES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, ctx: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ debug!("REGISTER TRACE: Begin register for user: {}", message.author.id.0);
+ if let Some(guild_id) = message.guild_id {
+ let settings = db.get_premium(guild_id.0 as i64).map_err(|_| "Premium is required to use this command.")?;
+ let guild_data = db.get_guild(guild_id.0 as i64)?;
+ let roles = db.get_roles(guild_id.0 as i64)?;
+ debug!("REGISTER TRACE: Settings obtained");
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user_id, mut member)) => {
+ debug!("REGISTER TRACE: User matched");
+ let channel = if guild_data.modlog {
+ ChannelId(guild_data.modlog_channel as u64)
+ } else { message.channel_id };
+ let list = args.rest().split(",").map(|s| s.trim().to_string());
+ let mut to_add = Vec::new();
+ for r1 in list {
+ if let Some((r, _)) = parse_role(r1.clone(), guild_id) {
+ if settings.cooldown_restricted_roles.contains(&(r.0 as i64)) { continue; }
+ to_add.push(r);
+ } else if let Some(i) = roles.iter().position(|r| r.aliases.contains(&r1)) {
+ if settings.cooldown_restricted_roles.contains(&(roles[i].id)) { continue; }
+ to_add.push(RoleId(roles[i].id as u64));
+ }
+ }
+ debug!("REGISTER TRACE: Resolved roles");
+ let mut to_add = filter_roles(to_add, guild_id.member(&message.author)?);
+ debug!("REGISTER TRACE: Filtered roles");
+ for (i, role_id) in to_add.clone().iter().enumerate() {
+ if member.roles.contains(role_id) {
+ to_add.remove(i);
+ continue;
+ }
+ if let Err(_) = member.add_role(*role_id) {
+ to_add.remove(i);
+ };
+ }
+ debug!("REGISTER TRACE: Roles added");
+ if let Some(role) = settings.register_cooldown_role {
+ member.add_role(RoleId(role as u64))?;
+ debug!("REGISTER TRACE: Added cooldown role");
+ if let Some(member_role) = settings.register_member_role {
+ debug!("REGISTER TRACE: Added member role");
+ let data = ctx.data.lock();
+ let tc_lock = data.get::<TC>().ok_or("Failed to obtain timer client.")?;
+ let tc = tc_lock.lock();
+ let dur = match settings.register_cooldown_duration {
+ Some(dur) => dur,
+ None => DAY as i32,
+ };
+ let data = format!("COOLDOWN||{}||{}||{}||{}",
+ user_id.0,
+ guild_id.0,
+ member_role,
+ role);
+ let start_time = Utc::now().timestamp();
+ let end_time = start_time + dur as i64;
+ check_error!(db.new_timer(start_time, end_time, data));
+ tc.request();
+ debug!("REGISTER TRACE: Timer registered");
+ }
+ } else if let Some(role) = settings.register_member_role {
+ member.add_role(RoleId(role as u64))?;
+ debug!("REGISTER TRACE: Added member role (No cooldown role)");
+ }
+ let desc = if !to_add.is_empty() {
+ to_add.iter().map(|r| match r.to_role_cached() {
+ Some(role) => role.name,
+ None => r.0.to_string(),
+ })
+ .collect::<Vec<String>>()
+ .join("\n")
+ } else { String::new() };
+ debug!("REGISTER TRACE: Built log message");
+ channel.send_message(|m| m
+ .embed(|e| e
+ .title(format!(
+ "Registered {} with the following roles:",
+ member.user.read().tag()
+ ))
+ .description(desc)
+ .colour(member.colour().unwrap_or(*colours::MAIN))
+ .timestamp(now!())
+ ))?;
+ debug!("REGISTER TRACE: Sent log message");
+ if guild_data.introduction && guild_data.introduction_channel>0 {
+ let channel = ChannelId(guild_data.introduction_channel as u64);
+ if guild_data.introduction_type == "embed" {
+ send_welcome_embed(guild_data.introduction_message, &member, channel)?;
+ } else {
+ channel.say(parse_welcome_items(guild_data.introduction_message, &member))?;
+ }
+ debug!("REGISTER TRACE: Sent introduction message");
+ }
+ },
+ None => { message.channel_id.say("I couldn't find that user.")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ debug!("REGISTER TRACE: Register completed successfully");
+ Ok(())
+ }
+}
+
+pub struct AddRole;
+impl Command for AddRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Welcome to the club ! Add a role(s) to someone !".to_string()),
+ usage: Some("<user_resolvable> <role_resolvables as CSV>".to_string()),
+ example: Some("@fun red, green".to_string()),
+ aliases: vec!["ar"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_ROLES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some((_, mut member)) = parse_user(args.single::<String>()?, guild_id) {
+ let list = args.rest().split(",").map(|s| s.trim().to_string());
+ let mut to_add = Vec::new();
+ let mut failed = Vec::new();
+ for r1 in list {
+ if let Some((s,_)) = parse_role(r1.clone(), guild_id) {
+ to_add.push(s);
+ } else {
+ failed.push(format!("Could not locate {}", r1));
+ }
+ }
+ let mut to_add = filter_roles(to_add, guild_id.member(&message.author)?);
+ for (i, role_id) in to_add.clone().iter().enumerate() {
+ if member.roles.contains(role_id) {
+ to_add.remove(i);
+ failed.push(format!(
+ "You already have {}",
+ match role_id.to_role_cached() {
+ Some(role) => role.name,
+ None => role_id.0.to_string(),
+ }));
+ }
+ if let Err(_) = member.add_role(*role_id) {
+ to_add.remove(i);
+ failed.push(format!(
+ "Failed to add {}",
+ match role_id.to_role_cached() {
+ Some(role) => role.name,
+ None => role_id.0.to_string(),
+ }));
+ };
+ }
+ let mut fields = Vec::new();
+ if !to_add.is_empty() {
+ fields.push(("Added Roles", to_add.iter()
+ .map(|r| match r.to_role_cached() {
+ Some(role) => role.name,
+ None => r.0.to_string(),
+ })
+ .collect::<Vec<String>>()
+ .join("\n"),
+ false));
+ }
+ if !failed.is_empty() {
+ fields.push(("Failed to Add", failed.join("\n"), false));
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Add Role Summary")
+ .fields(fields)
+ .colour(member.colour().unwrap_or(*colours::MAIN))
+ ))?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct RemoveRole;
+impl Command for RemoveRole {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("It doesn't have to end like this... Remove someone's role(s).".to_string()),
+ usage: Some("<user_resolvable> <role_resolvables as CSV>".to_string()),
+ example: Some("@fun red, green".to_string()),
+ aliases: vec!["rr"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_ROLES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ if let Some((_, mut member)) = parse_user(args.single::<String>()?, guild_id) {
+ let list = args.rest().split(",").map(|s| s.trim().to_string());
+ let mut to_remove = Vec::new();
+ let mut failed = Vec::new();
+ for r1 in list {
+ if let Some((s,_)) = parse_role(r1.clone(), guild_id) {
+ to_remove.push(s);
+ } else {
+ failed.push(format!("Could not locate {}", r1));
+ }
+ }
+ let mut to_remove = filter_roles(to_remove, guild_id.member(&message.author)?);
+ for (i, role_id) in to_remove.clone().iter().enumerate() {
+ if !member.roles.contains(role_id) {
+ to_remove.remove(i);
+ failed.push(format!(
+ "You don't have {}",
+ match role_id.to_role_cached() {
+ Some(role) => role.name,
+ None => role_id.0.to_string(),
+ }));
+ }
+ if let Err(_) = member.remove_role(*role_id) {
+ to_remove.remove(i);
+ failed.push(format!(
+ "Failed to remove {}",
+ match role_id.to_role_cached() {
+ Some(role) => role.name,
+ None => role_id.0.to_string(),
+ }));
+ };
+ }
+ let mut fields = Vec::new();
+ if !to_remove.is_empty() {
+ fields.push(("Removed Roles", to_remove.iter()
+ .map(|r| match r.to_role_cached() {
+ Some(role) => role.name,
+ None => r.0.to_string(),
+ })
+ .collect::<Vec<String>>()
+ .join("\n"),
+ false));
+ }
+ if !failed.is_empty() {
+ fields.push(("Failed to Remove", failed.join("\n"), false));
+ }
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Remove Role Summary")
+ .fields(fields)
+ .colour(member.colour().unwrap_or(*colours::MAIN))
+ ))?;
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct RoleColour;
+impl Command for RoleColour {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Feeling \"colourful\" ? Change the colour of a role !".to_string()),
+ usage: Some("<role_resolvable> <colour>".to_string()),
+ example: Some("418130449089691658 e0ffff".to_string()),
+ aliases: vec!["rc", "rolecolor"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_ROLES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_role(args.single_quoted::<String>().unwrap_or(String::new()), guild_id) {
+ Some((_, mut role)) => {
+ let input = args.single::<String>()?;
+ let colour_as_hex = if input.starts_with("#") {
+ &input[1..]
+ } else { input.as_str() };
+ let colour = u64::from_str_radix(colour_as_hex, 16)?;
+ role.edit(|r| r.colour(colour))?;
+ message.channel_id.say(format!("Colour of `{}` changed to `#{:06X}`", role.name, colour))?;
+ },
+ None => { message.channel_id.say("I couldn't find that role")?; },
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+fn filter_roles(roles: Vec<RoleId>, member: Member) -> Vec<RoleId> {
+ let highest = match member.highest_role_info() {
+ Some((_,h)) => h,
+ None => -1,
+ };
+ roles.into_iter()
+ .filter_map(|r| {
+ let role = r.to_role_cached()?;
+ match role.position >= highest {
+ true => None,
+ false => Some(r),
+ }
+ })
+ .collect()
+}
diff --git a/src/modules/commands/mods/watchlist.rs b/src/modules/commands/mods/watchlist.rs
new file mode 100644
index 0000000..b4470a5
--- /dev/null
+++ b/src/modules/commands/mods/watchlist.rs
@@ -0,0 +1,113 @@
+use crate::core::colours;
+use crate::core::consts::*;
+use crate::core::consts::DB as db;
+use crate::core::utils::*;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::model::id::UserId;
+use serenity::model::Permissions;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct WatchlistAdd;
+impl Command for WatchlistAdd {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("What have they done now... Add someone the the watchlist.".to_string()),
+ usage: Some("<user_resolvable>".to_string()),
+ example: Some("@fun".to_string()),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user_id, member)) => {
+ let mut user_data = db.get_user(user_id.0 as i64, guild_id.0 as i64)?;
+ user_data.watchlist = true;
+ db.update_user(user_id.0 as i64, guild_id.0 as i64, user_data)?;
+ message.channel_id.say(format!("Set {} to watchlist status.", member.display_name().into_owned()))?;
+ },
+ None => { message.channel_id.say("I couldn't find that user")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct WatchlistRemove;
+impl Command for WatchlistRemove {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Given up, have you ?".to_string()),
+ usage: Some("<user_resolvable>".to_string()),
+ example: Some("@fun".to_string()),
+ aliases: vec!["del", "rm"].iter().map(|e| e.to_string()).collect(),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ match parse_user(args.single::<String>().unwrap_or(String::new()), guild_id) {
+ Some((user_id, member)) => {
+ let mut user_data = db.get_user(user_id.0 as i64, guild_id.0 as i64)?;
+ user_data.watchlist = false;
+ db.update_user(user_id.0 as i64, guild_id.0 as i64, user_data)?;
+ message.channel_id.say(format!("Unset {} from watchlist status.", member.display_name().into_owned()))?;
+ },
+ None => { message.channel_id.say("I couldn't find that user")?; }
+ }
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
+
+pub struct WatchlistList;
+impl Command for WatchlistList {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ desc: Some("Yeah, I'll tell you who's on the watchlist.".to_string()),
+ required_permissions: Permissions::MANAGE_MESSAGES,
+ aliases: vec!["ls"].iter().map(|e| e.to_string()).collect(),
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ if let Some(guild_id) = message.guild_id {
+ let users = db.get_users(guild_id.0 as i64)?;
+ let user_map = users.iter()
+ .filter(|e| e.watchlist)
+ .map(|u| {
+ match UserId(u.id as u64).to_user() {
+ Ok(user) => user.tag(),
+ Err(_) => format!("<#{}>", u.id),
+ }
+ })
+ .collect::<Vec<String>>()
+ .join("\n");
+ message.channel_id.send_message(|m| m
+ .embed(|e| e
+ .title("Watchlist")
+ .description(user_map)
+ .colour(*colours::MAIN)
+ ))?;
+ } else { failed!(GUILDID_FAIL); }
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/owners/log.rs b/src/modules/commands/owners/log.rs
new file mode 100644
index 0000000..67a8156
--- /dev/null
+++ b/src/modules/commands/owners/log.rs
@@ -0,0 +1,27 @@
+use std::path::Path;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct Log;
+impl Command for Log {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ owners_only: true,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, _: Args) -> Result<(), CommandError> {
+ message.channel_id.send_files(vec![Path::new("./output.log")], |m| m)?;
+ Ok(())
+ }
+}
diff --git a/src/modules/commands/owners/mod.rs b/src/modules/commands/owners/mod.rs
new file mode 100644
index 0000000..55105c3
--- /dev/null
+++ b/src/modules/commands/owners/mod.rs
@@ -0,0 +1,14 @@
+pub mod log;
+pub mod premium;
+
+use self::log::*;
+use self::premium::*;
+use serenity::framework::standard::CreateGroup;
+
+pub fn init() -> CreateGroup {
+ CreateGroup::default()
+ .help_available(false)
+ .cmd("log", Log)
+ .cmd("premium", Premium) // op
+ .cmd("say", Say)
+}
diff --git a/src/modules/commands/owners/premium.rs b/src/modules/commands/owners/premium.rs
new file mode 100644
index 0000000..4719faa
--- /dev/null
+++ b/src/modules/commands/owners/premium.rs
@@ -0,0 +1,76 @@
+use crate::core::consts::DB as db;
+use crate::core::utils::parse_guild;
+use serenity::framework::standard::{
+ Args,
+ Command,
+ CommandError,
+ CommandOptions
+};
+use serenity::model::channel::Message;
+use serenity::prelude::Context;
+use std::sync::Arc;
+
+pub struct Say;
+impl Command for Say {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ owners_only: true,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, args: Args) -> Result<(), CommandError> {
+ message.delete()?;
+ message.channel_id.say(args.to_string())?;
+
+ Ok(())
+ }
+}
+
+pub struct Premium;
+impl Command for Premium {
+ fn options(&self) -> Arc<CommandOptions> {
+ let default = CommandOptions::default();
+ let options = CommandOptions {
+ owners_only: true,
+ ..default
+ };
+ Arc::new(options)
+ }
+
+ fn execute(&self, _: &mut Context, message: &Message, mut args: Args) -> Result<(), CommandError> {
+ let op = args.single::<String>()?;
+ let g = args.single_quoted::<String>()?;
+ if let Some((guild_id, guild_lock)) = parse_guild(g) {
+ let guild = guild_lock.read();
+ match op.to_lowercase().as_str() {
+ "enable" => {
+ if let Ok(_) = db.new_premium(guild_id.0 as i64) {
+ message.channel_id.say(format!("{} is now premium !", guild.name))?;
+ }
+ },
+ "disable" => {
+ if let Ok(_) = db.del_premium(guild_id.0 as i64) {
+ message.channel_id.say(format!("{} is no longer premium...", guild.name))?;
+ }
+ },
+ "set" => {
+ let mut prem = db.get_premium(guild_id.0 as i64)?;
+ prem.tier = args.single::<i32>()?;
+ let pr = db.update_premium(guild_id.0 as i64, prem)?;
+ message.channel_id.say(format!("Updated premium tier for {} to {}.", guild.name, pr.tier))?;
+ },
+ "show" => {
+ if let Ok(prem) = db.get_premium(guild_id.0 as i64) { // mut
+ // TODO: add impl Display for PremiumSettings
+ message.channel_id.say(format!("{:?}", prem))?;
+ }
+ },
+ _ => {},
+ }
+ }
+ Ok(())
+ }
+}