moved code around. made help menu work nice
This commit is contained in:
parent
87301e4212
commit
14ef6385a0
@ -7,7 +7,7 @@ use syn::{
|
|||||||
Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility,
|
Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::util::{self, Argument, AsOption, Parenthesised};
|
use crate::util::{self, Argument, Parenthesised};
|
||||||
|
|
||||||
fn parse_argument(arg: FnArg) -> Result<Argument> {
|
fn parse_argument(arg: FnArg) -> Result<Argument> {
|
||||||
match arg {
|
match arg {
|
||||||
@ -290,7 +290,7 @@ impl Default for Arg {
|
|||||||
pub(crate) struct Options {
|
pub(crate) struct Options {
|
||||||
pub aliases: Vec<String>,
|
pub aliases: Vec<String>,
|
||||||
pub description: String,
|
pub description: String,
|
||||||
pub group: AsOption<String>,
|
pub group: String,
|
||||||
pub examples: Vec<String>,
|
pub examples: Vec<String>,
|
||||||
pub required_permissions: PermissionLevel,
|
pub required_permissions: PermissionLevel,
|
||||||
pub allow_slash: bool,
|
pub allow_slash: bool,
|
||||||
@ -302,6 +302,7 @@ impl Options {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
allow_slash: true,
|
allow_slash: true,
|
||||||
|
group: "Other".to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
198
src/cmds/info.rs
Normal file
198
src/cmds/info.rs
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
use regex_command_attr::command;
|
||||||
|
|
||||||
|
use serenity::{client::Context, framework::standard::CommandResult};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
framework::{Args, CommandInvoke, CreateGenericResponse, RegexFramework},
|
||||||
|
THEME_COLOR,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[group("Information")]
|
||||||
|
#[description("Get information on the commands of the bot")]
|
||||||
|
#[arg(
|
||||||
|
name = "command",
|
||||||
|
description = "Get help for a specific command",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[example("`/help` - see all commands")]
|
||||||
|
#[example("`/help play` - get help about the `play` command")]
|
||||||
|
pub async fn help(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
fn get_groups(framework: Arc<RegexFramework>) -> HashMap<&'static str, Vec<&'static str>> {
|
||||||
|
let mut groups = HashMap::new();
|
||||||
|
|
||||||
|
for command in &framework.commands_ {
|
||||||
|
let entry = groups.entry(command.group).or_insert(vec![]);
|
||||||
|
|
||||||
|
entry.push(command.names[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
groups
|
||||||
|
}
|
||||||
|
|
||||||
|
let framework = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<RegexFramework>()
|
||||||
|
.cloned()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if let Some(command_name) = args.named("command") {
|
||||||
|
if let Some(command) = framework.commands.get(command_name) {
|
||||||
|
let examples = if command.examples.is_empty() {
|
||||||
|
"".to_string()
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"**Examples**
|
||||||
|
{}",
|
||||||
|
command
|
||||||
|
.examples
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!(" • {}", e))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().embed(|e| {
|
||||||
|
e.title(format!("{} Help", command_name))
|
||||||
|
.color(THEME_COLOR)
|
||||||
|
.description(format!(
|
||||||
|
"**Aliases**
|
||||||
|
{}
|
||||||
|
|
||||||
|
**Overview**
|
||||||
|
• {}
|
||||||
|
**Arguments**
|
||||||
|
{}
|
||||||
|
|
||||||
|
{}",
|
||||||
|
command
|
||||||
|
.names
|
||||||
|
.iter()
|
||||||
|
.map(|n| format!("`{}`", n))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" "),
|
||||||
|
command.desc,
|
||||||
|
command
|
||||||
|
.args
|
||||||
|
.iter()
|
||||||
|
.map(|a| format!(
|
||||||
|
" • `{}` {} - {}",
|
||||||
|
a.name,
|
||||||
|
if a.required { "" } else { "[optional]" },
|
||||||
|
a.description
|
||||||
|
))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("\n"),
|
||||||
|
examples
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let groups = get_groups(framework);
|
||||||
|
let groups_iter = groups.iter().map(|(name, commands)| {
|
||||||
|
(
|
||||||
|
name,
|
||||||
|
commands
|
||||||
|
.iter()
|
||||||
|
.map(|c| format!("`{}`", c))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" "),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().embed(|e| {
|
||||||
|
e.title("Invalid Command")
|
||||||
|
.color(THEME_COLOR)
|
||||||
|
.description("Type `/help command` to view help about a command below:")
|
||||||
|
.fields(groups_iter)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let groups = get_groups(framework);
|
||||||
|
let groups_iter = groups.iter().map(|(name, commands)| {
|
||||||
|
(
|
||||||
|
name,
|
||||||
|
commands
|
||||||
|
.iter()
|
||||||
|
.map(|c| format!("`{}`", c))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(" "),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().embed(|e| {
|
||||||
|
e.title("Help")
|
||||||
|
.color(THEME_COLOR)
|
||||||
|
.description("Type `/help command` to view help about a command below:")
|
||||||
|
.fields(groups_iter)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[group("Information")]
|
||||||
|
#[aliases("invite")]
|
||||||
|
#[description("Get additional information on the bot")]
|
||||||
|
async fn info(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
_args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let current_user = ctx.cache.current_user().await;
|
||||||
|
|
||||||
|
invoke.respond(ctx.http.clone(), CreateGenericResponse::new()
|
||||||
|
.embed(|e| e
|
||||||
|
.title("Info")
|
||||||
|
.color(THEME_COLOR)
|
||||||
|
.footer(|f| f
|
||||||
|
.text(concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION"))))
|
||||||
|
.description(format!("Default prefix: `?`
|
||||||
|
|
||||||
|
Reset prefix: `@{0} prefix ?`
|
||||||
|
|
||||||
|
Invite me: https://discord.com/api/oauth2/authorize?client_id={1}&permissions=3165184&scope=applications.commands%20bot
|
||||||
|
|
||||||
|
**Welcome to SoundFX!**
|
||||||
|
Developer: <@203532103185465344>
|
||||||
|
Find me on https://discord.jellywx.com/ and on https://github.com/JellyWX :)
|
||||||
|
|
||||||
|
**Sound Credits**
|
||||||
|
\"The rain falls against the parasol\" https://freesound.org/people/straget/
|
||||||
|
\"Heavy Rain\" https://freesound.org/people/lebaston100/
|
||||||
|
\"Rain on Windows, Interior, A\" https://freesound.org/people/InspectorJ/
|
||||||
|
\"Seaside Waves, Close, A\" https://freesound.org/people/InspectorJ/
|
||||||
|
\"Small River 1 - Fast - Close\" https://freesound.org/people/Pfannkuchn/
|
||||||
|
|
||||||
|
**An online dashboard is available!** Visit https://soundfx.jellywx.com/dashboard
|
||||||
|
There is a maximum sound limit per user. This can be removed by subscribing at **https://patreon.com/jellywx**", current_user.name, current_user.id.as_u64())))).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
343
src/cmds/manage.rs
Normal file
343
src/cmds/manage.rs
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
use regex_command_attr::command;
|
||||||
|
|
||||||
|
use serenity::{
|
||||||
|
client::Context,
|
||||||
|
framework::standard::CommandResult,
|
||||||
|
model::id::{GuildId, RoleId},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
framework::{Args, CommandInvoke, CreateGenericResponse},
|
||||||
|
sound::Sound,
|
||||||
|
MySQL, MAX_SOUNDS, PATREON_GUILD, PATREON_ROLE,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[command("upload")]
|
||||||
|
#[group("Manage")]
|
||||||
|
#[description("Upload a new sound to the bot")]
|
||||||
|
#[arg(
|
||||||
|
name = "name",
|
||||||
|
description = "Name to upload sound to",
|
||||||
|
kind = "String",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
pub async fn upload_new_sound(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
fn is_numeric(s: &String) -> bool {
|
||||||
|
for char in s.chars() {
|
||||||
|
if char.is_digit(10) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_name = args
|
||||||
|
.named("name")
|
||||||
|
.map(|n| n.to_string())
|
||||||
|
.unwrap_or(String::new());
|
||||||
|
|
||||||
|
if !new_name.is_empty() && new_name.len() <= 20 {
|
||||||
|
if !is_numeric(&new_name) {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
// need to check the name is not currently in use by the user
|
||||||
|
let count_name =
|
||||||
|
Sound::count_named_user_sounds(invoke.author_id().0, &new_name, pool.clone())
|
||||||
|
.await?;
|
||||||
|
if count_name > 0 {
|
||||||
|
invoke.respond(ctx.http.clone(), CreateGenericResponse::new().content("You are already using that name. Please choose a unique name for your upload.")).await?;
|
||||||
|
} else {
|
||||||
|
// need to check how many sounds user currently has
|
||||||
|
let count = Sound::count_user_sounds(invoke.author_id().0, pool.clone()).await?;
|
||||||
|
let mut permit_upload = true;
|
||||||
|
|
||||||
|
// need to check if user is patreon or nah
|
||||||
|
if count >= *MAX_SOUNDS {
|
||||||
|
let patreon_guild_member = GuildId(*PATREON_GUILD)
|
||||||
|
.member(ctx, invoke.author_id())
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(member) = patreon_guild_member {
|
||||||
|
permit_upload = member.roles.contains(&RoleId(*PATREON_ROLE));
|
||||||
|
} else {
|
||||||
|
permit_upload = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if permit_upload {
|
||||||
|
let attachment = if let Some(attachment) = invoke
|
||||||
|
.msg()
|
||||||
|
.map(|m| m.attachments.get(0).map(|a| a.url.clone()))
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
Some(attachment)
|
||||||
|
} else {
|
||||||
|
invoke.respond(ctx.http.clone(), CreateGenericResponse::new().content("Please now upload an audio file under 1MB in size (larger files will be automatically trimmed):")).await?;
|
||||||
|
|
||||||
|
let reply = invoke
|
||||||
|
.channel_id()
|
||||||
|
.await_reply(&ctx)
|
||||||
|
.author_id(invoke.author_id())
|
||||||
|
.timeout(Duration::from_secs(120))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Some(reply_msg) => {
|
||||||
|
if let Some(attachment) = reply_msg.attachments.get(0) {
|
||||||
|
Some(attachment.url.clone())
|
||||||
|
} else {
|
||||||
|
invoke.followup(ctx.http.clone(), CreateGenericResponse::new().content("Please upload 1 attachment following your upload command. Aborted")).await?;
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
invoke
|
||||||
|
.followup(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Upload timed out. Please redo the command"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(url) = attachment {
|
||||||
|
match Sound::create_anon(
|
||||||
|
&new_name,
|
||||||
|
url.as_str(),
|
||||||
|
invoke.guild_id().unwrap().0,
|
||||||
|
invoke.author_id().0,
|
||||||
|
pool,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {
|
||||||
|
invoke
|
||||||
|
.followup(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Sound has been uploaded"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error occurred during upload: {:?}", e);
|
||||||
|
invoke
|
||||||
|
.followup(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Sound failed to upload."),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invoke.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(format!(
|
||||||
|
"You have reached the maximum number of sounds ({}). Either delete some with `?delete` or join our Patreon for unlimited uploads at **https://patreon.com/jellywx**",
|
||||||
|
*MAX_SOUNDS,
|
||||||
|
))).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Please ensure the sound name contains a non-numerical character"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invoke.respond(ctx.http.clone(), CreateGenericResponse::new().content("Usage: `?upload <name>`. Please ensure the name provided is less than 20 characters in length")).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("delete")]
|
||||||
|
#[group("Manage")]
|
||||||
|
#[description("Delete a sound you have uploaded")]
|
||||||
|
#[arg(
|
||||||
|
name = "query",
|
||||||
|
description = "Delete sound with the specified name or ID",
|
||||||
|
kind = "String",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
pub async fn delete_sound(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let uid = invoke.author_id().0;
|
||||||
|
let gid = invoke.guild_id().unwrap().0;
|
||||||
|
|
||||||
|
let name = args
|
||||||
|
.named("query")
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.unwrap_or(String::new());
|
||||||
|
|
||||||
|
let sound_vec = Sound::search_for_sound(&name, gid, uid, pool.clone(), true).await?;
|
||||||
|
let sound_result = sound_vec.first();
|
||||||
|
|
||||||
|
match sound_result {
|
||||||
|
Some(sound) => {
|
||||||
|
if sound.uploader_id != Some(uid) && sound.server_id != gid {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(
|
||||||
|
"You can only delete sounds from this guild or that you have uploaded.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let has_perms = {
|
||||||
|
if let Ok(member) = invoke.member(&ctx).await {
|
||||||
|
if let Ok(perms) = member.permissions(&ctx).await {
|
||||||
|
perms.manage_guild()
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if sound.uploader_id == Some(uid) || has_perms {
|
||||||
|
sound.delete(pool).await?;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content("Sound has been deleted"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(
|
||||||
|
"Only server admins can delete sounds uploaded by other users.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content("Sound could not be found by that name."),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("public")]
|
||||||
|
#[group("Manage")]
|
||||||
|
#[description("Change a sound between public and private")]
|
||||||
|
#[arg(
|
||||||
|
name = "query",
|
||||||
|
kind = "String",
|
||||||
|
description = "Sound name or ID to change the privacy setting of",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
pub async fn change_public(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let uid = invoke.author_id().as_u64().to_owned();
|
||||||
|
|
||||||
|
let name = args.named("query").unwrap();
|
||||||
|
let gid = *invoke.guild_id().unwrap().as_u64();
|
||||||
|
|
||||||
|
let mut sound_vec = Sound::search_for_sound(name, gid, uid, pool.clone(), true).await?;
|
||||||
|
let sound_result = sound_vec.first_mut();
|
||||||
|
|
||||||
|
match sound_result {
|
||||||
|
Some(sound) => {
|
||||||
|
if sound.uploader_id != Some(uid) {
|
||||||
|
invoke.respond(ctx.http.clone(), CreateGenericResponse::new().content("You can only change the visibility of sounds you have uploaded. Use `?list me` to view your sounds")).await?;
|
||||||
|
} else {
|
||||||
|
if sound.public {
|
||||||
|
sound.public = false;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Sound has been set to private 🔒"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
sound.public = true;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content("Sound has been set to public 🔓"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
sound.commit(pool).await?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content("Sound could not be found by that name."),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
6
src/cmds/mod.rs
Normal file
6
src/cmds/mod.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
pub mod info;
|
||||||
|
pub mod manage;
|
||||||
|
pub mod play;
|
||||||
|
pub mod search;
|
||||||
|
pub mod settings;
|
||||||
|
pub mod stop;
|
405
src/cmds/play.rs
Normal file
405
src/cmds/play.rs
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
use regex_command_attr::command;
|
||||||
|
|
||||||
|
use serenity::{
|
||||||
|
builder::CreateActionRow,
|
||||||
|
client::Context,
|
||||||
|
framework::standard::CommandResult,
|
||||||
|
model::interactions::{ButtonStyle, InteractionResponseType},
|
||||||
|
};
|
||||||
|
|
||||||
|
use songbird::{
|
||||||
|
create_player, ffmpeg,
|
||||||
|
input::{cached::Memory, Input},
|
||||||
|
Event,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
event_handlers::RestartTrack,
|
||||||
|
framework::{Args, CommandInvoke, CreateGenericResponse},
|
||||||
|
guild_data::CtxGuildData,
|
||||||
|
join_channel, play_cmd,
|
||||||
|
sound::Sound,
|
||||||
|
AudioIndex, MySQL,
|
||||||
|
};
|
||||||
|
|
||||||
|
use std::{convert::TryFrom, time::Duration};
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[aliases("p")]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
#[group("Play")]
|
||||||
|
#[description("Play a sound in your current voice channel")]
|
||||||
|
#[arg(
|
||||||
|
name = "query",
|
||||||
|
description = "Play sound with the specified name or ID",
|
||||||
|
kind = "String",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
#[example("`/play ubercharge` - play sound with name \"ubercharge\" ")]
|
||||||
|
#[example("`/play 13002` - play sound with ID 13002")]
|
||||||
|
pub async fn play(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let guild = invoke.guild(ctx.cache.clone()).await.unwrap();
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content(play_cmd(ctx, guild, invoke.author_id(), args, false).await),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("loop")]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
#[group("Play")]
|
||||||
|
#[description("Play a sound on loop in your current voice channel")]
|
||||||
|
#[arg(
|
||||||
|
name = "query",
|
||||||
|
description = "Play sound with the specified name or ID",
|
||||||
|
kind = "String",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
#[example("`/loop rain` - loop sound with name \"rain\" ")]
|
||||||
|
#[example("`/loop 13002` - play sound with ID 13002")]
|
||||||
|
pub async fn loop_play(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let guild = invoke.guild(ctx.cache.clone()).await.unwrap();
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content(play_cmd(ctx, guild, invoke.author_id(), args, true).await),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("ambience")]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
#[group("Play")]
|
||||||
|
#[description("Play ambient sound in your current voice channel")]
|
||||||
|
#[arg(
|
||||||
|
name = "name",
|
||||||
|
description = "Play sound with the specified name",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[example("`/ambience rain on tent` - play the ambient sound \"rain on tent\" ")]
|
||||||
|
pub async fn play_ambience(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let guild = invoke.guild(ctx.cache.clone()).await.unwrap();
|
||||||
|
|
||||||
|
let channel_to_join = guild
|
||||||
|
.voice_states
|
||||||
|
.get(&invoke.author_id())
|
||||||
|
.and_then(|voice_state| voice_state.channel_id);
|
||||||
|
|
||||||
|
match channel_to_join {
|
||||||
|
Some(user_channel) => {
|
||||||
|
let search_name = args.named("name").unwrap().to_lowercase();
|
||||||
|
let audio_index = ctx.data.read().await.get::<AudioIndex>().cloned().unwrap();
|
||||||
|
|
||||||
|
if let Some(filename) = audio_index.get(&search_name) {
|
||||||
|
let (track, track_handler) = create_player(
|
||||||
|
Input::try_from(
|
||||||
|
Memory::new(ffmpeg(format!("audio/{}", filename)).await.unwrap()).unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (call_handler, _) = join_channel(ctx, guild.clone(), user_channel).await;
|
||||||
|
let guild_data = ctx.guild_data(guild).await.unwrap();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut lock = call_handler.lock().await;
|
||||||
|
|
||||||
|
lock.play(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = track_handler.set_volume(guild_data.read().await.volume as f32 / 100.0);
|
||||||
|
let _ = track_handler.add_event(
|
||||||
|
Event::Periodic(
|
||||||
|
track_handler.metadata().duration.unwrap() - Duration::from_millis(200),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
RestartTrack {},
|
||||||
|
);
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content(format!("Playing ambience **{}**", search_name)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().embed(|e| {
|
||||||
|
e.title("Not Found").description(format!(
|
||||||
|
"Could not find ambience sound by name **{}**
|
||||||
|
|
||||||
|
__Available ambience sounds:__
|
||||||
|
{}",
|
||||||
|
search_name,
|
||||||
|
audio_index
|
||||||
|
.keys()
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| i.as_str())
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join("\n")
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content("You are not in a voice chat!"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("soundboard")]
|
||||||
|
#[aliases("board")]
|
||||||
|
#[group("Play")]
|
||||||
|
#[description("Get a menu of sounds with buttons to play them")]
|
||||||
|
#[arg(
|
||||||
|
name = "1",
|
||||||
|
description = "Query for sound button 1",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "2",
|
||||||
|
description = "Query for sound button 2",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "3",
|
||||||
|
description = "Query for sound button 3",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "4",
|
||||||
|
description = "Query for sound button 4",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "5",
|
||||||
|
description = "Query for sound button 5",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "6",
|
||||||
|
description = "Query for sound button 6",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "7",
|
||||||
|
description = "Query for sound button 7",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "8",
|
||||||
|
description = "Query for sound button 8",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "9",
|
||||||
|
description = "Query for sound button 9",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "10",
|
||||||
|
description = "Query for sound button 10",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "11",
|
||||||
|
description = "Query for sound button 11",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "12",
|
||||||
|
description = "Query for sound button 12",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "13",
|
||||||
|
description = "Query for sound button 13",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "14",
|
||||||
|
description = "Query for sound button 14",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "15",
|
||||||
|
description = "Query for sound button 15",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "16",
|
||||||
|
description = "Query for sound button 16",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "17",
|
||||||
|
description = "Query for sound button 17",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "18",
|
||||||
|
description = "Query for sound button 18",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "19",
|
||||||
|
description = "Query for sound button 19",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "20",
|
||||||
|
description = "Query for sound button 20",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "21",
|
||||||
|
description = "Query for sound button 21",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "22",
|
||||||
|
description = "Query for sound button 22",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "23",
|
||||||
|
description = "Query for sound button 23",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "24",
|
||||||
|
description = "Query for sound button 24",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[arg(
|
||||||
|
name = "25",
|
||||||
|
description = "Query for sound button 25",
|
||||||
|
kind = "String",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
#[example("`/soundboard ubercharge` - create a soundboard with a button for the \"ubercharge\" sound effect")]
|
||||||
|
#[example("`/soundboard 57000 24119 2 1002 13202` - create a soundboard with 5 buttons, for sounds with the IDs presented")]
|
||||||
|
pub async fn soundboard(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
if let Some(interaction) = invoke.interaction() {
|
||||||
|
let _ = interaction
|
||||||
|
.create_interaction_response(&ctx, |r| {
|
||||||
|
r.kind(InteractionResponseType::DeferredChannelMessageWithSource)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let mut sounds = vec![];
|
||||||
|
|
||||||
|
for n in 1..25 {
|
||||||
|
let search = Sound::search_for_sound(
|
||||||
|
args.named(&n.to_string()).unwrap_or(&"".to_string()),
|
||||||
|
invoke.guild_id().unwrap(),
|
||||||
|
invoke.author_id(),
|
||||||
|
pool.clone(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(sound) = search.first() {
|
||||||
|
sounds.push(sound.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.followup(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("**Play a sound:**")
|
||||||
|
.components(|c| {
|
||||||
|
for row in sounds.as_slice().chunks(5) {
|
||||||
|
let mut action_row: CreateActionRow = Default::default();
|
||||||
|
for sound in row {
|
||||||
|
action_row.create_button(|b| {
|
||||||
|
b.style(ButtonStyle::Primary)
|
||||||
|
.label(&sound.name)
|
||||||
|
.custom_id(sound.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
c.add_action_row(action_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
c
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
214
src/cmds/search.rs
Normal file
214
src/cmds/search.rs
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
use regex_command_attr::command;
|
||||||
|
|
||||||
|
use serenity::{client::Context, framework::standard::CommandResult};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
framework::{Args, CommandInvoke, CreateGenericResponse},
|
||||||
|
sound::Sound,
|
||||||
|
MySQL,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn format_search_results(search_results: Vec<Sound>) -> CreateGenericResponse {
|
||||||
|
let mut current_character_count = 0;
|
||||||
|
let title = "Public sounds matching filter:";
|
||||||
|
|
||||||
|
let field_iter = search_results
|
||||||
|
.iter()
|
||||||
|
.take(25)
|
||||||
|
.map(|item| {
|
||||||
|
(
|
||||||
|
&item.name,
|
||||||
|
format!("ID: {}\nPlays: {}", item.id, item.plays),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(|item| {
|
||||||
|
current_character_count += item.0.len() + item.1.len();
|
||||||
|
|
||||||
|
current_character_count <= serenity::constants::MESSAGE_CODE_LIMIT - title.len()
|
||||||
|
});
|
||||||
|
|
||||||
|
CreateGenericResponse::new().embed(|e| e.title(title).fields(field_iter))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("list")]
|
||||||
|
#[group("Search")]
|
||||||
|
#[description("Show the sounds uploaded by you or to your server")]
|
||||||
|
#[arg(
|
||||||
|
name = "me",
|
||||||
|
description = "Whether to list your sounds or server sounds (default: server)",
|
||||||
|
kind = "Boolean",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
pub async fn list_sounds(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let sounds;
|
||||||
|
let mut message_buffer;
|
||||||
|
|
||||||
|
if args.named("me").map(|i| i.to_owned()) == Some("me".to_string()) {
|
||||||
|
sounds = Sound::get_user_sounds(invoke.author_id(), pool).await?;
|
||||||
|
|
||||||
|
message_buffer = "All your sounds: ".to_string();
|
||||||
|
} else {
|
||||||
|
sounds = Sound::get_guild_sounds(invoke.guild_id().unwrap(), pool).await?;
|
||||||
|
|
||||||
|
message_buffer = "All sounds on this server: ".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
for sound in sounds {
|
||||||
|
message_buffer.push_str(
|
||||||
|
format!(
|
||||||
|
"**{}** ({}), ",
|
||||||
|
sound.name,
|
||||||
|
if sound.public { "🔓" } else { "🔒" }
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if message_buffer.len() > 2000 {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(message_buffer),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
message_buffer = "".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if message_buffer.len() > 0 {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(message_buffer),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("search")]
|
||||||
|
#[group("Search")]
|
||||||
|
#[description("Search for sounds")]
|
||||||
|
#[arg(
|
||||||
|
name = "query",
|
||||||
|
kind = "String",
|
||||||
|
description = "Sound name to search for",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
pub async fn search_sounds(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let query = args.named("query").unwrap();
|
||||||
|
|
||||||
|
let search_results = Sound::search_for_sound(
|
||||||
|
query,
|
||||||
|
invoke.guild_id().unwrap(),
|
||||||
|
invoke.author_id(),
|
||||||
|
pool,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(ctx.http.clone(), format_search_results(search_results))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("popular")]
|
||||||
|
#[group("Search")]
|
||||||
|
#[description("Show popular sounds")]
|
||||||
|
pub async fn show_popular_sounds(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
_args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let search_results = sqlx::query_as_unchecked!(
|
||||||
|
Sound,
|
||||||
|
"
|
||||||
|
SELECT name, id, plays, public, server_id, uploader_id
|
||||||
|
FROM sounds
|
||||||
|
WHERE public = 1
|
||||||
|
ORDER BY plays DESC
|
||||||
|
LIMIT 25
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(ctx.http.clone(), format_search_results(search_results))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("random")]
|
||||||
|
#[group("Search")]
|
||||||
|
#[description("Show a page of random sounds")]
|
||||||
|
pub async fn show_random_sounds(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
_args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let search_results = sqlx::query_as_unchecked!(
|
||||||
|
Sound,
|
||||||
|
"
|
||||||
|
SELECT name, id, plays, public, server_id, uploader_id
|
||||||
|
FROM sounds
|
||||||
|
WHERE public = 1
|
||||||
|
ORDER BY rand()
|
||||||
|
LIMIT 25
|
||||||
|
"
|
||||||
|
)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(ctx.http.clone(), format_search_results(search_results))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
351
src/cmds/settings.rs
Normal file
351
src/cmds/settings.rs
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
use regex_command_attr::command;
|
||||||
|
|
||||||
|
use serenity::{client::Context, framework::standard::CommandResult};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
framework::{Args, CommandInvoke, CreateGenericResponse},
|
||||||
|
guild_data::CtxGuildData,
|
||||||
|
sound::{JoinSoundCtx, Sound},
|
||||||
|
MySQL,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[command("volume")]
|
||||||
|
#[aliases("vol")]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
#[group("Settings")]
|
||||||
|
#[description("Change the bot's volume in this server")]
|
||||||
|
#[arg(
|
||||||
|
name = "volume",
|
||||||
|
description = "New volume for the bot to use",
|
||||||
|
kind = "Integer",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
pub async fn change_volume(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let guild_data_opt = ctx.guild_data(invoke.guild_id().unwrap()).await;
|
||||||
|
let guild_data = guild_data_opt.unwrap();
|
||||||
|
|
||||||
|
if let Some(volume) = args.named("volume").map(|i| i.parse::<u8>().ok()).flatten() {
|
||||||
|
guild_data.write().await.volume = volume;
|
||||||
|
|
||||||
|
guild_data.read().await.commit(pool).await?;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(format!("Volume changed to {}%", volume)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let read = guild_data.read().await;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(format!(
|
||||||
|
"Current server volume: {vol}%. Change the volume with `/volume <new volume>`",
|
||||||
|
vol = read.volume
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("prefix")]
|
||||||
|
#[required_permissions(Restricted)]
|
||||||
|
#[allow_slash(false)]
|
||||||
|
#[group("Settings")]
|
||||||
|
#[description("Change the prefix of the bot for using non-slash commands")]
|
||||||
|
#[arg(
|
||||||
|
name = "prefix",
|
||||||
|
kind = "String",
|
||||||
|
description = "The new prefix to use for the bot",
|
||||||
|
required = true
|
||||||
|
)]
|
||||||
|
pub async fn change_prefix(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let guild_data;
|
||||||
|
|
||||||
|
{
|
||||||
|
let guild_data_opt = ctx.guild_data(invoke.guild_id().unwrap()).await;
|
||||||
|
|
||||||
|
guild_data = guild_data_opt.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(prefix) = args.named("prefix") {
|
||||||
|
if prefix.len() <= 5 {
|
||||||
|
let reply = format!("Prefix changed to `{}`", prefix);
|
||||||
|
|
||||||
|
{
|
||||||
|
guild_data.write().await.prefix = prefix.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let read = guild_data.read().await;
|
||||||
|
|
||||||
|
read.commit(pool).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(reply),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Prefix must be less than 5 characters long"),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(format!(
|
||||||
|
"Usage: `{prefix}prefix <new prefix>`",
|
||||||
|
prefix = guild_data.read().await.prefix
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("roles")]
|
||||||
|
#[required_permissions(Restricted)]
|
||||||
|
#[allow_slash(false)]
|
||||||
|
#[group("Settings")]
|
||||||
|
#[description("Change the roles allowed to use the bot")]
|
||||||
|
pub async fn set_allowed_roles(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let msg = invoke.msg().unwrap();
|
||||||
|
let guild_id = *msg.guild_id.unwrap().as_u64();
|
||||||
|
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
if args.is_empty() {
|
||||||
|
let roles = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT role
|
||||||
|
FROM roles
|
||||||
|
WHERE guild_id = ?
|
||||||
|
",
|
||||||
|
guild_id
|
||||||
|
)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let all_roles = roles
|
||||||
|
.iter()
|
||||||
|
.map(|i| format!("<@&{}>", i.role.to_string()))
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
msg.channel_id.say(&ctx, format!("Usage: `?roles <role mentions or anything else to disable>`. Current roles: {}", all_roles)).await?;
|
||||||
|
} else {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
DELETE FROM roles
|
||||||
|
WHERE guild_id = ?
|
||||||
|
",
|
||||||
|
guild_id
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if msg.mention_roles.len() > 0 {
|
||||||
|
for role in msg.mention_roles.iter().map(|r| *r.as_u64()) {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT INTO roles (guild_id, role)
|
||||||
|
VALUES
|
||||||
|
(?, ?)
|
||||||
|
",
|
||||||
|
guild_id,
|
||||||
|
role
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.channel_id
|
||||||
|
.say(&ctx, "Specified roles whitelisted")
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT INTO roles (guild_id, role)
|
||||||
|
VALUES
|
||||||
|
(?, ?)
|
||||||
|
",
|
||||||
|
guild_id,
|
||||||
|
guild_id
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
msg.channel_id
|
||||||
|
.say(&ctx, "Role whitelisting disabled")
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("greet")]
|
||||||
|
#[group("Settings")]
|
||||||
|
#[description("Set a join sound")]
|
||||||
|
#[arg(
|
||||||
|
name = "query",
|
||||||
|
kind = "String",
|
||||||
|
description = "Name or ID of sound to set as your greet sound",
|
||||||
|
required = false
|
||||||
|
)]
|
||||||
|
pub async fn set_greet_sound(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not get SQLPool from data");
|
||||||
|
|
||||||
|
let query = args
|
||||||
|
.named("query")
|
||||||
|
.map(|s| s.to_owned())
|
||||||
|
.unwrap_or(String::new());
|
||||||
|
let user_id = invoke.author_id();
|
||||||
|
|
||||||
|
if query.len() == 0 {
|
||||||
|
ctx.update_join_sound(user_id, None).await;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content("Your greet sound has been unset."),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
let sound_vec = Sound::search_for_sound(
|
||||||
|
&query,
|
||||||
|
invoke.guild_id().unwrap(),
|
||||||
|
user_id,
|
||||||
|
pool.clone(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match sound_vec.first() {
|
||||||
|
Some(sound) => {
|
||||||
|
ctx.update_join_sound(user_id, Some(sound.id)).await;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(format!(
|
||||||
|
"Greet sound has been set to {} (ID {})",
|
||||||
|
sound.name, sound.id
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new()
|
||||||
|
.content("Could not find a sound by that name."),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command("allow_greet")]
|
||||||
|
#[group("Settings")]
|
||||||
|
#[description("Configure whether users should be able to use join sounds")]
|
||||||
|
#[required_permissions(Restricted)]
|
||||||
|
pub async fn allow_greet_sounds(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
_args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let pool = ctx
|
||||||
|
.data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.get::<MySQL>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Could not acquire SQL pool from data");
|
||||||
|
|
||||||
|
let guild_data_opt = ctx.guild_data(invoke.guild_id().unwrap()).await;
|
||||||
|
|
||||||
|
if let Ok(guild_data) = guild_data_opt {
|
||||||
|
let current = guild_data.read().await.allow_greets;
|
||||||
|
|
||||||
|
{
|
||||||
|
guild_data.write().await.allow_greets = !current;
|
||||||
|
}
|
||||||
|
|
||||||
|
guild_data.read().await.commit(pool).await?;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(
|
||||||
|
ctx.http.clone(),
|
||||||
|
CreateGenericResponse::new().content(format!(
|
||||||
|
"Greet sounds have been {}abled in this server",
|
||||||
|
if !current { "en" } else { "dis" }
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
56
src/cmds/stop.rs
Normal file
56
src/cmds/stop.rs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
use regex_command_attr::command;
|
||||||
|
|
||||||
|
use serenity::{client::Context, framework::standard::CommandResult};
|
||||||
|
|
||||||
|
use crate::framework::{Args, CommandInvoke, CreateGenericResponse};
|
||||||
|
|
||||||
|
use songbird;
|
||||||
|
|
||||||
|
#[command("stop")]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
#[group("Stop")]
|
||||||
|
#[description("Stop the bot from playing")]
|
||||||
|
pub async fn stop_playing(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
_args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let guild_id = invoke.guild_id().unwrap();
|
||||||
|
|
||||||
|
let songbird = songbird::get(ctx).await.unwrap();
|
||||||
|
let call_opt = songbird.get(guild_id);
|
||||||
|
|
||||||
|
if let Some(call) = call_opt {
|
||||||
|
let mut lock = call.lock().await;
|
||||||
|
|
||||||
|
lock.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(ctx.http.clone(), CreateGenericResponse::new().content("👍"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
#[aliases("dc")]
|
||||||
|
#[required_permissions(Managed)]
|
||||||
|
#[group("Stop")]
|
||||||
|
#[description("Disconnect the bot")]
|
||||||
|
pub async fn disconnect(
|
||||||
|
ctx: &Context,
|
||||||
|
invoke: &(dyn CommandInvoke + Sync + Send),
|
||||||
|
_args: Args,
|
||||||
|
) -> CommandResult {
|
||||||
|
let guild_id = invoke.guild_id().unwrap();
|
||||||
|
|
||||||
|
let songbird = songbird::get(ctx).await.unwrap();
|
||||||
|
let _ = songbird.leave(guild_id).await;
|
||||||
|
|
||||||
|
invoke
|
||||||
|
.respond(ctx.http.clone(), CreateGenericResponse::new().content("👍"))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -70,10 +70,6 @@ impl Args {
|
|||||||
Self { args }
|
Self { args }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.args.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.args.is_empty()
|
self.args.is_empty()
|
||||||
}
|
}
|
||||||
@ -345,7 +341,7 @@ pub struct Command {
|
|||||||
|
|
||||||
pub desc: &'static str,
|
pub desc: &'static str,
|
||||||
pub examples: &'static [&'static str],
|
pub examples: &'static [&'static str],
|
||||||
pub group: Option<&'static str>,
|
pub group: &'static str,
|
||||||
|
|
||||||
pub allow_slash: bool,
|
pub allow_slash: bool,
|
||||||
pub required_permissions: PermissionLevel,
|
pub required_permissions: PermissionLevel,
|
||||||
@ -440,7 +436,7 @@ impl fmt::Debug for Command {
|
|||||||
|
|
||||||
pub struct RegexFramework {
|
pub struct RegexFramework {
|
||||||
pub commands: HashMap<String, &'static Command>,
|
pub commands: HashMap<String, &'static Command>,
|
||||||
commands_: HashSet<&'static Command>,
|
pub commands_: HashSet<&'static Command>,
|
||||||
command_matcher: Regex,
|
command_matcher: Regex,
|
||||||
default_prefix: String,
|
default_prefix: String,
|
||||||
client_id: u64,
|
client_id: u64,
|
||||||
|
1597
src/main.rs
1597
src/main.rs
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user