restructured a lot. everything updated to poise.
This commit is contained in:
parent
bb54c0d2c0
commit
b350007dae
@ -1,4 +1,4 @@
|
|||||||
use crate::{Context, Error, THEME_COLOR};
|
use crate::{consts::THEME_COLOR, Context, Error};
|
||||||
|
|
||||||
/// Get additional information about the bot
|
/// Get additional information about the bot
|
||||||
#[poise::command(slash_command, category = "Information")]
|
#[poise::command(slash_command, category = "Information")]
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use poise::serenity::model::id::{GuildId, RoleId};
|
use poise::serenity::model::id::{GuildId, RoleId};
|
||||||
|
use tokio::fs::File;
|
||||||
|
|
||||||
use crate::{sound::Sound, Context, Error, MAX_SOUNDS, PATREON_GUILD, PATREON_ROLE};
|
use crate::{
|
||||||
|
cmds::autocomplete_sound,
|
||||||
|
consts::{MAX_SOUNDS, PATREON_GUILD, PATREON_ROLE},
|
||||||
|
models::sound::{Sound, SoundCtx},
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
/// Upload a new sound to the bot
|
/// Upload a new sound to the bot
|
||||||
#[poise::command(slash_command, rename = "upload", category = "Manage")]
|
#[poise::command(slash_command, rename = "upload", category = "Manage")]
|
||||||
@ -123,14 +129,16 @@ pub async fn upload_new_sound(
|
|||||||
#[poise::command(slash_command, rename = "delete", category = "Manage")]
|
#[poise::command(slash_command, rename = "delete", category = "Manage")]
|
||||||
pub async fn delete_sound(
|
pub async fn delete_sound(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Name or ID of sound to delete"] name: String,
|
#[description = "Name or ID of sound to delete"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
name: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let pool = ctx.data().database.clone();
|
let pool = ctx.data().database.clone();
|
||||||
|
|
||||||
let uid = ctx.author().id.0;
|
let uid = ctx.author().id.0;
|
||||||
let gid = ctx.guild_id().unwrap().0;
|
let gid = ctx.guild_id().unwrap().0;
|
||||||
|
|
||||||
let sound_vec = Sound::search_for_sound(&name, gid, uid, pool.clone(), true).await?;
|
let sound_vec = ctx.data().search_for_sound(&name, gid, uid, true).await?;
|
||||||
let sound_result = sound_vec.first();
|
let sound_result = sound_vec.first();
|
||||||
|
|
||||||
match sound_result {
|
match sound_result {
|
||||||
@ -174,14 +182,16 @@ pub async fn delete_sound(
|
|||||||
#[poise::command(slash_command, rename = "public", category = "Manage")]
|
#[poise::command(slash_command, rename = "public", category = "Manage")]
|
||||||
pub async fn change_public(
|
pub async fn change_public(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Name or ID of sound to change privacy setting of"] name: String,
|
#[description = "Name or ID of sound to change privacy setting of"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
name: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let pool = ctx.data().database.clone();
|
let pool = ctx.data().database.clone();
|
||||||
|
|
||||||
let uid = ctx.author().id.0;
|
let uid = ctx.author().id.0;
|
||||||
let gid = ctx.guild_id().unwrap().0;
|
let gid = ctx.guild_id().unwrap().0;
|
||||||
|
|
||||||
let mut sound_vec = Sound::search_for_sound(&name, gid, uid, pool.clone(), true).await?;
|
let mut sound_vec = ctx.data().search_for_sound(&name, gid, uid, true).await?;
|
||||||
let sound_result = sound_vec.first_mut();
|
let sound_result = sound_vec.first_mut();
|
||||||
|
|
||||||
match sound_result {
|
match sound_result {
|
||||||
@ -210,3 +220,39 @@ pub async fn change_public(
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Download a sound file from the bot
|
||||||
|
#[poise::command(slash_command, rename = "download", category = "Manage")]
|
||||||
|
pub async fn download_file(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Name or ID of sound to download"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
name: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.defer().await?;
|
||||||
|
|
||||||
|
let sound = ctx
|
||||||
|
.data()
|
||||||
|
.search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match sound.first() {
|
||||||
|
Some(sound) => {
|
||||||
|
let source = sound
|
||||||
|
.store_sound_source(ctx.data().database.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let file = File::open(&source).await?;
|
||||||
|
let name = format!("{}-{}.opus", sound.id, sound.name);
|
||||||
|
|
||||||
|
ctx.send(|m| m.attachment((&file, name.as_str()).into()))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
ctx.say("No sound found by specified name/ID").await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
@ -1,6 +1,24 @@
|
|||||||
|
use crate::{models::sound::SoundCtx, Context};
|
||||||
|
|
||||||
pub mod info;
|
pub mod info;
|
||||||
pub mod manage;
|
pub mod manage;
|
||||||
pub mod play;
|
pub mod play;
|
||||||
// pub mod search;
|
pub mod search;
|
||||||
// pub mod settings;
|
pub mod settings;
|
||||||
// pub mod stop;
|
pub mod stop;
|
||||||
|
|
||||||
|
pub async fn autocomplete_sound(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
partial: String,
|
||||||
|
) -> Vec<poise::AutocompleteChoice<String>> {
|
||||||
|
ctx.data()
|
||||||
|
.autocomplete_user_sounds(&partial, ctx.author().id, ctx.guild_id().unwrap())
|
||||||
|
.await
|
||||||
|
.unwrap_or(vec![])
|
||||||
|
.iter()
|
||||||
|
.map(|s| poise::AutocompleteChoice {
|
||||||
|
name: s.name.clone(),
|
||||||
|
value: s.id.to_string(),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
150
src/cmds/play.rs
150
src/cmds/play.rs
@ -2,31 +2,57 @@ use poise::serenity::{
|
|||||||
builder::CreateActionRow, model::interactions::message_component::ButtonStyle,
|
builder::CreateActionRow, model::interactions::message_component::ButtonStyle,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{play_from_query, sound::Sound, Context, Error};
|
use crate::{
|
||||||
|
cmds::autocomplete_sound, models::sound::SoundCtx, utils::play_from_query, Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
/// Play a sound in your current voice channel
|
/// Play a sound in your current voice channel
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command)]
|
||||||
pub async fn play(
|
pub async fn play(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Name or ID of sound to play"] name: String,
|
#[description = "Name or ID of sound to play"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
name: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let guild = ctx.guild().unwrap();
|
let guild = ctx.guild().unwrap();
|
||||||
|
|
||||||
ctx.say(play_from_query(&ctx, guild, ctx.author().id, &name, false).await)
|
ctx.say(
|
||||||
|
play_from_query(
|
||||||
|
&ctx.discord(),
|
||||||
|
&ctx.data(),
|
||||||
|
guild,
|
||||||
|
ctx.author().id,
|
||||||
|
&name,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loop a sound in your current voice channel
|
/// Loop a sound in your current voice channel
|
||||||
#[poise::command(slash_command)]
|
#[poise::command(slash_command, rename = "loop")]
|
||||||
pub async fn loop_play(
|
pub async fn loop_play(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Name or ID of sound to loop"] name: String,
|
#[description = "Name or ID of sound to loop"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
name: String,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let guild = ctx.guild().unwrap();
|
let guild = ctx.guild().unwrap();
|
||||||
|
|
||||||
ctx.say(play_from_query(&ctx, guild, ctx.author().id, &name, true).await)
|
ctx.say(
|
||||||
|
play_from_query(
|
||||||
|
&ctx.discord(),
|
||||||
|
&ctx.data(),
|
||||||
|
guild,
|
||||||
|
ctx.author().id,
|
||||||
|
&name,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -36,36 +62,84 @@ pub async fn loop_play(
|
|||||||
#[poise::command(slash_command, rename = "soundboard", category = "Play")]
|
#[poise::command(slash_command, rename = "soundboard", category = "Play")]
|
||||||
pub async fn soundboard(
|
pub async fn soundboard(
|
||||||
ctx: Context<'_>,
|
ctx: Context<'_>,
|
||||||
#[description = "Name or ID of sound for button 1"] sound_1: String,
|
#[description = "Name or ID of sound for button 1"]
|
||||||
#[description = "Name or ID of sound for button 2"] sound_2: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 3"] sound_3: Option<String>,
|
sound_1: String,
|
||||||
#[description = "Name or ID of sound for button 4"] sound_4: Option<String>,
|
#[description = "Name or ID of sound for button 2"]
|
||||||
#[description = "Name or ID of sound for button 5"] sound_5: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 6"] sound_6: Option<String>,
|
sound_2: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 7"] sound_7: Option<String>,
|
#[description = "Name or ID of sound for button 3"]
|
||||||
#[description = "Name or ID of sound for button 8"] sound_8: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 9"] sound_9: Option<String>,
|
sound_3: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 10"] sound_10: Option<String>,
|
#[description = "Name or ID of sound for button 4"]
|
||||||
#[description = "Name or ID of sound for button 11"] sound_11: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 12"] sound_12: Option<String>,
|
sound_4: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 13"] sound_13: Option<String>,
|
#[description = "Name or ID of sound for button 5"]
|
||||||
#[description = "Name or ID of sound for button 14"] sound_14: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 15"] sound_15: Option<String>,
|
sound_5: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 16"] sound_16: Option<String>,
|
#[description = "Name or ID of sound for button 6"]
|
||||||
#[description = "Name or ID of sound for button 17"] sound_17: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 18"] sound_18: Option<String>,
|
sound_6: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 19"] sound_19: Option<String>,
|
#[description = "Name or ID of sound for button 7"]
|
||||||
#[description = "Name or ID of sound for button 20"] sound_20: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 21"] sound_21: Option<String>,
|
sound_7: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 22"] sound_22: Option<String>,
|
#[description = "Name or ID of sound for button 8"]
|
||||||
#[description = "Name or ID of sound for button 23"] sound_23: Option<String>,
|
#[autocomplete = "autocomplete_sound"]
|
||||||
#[description = "Name or ID of sound for button 24"] sound_24: Option<String>,
|
sound_8: Option<String>,
|
||||||
#[description = "Name or ID of sound for button 25"] sound_25: Option<String>,
|
#[description = "Name or ID of sound for button 9"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_9: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 10"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_10: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 11"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_11: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 12"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_12: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 13"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_13: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 14"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_14: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 15"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_15: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 16"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_16: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 17"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_17: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 18"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_18: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 19"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_19: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 20"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_20: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 21"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_21: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 22"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_22: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 23"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_23: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 24"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_24: Option<String>,
|
||||||
|
#[description = "Name or ID of sound for button 25"]
|
||||||
|
#[autocomplete = "autocomplete_sound"]
|
||||||
|
sound_25: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
ctx.defer().await?;
|
ctx.defer().await?;
|
||||||
|
|
||||||
let pool = ctx.data().database.clone();
|
|
||||||
|
|
||||||
let query_terms = [
|
let query_terms = [
|
||||||
Some(sound_1),
|
Some(sound_1),
|
||||||
sound_2,
|
sound_2,
|
||||||
@ -97,13 +171,9 @@ pub async fn soundboard(
|
|||||||
let mut sounds = vec![];
|
let mut sounds = vec![];
|
||||||
|
|
||||||
for sound in query_terms.iter().flatten() {
|
for sound in query_terms.iter().flatten() {
|
||||||
let search = Sound::search_for_sound(
|
let search = ctx
|
||||||
&sound,
|
.data()
|
||||||
ctx.guild_id().unwrap(),
|
.search_for_sound(&sound, ctx.guild_id().unwrap(), ctx.author().id, true)
|
||||||
ctx.author().id,
|
|
||||||
pool.clone(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(sound) = search.first() {
|
if let Some(sound) = search.first() {
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
use crate::sound::Sound;
|
use poise::{serenity::constants::MESSAGE_CODE_LIMIT, CreateReply};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
models::sound::{Sound, SoundCtx},
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn format_search_results<'a>(search_results: Vec<Sound>) -> CreateReply<'a> {
|
||||||
|
let mut builder = CreateReply::default();
|
||||||
|
|
||||||
fn format_search_results(search_results: Vec<Sound>) -> CreateGenericResponse {
|
|
||||||
let mut current_character_count = 0;
|
let mut current_character_count = 0;
|
||||||
let title = "Public sounds matching filter:";
|
let title = "Public sounds matching filter:";
|
||||||
|
|
||||||
@ -11,49 +18,25 @@ fn format_search_results(search_results: Vec<Sound>) -> CreateGenericResponse {
|
|||||||
.filter(|item| {
|
.filter(|item| {
|
||||||
current_character_count += item.0.len() + item.1.len();
|
current_character_count += item.0.len() + item.1.len();
|
||||||
|
|
||||||
current_character_count <= serenity::constants::MESSAGE_CODE_LIMIT - title.len()
|
current_character_count <= MESSAGE_CODE_LIMIT - title.len()
|
||||||
});
|
});
|
||||||
|
|
||||||
CreateGenericResponse::new().embed(|e| e.title(title).fields(field_iter))
|
builder.embed(|e| e.title(title).fields(field_iter));
|
||||||
|
|
||||||
|
builder
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("list")]
|
/// Show the sounds uploaded to this server
|
||||||
#[group("Search")]
|
#[poise::command(slash_command, rename = "list")]
|
||||||
#[description("Show the sounds uploaded by you or to your server")]
|
pub async fn list_sounds(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[arg(
|
|
||||||
name = "me",
|
|
||||||
description = "Whether to list your sounds or server sounds (default: server)",
|
|
||||||
kind = "Boolean",
|
|
||||||
required = false
|
|
||||||
)]
|
|
||||||
#[example("`/list` - list sounds uploaded to the server you're in")]
|
|
||||||
#[example("`/list [me: True]` - list sounds you have uploaded across all servers")]
|
|
||||||
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 sounds;
|
||||||
let mut message_buffer;
|
let mut message_buffer;
|
||||||
|
|
||||||
if args.named("me").map(|i| i.to_owned()) == Some("me".to_string()) {
|
sounds = ctx.data().guild_sounds(ctx.guild_id().unwrap()).await?;
|
||||||
sounds = Sound::user_sounds(invoke.author_id(), pool).await?;
|
|
||||||
|
|
||||||
message_buffer = "All your sounds: ".to_string();
|
message_buffer = "Sounds on this server: ".to_string();
|
||||||
} else {
|
|
||||||
sounds = Sound::guild_sounds(invoke.guild_id().unwrap(), pool).await?;
|
|
||||||
|
|
||||||
message_buffer = "All sounds on this server: ".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// todo change this to iterator
|
||||||
for sound in sounds {
|
for sound in sounds {
|
||||||
message_buffer.push_str(
|
message_buffer.push_str(
|
||||||
format!(
|
format!(
|
||||||
@ -65,85 +48,77 @@ pub async fn list_sounds(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if message_buffer.len() > 2000 {
|
if message_buffer.len() > 2000 {
|
||||||
invoke
|
ctx.say(message_buffer).await?;
|
||||||
.respond(
|
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new().content(message_buffer),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
message_buffer = "".to_string();
|
message_buffer = "".to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if message_buffer.len() > 0 {
|
if message_buffer.len() > 0 {
|
||||||
invoke
|
ctx.say(message_buffer).await?;
|
||||||
.respond(
|
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new().content(message_buffer),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("search")]
|
/// Show all sounds you have uploaded
|
||||||
#[group("Search")]
|
#[poise::command(slash_command, rename = "me")]
|
||||||
#[description("Search for sounds")]
|
pub async fn list_user_sounds(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[arg(
|
let sounds;
|
||||||
name = "query",
|
let mut message_buffer;
|
||||||
kind = "String",
|
|
||||||
description = "Sound name to search for",
|
sounds = ctx.data().user_sounds(ctx.author().id).await?;
|
||||||
required = true
|
|
||||||
)]
|
message_buffer = "Sounds on this server: ".to_string();
|
||||||
|
|
||||||
|
// todo change this to iterator
|
||||||
|
for sound in sounds {
|
||||||
|
message_buffer.push_str(
|
||||||
|
format!(
|
||||||
|
"**{}** ({}), ",
|
||||||
|
sound.name,
|
||||||
|
if sound.public { "🔓" } else { "🔒" }
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if message_buffer.len() > 2000 {
|
||||||
|
ctx.say(message_buffer).await?;
|
||||||
|
|
||||||
|
message_buffer = "".to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if message_buffer.len() > 0 {
|
||||||
|
ctx.say(message_buffer).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Search for sounds
|
||||||
|
#[poise::command(slash_command, rename = "search", category = "Search")]
|
||||||
pub async fn search_sounds(
|
pub async fn search_sounds(
|
||||||
ctx: &Context,
|
ctx: Context<'_>,
|
||||||
invoke: &(dyn CommandInvoke + Sync + Send),
|
#[description = "Sound name to search for"] query: String,
|
||||||
args: Args,
|
) -> Result<(), Error> {
|
||||||
) -> CommandResult {
|
let search_results = ctx
|
||||||
let pool = ctx
|
.data()
|
||||||
.data
|
.search_for_sound(&query, ctx.guild_id().unwrap(), ctx.author().id, false)
|
||||||
.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?;
|
.await?;
|
||||||
|
|
||||||
invoke
|
ctx.send(|m| {
|
||||||
.respond(ctx.http.clone(), format_search_results(search_results))
|
*m = format_search_results(search_results);
|
||||||
|
m
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("random")]
|
/// Show a page of random sounds
|
||||||
#[group("Search")]
|
#[poise::command(slash_command, rename = "random")]
|
||||||
#[description("Show a page of random sounds")]
|
pub async fn show_random_sounds(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
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!(
|
let search_results = sqlx::query_as_unchecked!(
|
||||||
Sound,
|
Sound,
|
||||||
"
|
"
|
||||||
@ -154,12 +129,13 @@ SELECT name, id, public, server_id, uploader_id
|
|||||||
LIMIT 25
|
LIMIT 25
|
||||||
"
|
"
|
||||||
)
|
)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&ctx.data().database)
|
||||||
.await
|
.await?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
invoke
|
ctx.send(|m| {
|
||||||
.respond(ctx.http.clone(), format_search_results(search_results))
|
*m = format_search_results(search_results);
|
||||||
|
m
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,307 +1,126 @@
|
|||||||
use regex_command_attr::command;
|
|
||||||
use serenity::{client::Context, framework::standard::CommandResult};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
framework::{Args, CommandInvoke, CreateGenericResponse},
|
models::{guild_data::CtxGuildData, join_sound::JoinSoundCtx, sound::SoundCtx},
|
||||||
guild_data::CtxGuildData,
|
Context, Error,
|
||||||
sound::{JoinSoundCtx, Sound},
|
|
||||||
MySQL,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[command("volume")]
|
/// Change the bot's volume in this server
|
||||||
#[aliases("vol")]
|
#[poise::command(slash_command, rename = "volume")]
|
||||||
#[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
|
|
||||||
)]
|
|
||||||
#[example("`/volume` - check the volume on the current server")]
|
|
||||||
#[example("`/volume 100` - reset the volume on the current server")]
|
|
||||||
#[example("`/volume 10` - set the volume on the current server to 10%")]
|
|
||||||
pub async fn change_volume(
|
pub async fn change_volume(
|
||||||
ctx: &Context,
|
ctx: Context<'_>,
|
||||||
invoke: &(dyn CommandInvoke + Sync + Send),
|
#[description = "New volume as a percentage"] volume: Option<usize>,
|
||||||
args: Args,
|
) -> Result<(), Error> {
|
||||||
) -> CommandResult {
|
let guild_data_opt = ctx.guild_data(ctx.guild_id().unwrap()).await;
|
||||||
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();
|
let guild_data = guild_data_opt.unwrap();
|
||||||
|
|
||||||
if let Some(volume) = args.named("volume").map(|i| i.parse::<u8>().ok()).flatten() {
|
if let Some(volume) = volume {
|
||||||
guild_data.write().await.volume = volume;
|
guild_data.write().await.volume = volume as u8;
|
||||||
|
|
||||||
guild_data.read().await.commit(pool).await?;
|
guild_data
|
||||||
|
.read()
|
||||||
invoke
|
.await
|
||||||
.respond(
|
.commit(ctx.data().database.clone())
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new().content(format!("Volume changed to {}%", volume)),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
ctx.say(format!("Volume changed to {}%", volume)).await?;
|
||||||
} else {
|
} else {
|
||||||
let read = guild_data.read().await;
|
let read = guild_data.read().await;
|
||||||
|
|
||||||
invoke
|
ctx.say(format!(
|
||||||
.respond(
|
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new().content(format!(
|
|
||||||
"Current server volume: {vol}%. Change the volume with `/volume <new volume>`",
|
"Current server volume: {vol}%. Change the volume with `/volume <new volume>`",
|
||||||
vol = read.volume
|
vol = read.volume
|
||||||
)),
|
))
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("prefix")]
|
/// Manage greet sounds on this server
|
||||||
#[required_permissions(Restricted)]
|
#[poise::command(slash_command, rename = "greet")]
|
||||||
#[kind(Text)]
|
pub async fn greet_sound(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[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 && !prefix.is_empty() {
|
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("roles")]
|
/// Set a join sound
|
||||||
#[required_permissions(Restricted)]
|
#[poise::command(slash_command, rename = "set")]
|
||||||
#[group("Settings")]
|
|
||||||
#[description("Change the role allowed to use the bot")]
|
|
||||||
#[arg(
|
|
||||||
name = "role",
|
|
||||||
kind = "Role",
|
|
||||||
description = "A role to allow to use the bot. Use @everyone to allow all server members",
|
|
||||||
required = true
|
|
||||||
)]
|
|
||||||
#[example("`/roles @everyone` - allow all server members to use the bot")]
|
|
||||||
#[example("`/roles @DJ` - allow only server members with the 'DJ' role to use the bot")]
|
|
||||||
pub async fn set_allowed_roles(
|
|
||||||
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 role_id = args.named("role").unwrap().parse::<u64>().unwrap();
|
|
||||||
let guild_data = ctx.guild_data(invoke.guild_id().unwrap()).await.unwrap();
|
|
||||||
|
|
||||||
guild_data.write().await.allowed_role = Some(role_id);
|
|
||||||
guild_data.read().await.commit(pool).await?;
|
|
||||||
|
|
||||||
invoke
|
|
||||||
.respond(
|
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new().content(format!("Allowed role set to <@&{}>", role_id)),
|
|
||||||
)
|
|
||||||
.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
|
|
||||||
)]
|
|
||||||
#[example("`/greet` - remove your join sound")]
|
|
||||||
#[example("`/greet 1523` - set your join sound to sound with ID 1523")]
|
|
||||||
pub async fn set_greet_sound(
|
pub async fn set_greet_sound(
|
||||||
ctx: &Context,
|
ctx: Context<'_>,
|
||||||
invoke: &(dyn CommandInvoke + Sync + Send),
|
#[description = "Name or ID of sound to set as your join sound"] name: String,
|
||||||
args: Args,
|
) -> Result<(), Error> {
|
||||||
) -> CommandResult {
|
let sound_vec = ctx
|
||||||
let pool = ctx
|
.data()
|
||||||
.data
|
.search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true)
|
||||||
.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?;
|
.await?;
|
||||||
|
|
||||||
match sound_vec.first() {
|
match sound_vec.first() {
|
||||||
Some(sound) => {
|
Some(sound) => {
|
||||||
ctx.update_join_sound(user_id, Some(sound.id)).await;
|
ctx.data()
|
||||||
|
.update_join_sound(ctx.author().id, Some(sound.id))
|
||||||
|
.await;
|
||||||
|
|
||||||
invoke
|
ctx.say(format!(
|
||||||
.respond(
|
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new().content(format!(
|
|
||||||
"Greet sound has been set to {} (ID {})",
|
"Greet sound has been set to {} (ID {})",
|
||||||
sound.name, sound.id
|
sound.name, sound.id
|
||||||
)),
|
))
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
invoke
|
ctx.say("Could not find a sound by that name.").await?;
|
||||||
.respond(
|
|
||||||
ctx.http.clone(),
|
|
||||||
CreateGenericResponse::new()
|
|
||||||
.content("Could not find a sound by that name."),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command("allow_greet")]
|
/// Set a join sound
|
||||||
#[group("Settings")]
|
#[poise::command(slash_command, rename = "unset")]
|
||||||
#[description("Configure whether users should be able to use join sounds")]
|
pub async fn unset_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[required_permissions(Restricted)]
|
ctx.data().update_join_sound(ctx.author().id, None).await;
|
||||||
#[example("`/allow_greet` - disable greet sounds in the server")]
|
|
||||||
#[example("`/allow_greet` - re-enable greet sounds in the server")]
|
|
||||||
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;
|
ctx.say("Greet sound has been unset").await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable greet sounds on this server
|
||||||
|
#[poise::command(slash_command, rename = "disable")]
|
||||||
|
pub async fn disable_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let guild_data_opt = ctx.guild_data(ctx.guild_id().unwrap()).await;
|
||||||
|
|
||||||
if let Ok(guild_data) = guild_data_opt {
|
if let Ok(guild_data) = guild_data_opt {
|
||||||
let current = guild_data.read().await.allow_greets;
|
guild_data.write().await.allow_greets = false;
|
||||||
|
|
||||||
{
|
guild_data
|
||||||
guild_data.write().await.allow_greets = !current;
|
.read()
|
||||||
}
|
.await
|
||||||
|
.commit(ctx.data().database.clone())
|
||||||
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?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.say("Greet sounds have been disabled in this server")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable greet sounds on this server
|
||||||
|
#[poise::command(slash_command, rename = "enable")]
|
||||||
|
pub async fn enable_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
|
let guild_data_opt = ctx.guild_data(ctx.guild_id().unwrap()).await;
|
||||||
|
|
||||||
|
if let Ok(guild_data) = guild_data_opt {
|
||||||
|
guild_data.write().await.allow_greets = true;
|
||||||
|
|
||||||
|
guild_data
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.commit(ctx.data().database.clone())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.say("Greet sounds have been enable in this server")
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,12 @@
|
|||||||
use regex_command_attr::command;
|
|
||||||
use serenity::{client::Context, framework::standard::CommandResult};
|
|
||||||
use songbird;
|
use songbird;
|
||||||
|
|
||||||
use crate::framework::{Args, CommandInvoke, CreateGenericResponse};
|
use crate::{Context, Error};
|
||||||
|
|
||||||
#[command("stop")]
|
/// Stop the bot from playing
|
||||||
#[required_permissions(Managed)]
|
#[poise::command(slash_command, rename = "stop")]
|
||||||
#[group("Stop")]
|
pub async fn stop_playing(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[description("Stop the bot from playing")]
|
let songbird = songbird::get(ctx.discord()).await.unwrap();
|
||||||
pub async fn stop_playing(
|
let call_opt = songbird.get(ctx.guild_id().unwrap());
|
||||||
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 {
|
if let Some(call) = call_opt {
|
||||||
let mut lock = call.lock().await;
|
let mut lock = call.lock().await;
|
||||||
@ -24,31 +14,18 @@ pub async fn stop_playing(
|
|||||||
lock.stop();
|
lock.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
invoke
|
ctx.say("👍").await?;
|
||||||
.respond(ctx.http.clone(), CreateGenericResponse::new().content("👍"))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[command]
|
/// Disconnect the bot
|
||||||
#[aliases("dc")]
|
#[poise::command(slash_command)]
|
||||||
#[required_permissions(Managed)]
|
pub async fn disconnect(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
#[group("Stop")]
|
let songbird = songbird::get(ctx.discord()).await.unwrap();
|
||||||
#[description("Disconnect the bot")]
|
let _ = songbird.leave(ctx.guild_id().unwrap()).await;
|
||||||
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();
|
ctx.say("👍").await?;
|
||||||
let _ = songbird.leave(guild_id).await;
|
|
||||||
|
|
||||||
invoke
|
|
||||||
.respond(ctx.http.clone(), CreateGenericResponse::new().content("👍"))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
9
src/consts.rs
Normal file
9
src/consts.rs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
pub const THEME_COLOR: u32 = 0x00e0f3;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref MAX_SOUNDS: u32 = env::var("MAX_SOUNDS").unwrap().parse::<u32>().unwrap();
|
||||||
|
pub static ref PATREON_GUILD: u64 = env::var("PATREON_GUILD").unwrap().parse::<u64>().unwrap();
|
||||||
|
pub static ref PATREON_ROLE: u64 = env::var("PATREON_ROLE").unwrap().parse::<u64>().unwrap();
|
||||||
|
}
|
@ -1,27 +1,19 @@
|
|||||||
use std::{collections::HashMap, env};
|
use std::{collections::HashMap, env};
|
||||||
|
|
||||||
use poise::serenity::{async_trait, model::channel::Channel, prelude::Context, utils::shard_id};
|
use poise::serenity::{
|
||||||
use songbird::{Event, EventContext, EventHandler as SongbirdEventHandler};
|
model::{
|
||||||
|
channel::Channel,
|
||||||
use crate::{
|
interactions::{Interaction, InteractionResponseType},
|
||||||
guild_data::CtxGuildData,
|
},
|
||||||
join_channel, play_audio,
|
prelude::Context,
|
||||||
sound::{JoinSoundCtx, Sound},
|
utils::shard_id,
|
||||||
Data, Error,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct RestartTrack;
|
use crate::{
|
||||||
|
models::{guild_data::CtxGuildData, join_sound::JoinSoundCtx, sound::Sound},
|
||||||
#[async_trait]
|
utils::{join_channel, play_audio, play_from_query},
|
||||||
impl SongbirdEventHandler for RestartTrack {
|
Data, Error,
|
||||||
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
};
|
||||||
if let EventContext::Track(&[(_state, track)]) = ctx {
|
|
||||||
let _ = track.seek_time(Default::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> Result<(), Error> {
|
pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> Result<(), Error> {
|
||||||
match event {
|
match event {
|
||||||
@ -126,6 +118,29 @@ SELECT name, id, public, server_id, uploader_id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
poise::Event::InteractionCreate { interaction } => match interaction {
|
||||||
|
Interaction::MessageComponent(component) => {
|
||||||
|
if component.guild_id.is_some() {
|
||||||
|
play_from_query(
|
||||||
|
&ctx,
|
||||||
|
&data,
|
||||||
|
component.guild_id.unwrap().to_guild_cached(&ctx).unwrap(),
|
||||||
|
component.user.id,
|
||||||
|
&component.data.custom_id,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
component
|
||||||
|
.create_interaction_response(ctx, |r| {
|
||||||
|
r.kind(InteractionResponseType::DeferredUpdateMessage)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
168
src/main.rs
168
src/main.rs
@ -2,10 +2,11 @@
|
|||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
mod cmds;
|
mod cmds;
|
||||||
|
mod consts;
|
||||||
mod error;
|
mod error;
|
||||||
mod event_handlers;
|
mod event_handlers;
|
||||||
mod guild_data;
|
mod models;
|
||||||
mod sound;
|
mod utils;
|
||||||
|
|
||||||
use std::{env, sync::Arc};
|
use std::{env, sync::Arc};
|
||||||
|
|
||||||
@ -14,21 +15,15 @@ use dotenv::dotenv;
|
|||||||
use poise::serenity::{
|
use poise::serenity::{
|
||||||
builder::CreateApplicationCommands,
|
builder::CreateApplicationCommands,
|
||||||
model::{
|
model::{
|
||||||
channel::Channel,
|
|
||||||
gateway::{Activity, GatewayIntents},
|
gateway::{Activity, GatewayIntents},
|
||||||
guild::Guild,
|
id::{GuildId, UserId},
|
||||||
id::{ChannelId, GuildId, UserId},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use songbird::{create_player, error::JoinResult, tracks::TrackHandle, Call, SerenityInit};
|
use songbird::SerenityInit;
|
||||||
use sqlx::mysql::MySqlPool;
|
use sqlx::mysql::MySqlPool;
|
||||||
use tokio::sync::{Mutex, MutexGuard, RwLock};
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::{
|
use crate::{event_handlers::listener, models::guild_data::GuildData};
|
||||||
event_handlers::listener,
|
|
||||||
guild_data::{CtxGuildData, GuildData},
|
|
||||||
sound::Sound,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Data {
|
pub struct Data {
|
||||||
database: MySqlPool,
|
database: MySqlPool,
|
||||||
@ -40,137 +35,6 @@ pub struct Data {
|
|||||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||||
|
|
||||||
const THEME_COLOR: u32 = 0x00e0f3;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref MAX_SOUNDS: u32 = env::var("MAX_SOUNDS").unwrap().parse::<u32>().unwrap();
|
|
||||||
static ref PATREON_GUILD: u64 = env::var("PATREON_GUILD").unwrap().parse::<u64>().unwrap();
|
|
||||||
static ref PATREON_ROLE: u64 = env::var("PATREON_ROLE").unwrap().parse::<u64>().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn play_audio(
|
|
||||||
sound: &mut Sound,
|
|
||||||
volume: u8,
|
|
||||||
call_handler: &mut MutexGuard<'_, Call>,
|
|
||||||
mysql_pool: MySqlPool,
|
|
||||||
loop_: bool,
|
|
||||||
) -> Result<TrackHandle, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let (track, track_handler) =
|
|
||||||
create_player(sound.store_sound_source(mysql_pool.clone()).await?.into());
|
|
||||||
|
|
||||||
let _ = track_handler.set_volume(volume as f32 / 100.0);
|
|
||||||
|
|
||||||
if loop_ {
|
|
||||||
let _ = track_handler.enable_loop();
|
|
||||||
} else {
|
|
||||||
let _ = track_handler.disable_loop();
|
|
||||||
}
|
|
||||||
|
|
||||||
call_handler.play(track);
|
|
||||||
|
|
||||||
Ok(track_handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn join_channel(
|
|
||||||
ctx: &poise::serenity_prelude::Context,
|
|
||||||
guild: Guild,
|
|
||||||
channel_id: ChannelId,
|
|
||||||
) -> (Arc<Mutex<Call>>, JoinResult<()>) {
|
|
||||||
let songbird = songbird::get(ctx).await.unwrap();
|
|
||||||
let current_user = ctx.cache.current_user_id();
|
|
||||||
|
|
||||||
let current_voice_state = guild
|
|
||||||
.voice_states
|
|
||||||
.get(¤t_user)
|
|
||||||
.and_then(|voice_state| voice_state.channel_id);
|
|
||||||
|
|
||||||
let (call, res) = if current_voice_state == Some(channel_id) {
|
|
||||||
let call_opt = songbird.get(guild.id);
|
|
||||||
|
|
||||||
if let Some(call) = call_opt {
|
|
||||||
(call, Ok(()))
|
|
||||||
} else {
|
|
||||||
let (call, res) = songbird.join(guild.id, channel_id).await;
|
|
||||||
|
|
||||||
(call, res)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let (call, res) = songbird.join(guild.id, channel_id).await;
|
|
||||||
|
|
||||||
(call, res)
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
// set call to deafen
|
|
||||||
let _ = call.lock().await.deafen(true).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) {
|
|
||||||
channel
|
|
||||||
.edit_voice_state(&ctx, ctx.cache.current_user(), |v| v.suppress(false))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
|
|
||||||
(call, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn play_from_query(
|
|
||||||
ctx: &Context<'_>,
|
|
||||||
guild: Guild,
|
|
||||||
user_id: UserId,
|
|
||||||
query: &str,
|
|
||||||
loop_: bool,
|
|
||||||
) -> String {
|
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let channel_to_join = guild
|
|
||||||
.voice_states
|
|
||||||
.get(&user_id)
|
|
||||||
.and_then(|voice_state| voice_state.channel_id);
|
|
||||||
|
|
||||||
match channel_to_join {
|
|
||||||
Some(user_channel) => {
|
|
||||||
let pool = ctx.data().database.clone();
|
|
||||||
|
|
||||||
let mut sound_vec =
|
|
||||||
Sound::search_for_sound(query, guild_id, user_id, pool.clone(), true)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let sound_res = sound_vec.first_mut();
|
|
||||||
|
|
||||||
match sound_res {
|
|
||||||
Some(sound) => {
|
|
||||||
{
|
|
||||||
let (call_handler, _) =
|
|
||||||
join_channel(ctx.discord(), guild.clone(), user_channel).await;
|
|
||||||
|
|
||||||
let guild_data = ctx.guild_data(guild_id).await.unwrap();
|
|
||||||
|
|
||||||
let mut lock = call_handler.lock().await;
|
|
||||||
|
|
||||||
play_audio(
|
|
||||||
sound,
|
|
||||||
guild_data.read().await.volume,
|
|
||||||
&mut lock,
|
|
||||||
pool,
|
|
||||||
loop_,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("Playing sound {} with ID {}", sound.name, sound.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
None => "Couldn't find sound by term provided".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => "You are not in a voice chat!".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn register_application_commands(
|
pub async fn register_application_commands(
|
||||||
ctx: &poise::serenity::client::Context,
|
ctx: &poise::serenity::client::Context,
|
||||||
framework: &poise::Framework<Data, Error>,
|
framework: &poise::Framework<Data, Error>,
|
||||||
@ -215,10 +79,28 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|||||||
cmds::info::info(),
|
cmds::info::info(),
|
||||||
cmds::manage::change_public(),
|
cmds::manage::change_public(),
|
||||||
cmds::manage::upload_new_sound(),
|
cmds::manage::upload_new_sound(),
|
||||||
|
cmds::manage::download_file(),
|
||||||
cmds::manage::delete_sound(),
|
cmds::manage::delete_sound(),
|
||||||
cmds::play::play(),
|
cmds::play::play(),
|
||||||
cmds::play::loop_play(),
|
cmds::play::loop_play(),
|
||||||
cmds::play::soundboard(),
|
cmds::play::soundboard(),
|
||||||
|
cmds::search::list_sounds(),
|
||||||
|
cmds::search::list_user_sounds(),
|
||||||
|
cmds::search::show_random_sounds(),
|
||||||
|
cmds::search::search_sounds(),
|
||||||
|
cmds::stop::stop_playing(),
|
||||||
|
cmds::stop::disconnect(),
|
||||||
|
cmds::settings::change_volume(),
|
||||||
|
poise::Command {
|
||||||
|
subcommands: vec![
|
||||||
|
cmds::settings::disable_greet_sound(),
|
||||||
|
cmds::settings::enable_greet_sound(),
|
||||||
|
cmds::settings::set_greet_sound(),
|
||||||
|
cmds::settings::unset_greet_sound(),
|
||||||
|
cmds::settings::greet_sound(),
|
||||||
|
],
|
||||||
|
..cmds::settings::greet_sound()
|
||||||
|
},
|
||||||
],
|
],
|
||||||
allowed_mentions: None,
|
allowed_mentions: None,
|
||||||
listener: |ctx, event, _framework, data| Box::pin(listener(ctx, event, data)),
|
listener: |ctx, event, _framework, data| Box::pin(listener(ctx, event, data)),
|
||||||
|
87
src/models/join_sound.rs
Normal file
87
src/models/join_sound.rs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
use poise::serenity::{async_trait, model::id::UserId};
|
||||||
|
|
||||||
|
use crate::Data;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait JoinSoundCtx {
|
||||||
|
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32>;
|
||||||
|
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
user_id: U,
|
||||||
|
join_id: Option<u32>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl JoinSoundCtx for Data {
|
||||||
|
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32> {
|
||||||
|
let user_id = user_id.into();
|
||||||
|
|
||||||
|
let x = if let Some(join_sound_id) = self.join_sound_cache.get(&user_id) {
|
||||||
|
join_sound_id.value().clone()
|
||||||
|
} else {
|
||||||
|
let join_sound_id = {
|
||||||
|
let pool = self.database.clone();
|
||||||
|
|
||||||
|
let join_id_res = sqlx::query!(
|
||||||
|
"
|
||||||
|
SELECT join_sound_id
|
||||||
|
FROM users
|
||||||
|
WHERE user = ?
|
||||||
|
",
|
||||||
|
user_id.as_u64()
|
||||||
|
)
|
||||||
|
.fetch_one(&pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if let Ok(row) = join_id_res {
|
||||||
|
row.join_sound_id
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
self.join_sound_cache.insert(user_id, join_sound_id);
|
||||||
|
|
||||||
|
join_sound_id
|
||||||
|
};
|
||||||
|
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
|
||||||
|
&self,
|
||||||
|
user_id: U,
|
||||||
|
join_id: Option<u32>,
|
||||||
|
) {
|
||||||
|
let user_id = user_id.into();
|
||||||
|
|
||||||
|
self.join_sound_cache.insert(user_id, join_id);
|
||||||
|
|
||||||
|
let pool = self.database.clone();
|
||||||
|
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"
|
||||||
|
INSERT IGNORE INTO users (user)
|
||||||
|
VALUES (?)
|
||||||
|
",
|
||||||
|
user_id.as_u64()
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let _ = sqlx::query!(
|
||||||
|
"
|
||||||
|
UPDATE users
|
||||||
|
SET
|
||||||
|
join_sound_id = ?
|
||||||
|
WHERE
|
||||||
|
user = ?
|
||||||
|
",
|
||||||
|
join_id,
|
||||||
|
user_id.as_u64()
|
||||||
|
)
|
||||||
|
.execute(&pool)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
3
src/models/mod.rs
Normal file
3
src/models/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod guild_data;
|
||||||
|
pub mod join_sound;
|
||||||
|
pub mod sound;
|
@ -1,96 +1,11 @@
|
|||||||
use std::{env, path::Path};
|
use std::{env, path::Path};
|
||||||
|
|
||||||
use poise::serenity::{async_trait, model::id::UserId};
|
use poise::serenity::async_trait;
|
||||||
use songbird::input::restartable::Restartable;
|
use songbird::input::restartable::Restartable;
|
||||||
use sqlx::mysql::MySqlPool;
|
use sqlx::{mysql::MySqlPool, Error};
|
||||||
use tokio::{fs::File, io::AsyncWriteExt, process::Command};
|
use tokio::{fs::File, io::AsyncWriteExt, process::Command};
|
||||||
|
|
||||||
use super::error::ErrorTypes;
|
use crate::{error::ErrorTypes, Data};
|
||||||
use crate::Data;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait JoinSoundCtx {
|
|
||||||
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32>;
|
|
||||||
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
|
|
||||||
&self,
|
|
||||||
user_id: U,
|
|
||||||
join_id: Option<u32>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl JoinSoundCtx for Data {
|
|
||||||
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32> {
|
|
||||||
let user_id = user_id.into();
|
|
||||||
|
|
||||||
let x = if let Some(join_sound_id) = self.join_sound_cache.get(&user_id) {
|
|
||||||
join_sound_id.value().clone()
|
|
||||||
} else {
|
|
||||||
let join_sound_id = {
|
|
||||||
let pool = self.database.clone();
|
|
||||||
|
|
||||||
let join_id_res = sqlx::query!(
|
|
||||||
"
|
|
||||||
SELECT join_sound_id
|
|
||||||
FROM users
|
|
||||||
WHERE user = ?
|
|
||||||
",
|
|
||||||
user_id.as_u64()
|
|
||||||
)
|
|
||||||
.fetch_one(&pool)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Ok(row) = join_id_res {
|
|
||||||
row.join_sound_id
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.join_sound_cache.insert(user_id, join_sound_id);
|
|
||||||
|
|
||||||
join_sound_id
|
|
||||||
};
|
|
||||||
|
|
||||||
x
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
|
|
||||||
&self,
|
|
||||||
user_id: U,
|
|
||||||
join_id: Option<u32>,
|
|
||||||
) {
|
|
||||||
let user_id = user_id.into();
|
|
||||||
|
|
||||||
self.join_sound_cache.insert(user_id, join_id);
|
|
||||||
|
|
||||||
let pool = self.database.clone();
|
|
||||||
|
|
||||||
let _ = sqlx::query!(
|
|
||||||
"
|
|
||||||
INSERT IGNORE INTO users (user)
|
|
||||||
VALUES (?)
|
|
||||||
",
|
|
||||||
user_id.as_u64()
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let _ = sqlx::query!(
|
|
||||||
"
|
|
||||||
UPDATE users
|
|
||||||
SET
|
|
||||||
join_sound_id = ?
|
|
||||||
WHERE
|
|
||||||
user = ?
|
|
||||||
",
|
|
||||||
join_id,
|
|
||||||
user_id.as_u64()
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Sound {
|
pub struct Sound {
|
||||||
@ -107,16 +22,41 @@ impl PartialEq for Sound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sound {
|
#[async_trait]
|
||||||
pub async fn search_for_sound<G: Into<u64>, U: Into<u64>>(
|
pub trait SoundCtx {
|
||||||
|
async fn search_for_sound<G: Into<u64> + Send, U: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
|
query: &str,
|
||||||
|
guild_id: G,
|
||||||
|
user_id: U,
|
||||||
|
strict: bool,
|
||||||
|
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||||
|
async fn autocomplete_user_sounds<U: Into<u64> + Send, G: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
|
query: &str,
|
||||||
|
user_id: U,
|
||||||
|
guild_id: G,
|
||||||
|
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||||
|
async fn user_sounds<U: Into<u64> + Send>(&self, user_id: U)
|
||||||
|
-> Result<Vec<Sound>, sqlx::Error>;
|
||||||
|
async fn guild_sounds<G: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
|
guild_id: G,
|
||||||
|
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl SoundCtx for Data {
|
||||||
|
async fn search_for_sound<G: Into<u64> + Send, U: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
query: &str,
|
query: &str,
|
||||||
guild_id: G,
|
guild_id: G,
|
||||||
user_id: U,
|
user_id: U,
|
||||||
db_pool: MySqlPool,
|
|
||||||
strict: bool,
|
strict: bool,
|
||||||
) -> Result<Vec<Sound>, sqlx::Error> {
|
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||||
let guild_id = guild_id.into();
|
let guild_id = guild_id.into();
|
||||||
let user_id = user_id.into();
|
let user_id = user_id.into();
|
||||||
|
let db_pool = self.database.clone();
|
||||||
|
|
||||||
fn extract_id(s: &str) -> Option<u32> {
|
fn extract_id(s: &str) -> Option<u32> {
|
||||||
if s.len() > 3 && s.to_lowercase().starts_with("id:") {
|
if s.len() > 3 && s.to_lowercase().starts_with("id:") {
|
||||||
@ -134,7 +74,7 @@ impl Sound {
|
|||||||
|
|
||||||
if let Some(id) = extract_id(&query) {
|
if let Some(id) = extract_id(&query) {
|
||||||
let sound = sqlx::query_as_unchecked!(
|
let sound = sqlx::query_as_unchecked!(
|
||||||
Self,
|
Sound,
|
||||||
"
|
"
|
||||||
SELECT name, id, public, server_id, uploader_id
|
SELECT name, id, public, server_id, uploader_id
|
||||||
FROM sounds
|
FROM sounds
|
||||||
@ -158,7 +98,7 @@ SELECT name, id, public, server_id, uploader_id
|
|||||||
|
|
||||||
if strict {
|
if strict {
|
||||||
sound = sqlx::query_as_unchecked!(
|
sound = sqlx::query_as_unchecked!(
|
||||||
Self,
|
Sound,
|
||||||
"
|
"
|
||||||
SELECT name, id, public, server_id, uploader_id
|
SELECT name, id, public, server_id, uploader_id
|
||||||
FROM sounds
|
FROM sounds
|
||||||
@ -179,7 +119,7 @@ SELECT name, id, public, server_id, uploader_id
|
|||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
sound = sqlx::query_as_unchecked!(
|
sound = sqlx::query_as_unchecked!(
|
||||||
Self,
|
Sound,
|
||||||
"
|
"
|
||||||
SELECT name, id, public, server_id, uploader_id
|
SELECT name, id, public, server_id, uploader_id
|
||||||
FROM sounds
|
FROM sounds
|
||||||
@ -204,6 +144,70 @@ SELECT name, id, public, server_id, uploader_id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn autocomplete_user_sounds<U: Into<u64> + Send, G: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
|
query: &str,
|
||||||
|
user_id: U,
|
||||||
|
guild_id: G,
|
||||||
|
) -> Result<Vec<Sound>, Error> {
|
||||||
|
let db_pool = self.database.clone();
|
||||||
|
|
||||||
|
sqlx::query_as_unchecked!(
|
||||||
|
Sound,
|
||||||
|
"
|
||||||
|
SELECT name, id, public, server_id, uploader_id
|
||||||
|
FROM sounds
|
||||||
|
WHERE name LIKE CONCAT(?, '%') AND (uploader_id = ? OR server_id = ?)
|
||||||
|
LIMIT 25
|
||||||
|
",
|
||||||
|
query,
|
||||||
|
user_id.into(),
|
||||||
|
guild_id.into(),
|
||||||
|
)
|
||||||
|
.fetch_all(&db_pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn user_sounds<U: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
|
user_id: U,
|
||||||
|
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||||
|
let sounds = sqlx::query_as_unchecked!(
|
||||||
|
Sound,
|
||||||
|
"
|
||||||
|
SELECT name, id, public, server_id, uploader_id
|
||||||
|
FROM sounds
|
||||||
|
WHERE uploader_id = ?
|
||||||
|
",
|
||||||
|
user_id.into()
|
||||||
|
)
|
||||||
|
.fetch_all(&self.database)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(sounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn guild_sounds<G: Into<u64> + Send>(
|
||||||
|
&self,
|
||||||
|
guild_id: G,
|
||||||
|
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||||
|
let sounds = sqlx::query_as_unchecked!(
|
||||||
|
Sound,
|
||||||
|
"
|
||||||
|
SELECT name, id, public, server_id, uploader_id
|
||||||
|
FROM sounds
|
||||||
|
WHERE server_id = ?
|
||||||
|
",
|
||||||
|
guild_id.into()
|
||||||
|
)
|
||||||
|
.fetch_all(&self.database)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(sounds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sound {
|
||||||
async fn src(&self, db_pool: MySqlPool) -> Vec<u8> {
|
async fn src(&self, db_pool: MySqlPool) -> Vec<u8> {
|
||||||
struct Src {
|
struct Src {
|
||||||
src: Vec<u8>,
|
src: Vec<u8>,
|
||||||
@ -229,7 +233,7 @@ SELECT src
|
|||||||
pub async fn store_sound_source(
|
pub async fn store_sound_source(
|
||||||
&self,
|
&self,
|
||||||
db_pool: MySqlPool,
|
db_pool: MySqlPool,
|
||||||
) -> Result<Restartable, Box<dyn std::error::Error + Send + Sync>> {
|
) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let caching_location = env::var("CACHING_LOCATION").unwrap_or(String::from("/tmp"));
|
let caching_location = env::var("CACHING_LOCATION").unwrap_or(String::from("/tmp"));
|
||||||
|
|
||||||
let path_name = format!("{}/sound-{}", caching_location, self.id);
|
let path_name = format!("{}/sound-{}", caching_location, self.id);
|
||||||
@ -241,6 +245,15 @@ SELECT src
|
|||||||
file.write_all(&self.src(db_pool).await).await?;
|
file.write_all(&self.src(db_pool).await).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(path_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn playable(
|
||||||
|
&self,
|
||||||
|
db_pool: MySqlPool,
|
||||||
|
) -> Result<Restartable, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let path_name = self.store_sound_source(db_pool).await?;
|
||||||
|
|
||||||
Ok(Restartable::ffmpeg(path_name, false)
|
Ok(Restartable::ffmpeg(path_name, false)
|
||||||
.await
|
.await
|
||||||
.expect("FFMPEG ERROR!"))
|
.expect("FFMPEG ERROR!"))
|
||||||
@ -397,42 +410,4 @@ INSERT INTO sounds (name, server_id, uploader_id, public, src)
|
|||||||
None => Err(Box::new(ErrorTypes::InvalidFile)),
|
None => Err(Box::new(ErrorTypes::InvalidFile)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn user_sounds<U: Into<u64>>(
|
|
||||||
user_id: U,
|
|
||||||
db_pool: MySqlPool,
|
|
||||||
) -> Result<Vec<Sound>, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let sounds = sqlx::query_as_unchecked!(
|
|
||||||
Sound,
|
|
||||||
"
|
|
||||||
SELECT name, id, public, server_id, uploader_id
|
|
||||||
FROM sounds
|
|
||||||
WHERE uploader_id = ?
|
|
||||||
",
|
|
||||||
user_id.into()
|
|
||||||
)
|
|
||||||
.fetch_all(&db_pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(sounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn guild_sounds<G: Into<u64>>(
|
|
||||||
guild_id: G,
|
|
||||||
db_pool: MySqlPool,
|
|
||||||
) -> Result<Vec<Sound>, Box<dyn std::error::Error + Send + Sync>> {
|
|
||||||
let sounds = sqlx::query_as_unchecked!(
|
|
||||||
Sound,
|
|
||||||
"
|
|
||||||
SELECT name, id, public, server_id, uploader_id
|
|
||||||
FROM sounds
|
|
||||||
WHERE server_id = ?
|
|
||||||
",
|
|
||||||
guild_id.into()
|
|
||||||
)
|
|
||||||
.fetch_all(&db_pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(sounds)
|
|
||||||
}
|
|
||||||
}
|
}
|
141
src/utils.rs
Normal file
141
src/utils.rs
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use poise::serenity::model::{
|
||||||
|
channel::Channel,
|
||||||
|
guild::Guild,
|
||||||
|
id::{ChannelId, UserId},
|
||||||
|
};
|
||||||
|
use songbird::{create_player, error::JoinResult, tracks::TrackHandle, Call};
|
||||||
|
use sqlx::MySqlPool;
|
||||||
|
use tokio::sync::{Mutex, MutexGuard};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
models::{
|
||||||
|
guild_data::CtxGuildData,
|
||||||
|
sound::{Sound, SoundCtx},
|
||||||
|
},
|
||||||
|
Data,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn play_audio(
|
||||||
|
sound: &mut Sound,
|
||||||
|
volume: u8,
|
||||||
|
call_handler: &mut MutexGuard<'_, Call>,
|
||||||
|
mysql_pool: MySqlPool,
|
||||||
|
loop_: bool,
|
||||||
|
) -> Result<TrackHandle, Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
let (track, track_handler) = create_player(sound.playable(mysql_pool.clone()).await?.into());
|
||||||
|
|
||||||
|
let _ = track_handler.set_volume(volume as f32 / 100.0);
|
||||||
|
|
||||||
|
if loop_ {
|
||||||
|
let _ = track_handler.enable_loop();
|
||||||
|
} else {
|
||||||
|
let _ = track_handler.disable_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
call_handler.play(track);
|
||||||
|
|
||||||
|
Ok(track_handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn join_channel(
|
||||||
|
ctx: &poise::serenity_prelude::Context,
|
||||||
|
guild: Guild,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
) -> (Arc<Mutex<Call>>, JoinResult<()>) {
|
||||||
|
let songbird = songbird::get(ctx).await.unwrap();
|
||||||
|
let current_user = ctx.cache.current_user_id();
|
||||||
|
|
||||||
|
let current_voice_state = guild
|
||||||
|
.voice_states
|
||||||
|
.get(¤t_user)
|
||||||
|
.and_then(|voice_state| voice_state.channel_id);
|
||||||
|
|
||||||
|
let (call, res) = if current_voice_state == Some(channel_id) {
|
||||||
|
let call_opt = songbird.get(guild.id);
|
||||||
|
|
||||||
|
if let Some(call) = call_opt {
|
||||||
|
(call, Ok(()))
|
||||||
|
} else {
|
||||||
|
let (call, res) = songbird.join(guild.id, channel_id).await;
|
||||||
|
|
||||||
|
(call, res)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let (call, res) = songbird.join(guild.id, channel_id).await;
|
||||||
|
|
||||||
|
(call, res)
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
// set call to deafen
|
||||||
|
let _ = call.lock().await.deafen(true).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) {
|
||||||
|
let _ = channel
|
||||||
|
.edit_voice_state(&ctx, ctx.cache.current_user(), |v| v.suppress(false))
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(call, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn play_from_query(
|
||||||
|
ctx: &poise::serenity_prelude::Context,
|
||||||
|
data: &Data,
|
||||||
|
guild: Guild,
|
||||||
|
user_id: UserId,
|
||||||
|
query: &str,
|
||||||
|
loop_: bool,
|
||||||
|
) -> String {
|
||||||
|
let guild_id = guild.id;
|
||||||
|
|
||||||
|
let channel_to_join = guild
|
||||||
|
.voice_states
|
||||||
|
.get(&user_id)
|
||||||
|
.and_then(|voice_state| voice_state.channel_id);
|
||||||
|
|
||||||
|
match channel_to_join {
|
||||||
|
Some(user_channel) => {
|
||||||
|
let pool = data.database.clone();
|
||||||
|
|
||||||
|
let mut sound_vec = data
|
||||||
|
.search_for_sound(query, guild_id, user_id, true)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sound_res = sound_vec.first_mut();
|
||||||
|
|
||||||
|
match sound_res {
|
||||||
|
Some(sound) => {
|
||||||
|
{
|
||||||
|
let (call_handler, _) =
|
||||||
|
join_channel(ctx, guild.clone(), user_channel).await;
|
||||||
|
|
||||||
|
let guild_data = data.guild_data(guild_id).await.unwrap();
|
||||||
|
|
||||||
|
let mut lock = call_handler.lock().await;
|
||||||
|
|
||||||
|
play_audio(
|
||||||
|
sound,
|
||||||
|
guild_data.read().await.volume,
|
||||||
|
&mut lock,
|
||||||
|
pool,
|
||||||
|
loop_,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
format!("Playing sound {} with ID {}", sound.name, sound.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
None => "Couldn't find sound by term provided".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None => "You are not in a voice chat!".to_string(),
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user