diff --git a/Cargo.lock b/Cargo.lock index d9afef1..a105bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -195,7 +195,7 @@ dependencies = [ [[package]] name = "bot-database" -version = "0.5.0" +version = "0.6.0" dependencies = [ "chrono", "diesel", @@ -209,7 +209,7 @@ dependencies = [ [[package]] name = "bot-serenityutils" -version = "0.2.3" +version = "0.2.4" dependencies = [ "futures", "log 0.4.14", @@ -1095,6 +1095,7 @@ dependencies = [ "serde", "serde-aux", "serde_json", + "serenity", "songbird", "tokio", "tokio-native-tls", @@ -2521,7 +2522,7 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tobi-rs" -version = "0.7.0" +version = "0.7.1" dependencies = [ "aspotify", "bot-coreutils", diff --git a/Cargo.toml b/Cargo.toml index 07397b7..563c80e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tobi-rs" -version = "0.7.0" +version = "0.7.1" authors = ["trivernis "] edition = "2018" @@ -39,4 +39,4 @@ rustc_version_runtime = "0.2.0" trigram = "0.4.4" typemap_rev = "0.1.5" youtube-metadata = "0.1.1" -lavalink-rs = {version="0.7.1", features=["native"]} \ No newline at end of file +lavalink-rs = {version="0.7.1", features=["native", "serenity"]} \ No newline at end of file diff --git a/bot-database/Cargo.toml b/bot-database/Cargo.toml index 57372b2..c5da86e 100644 --- a/bot-database/Cargo.toml +++ b/bot-database/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bot-database" -version = "0.5.0" +version = "0.6.0" authors = ["trivernis "] edition = "2018" diff --git a/bot-database/migrations/2021-04-21-095152_rename_gifs_table/down.sql b/bot-database/migrations/2021-04-21-095152_rename_gifs_table/down.sql new file mode 100644 index 0000000..e3b46cf --- /dev/null +++ b/bot-database/migrations/2021-04-21-095152_rename_gifs_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE media RENAME TO gifs; \ No newline at end of file diff --git a/bot-database/migrations/2021-04-21-095152_rename_gifs_table/up.sql b/bot-database/migrations/2021-04-21-095152_rename_gifs_table/up.sql new file mode 100644 index 0000000..65b3f9a --- /dev/null +++ b/bot-database/migrations/2021-04-21-095152_rename_gifs_table/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE gifs RENAME TO media; \ No newline at end of file diff --git a/bot-database/src/database/gifs.rs b/bot-database/src/database/media.rs similarity index 67% rename from bot-database/src/database/gifs.rs rename to bot-database/src/database/media.rs index 148929c..3cde521 100644 --- a/bot-database/src/database/gifs.rs +++ b/bot-database/src/database/media.rs @@ -9,42 +9,42 @@ use crate::Database; impl Database { /// Returns a list of all gifs in the database - pub async fn get_all_gifs(&self) -> DatabaseResult> { - use gifs::dsl; + pub async fn get_all_media(&self) -> DatabaseResult> { + use media::dsl; log::debug!("Loading all gifs from the database"); - let gifs: Vec = dsl::gifs.load_async::(&self.pool).await?; + let gifs: Vec = dsl::media.load_async::(&self.pool).await?; Ok(gifs) } /// Returns a list of gifs by assigned category - pub async fn get_gifs_by_category(&self, category: &str) -> DatabaseResult> { - use gifs::dsl; + pub async fn get_media_by_category(&self, category: &str) -> DatabaseResult> { + use media::dsl; log::debug!("Searching for gifs in category '{}'", category); - let gifs: Vec = dsl::gifs + let gifs: Vec = dsl::media .filter(dsl::category.eq(category)) - .load_async::(&self.pool) + .load_async::(&self.pool) .await?; Ok(gifs) } /// Adds a gif to the database - pub async fn add_gif( + pub async fn add_media( &self, url: &str, category: Option, name: Option, ) -> DatabaseResult<()> { - use gifs::dsl; + use media::dsl; log::debug!( "Inserting gif with url '{}' and name {:?} and category {:?}", url, name, category ); - insert_into(dsl::gifs) - .values(GifInsert { + insert_into(dsl::media) + .values(MediaInsert { url: url.to_string(), name, category, diff --git a/bot-database/src/database/mod.rs b/bot-database/src/database/mod.rs index 39e6ba6..1bd144d 100644 --- a/bot-database/src/database/mod.rs +++ b/bot-database/src/database/mod.rs @@ -1,16 +1,16 @@ pub use ephemeral_messages::*; -pub use gifs::*; pub use guild_playlists::*; pub use guild_playlists::*; +pub use media::*; pub use statistics::*; pub use youtube_songs::*; use crate::PoolConnection; mod ephemeral_messages; -mod gifs; mod guild_playlists; mod guild_settings; +mod media; mod statistics; mod youtube_songs; diff --git a/bot-database/src/models.rs b/bot-database/src/models.rs index 27816b6..5240fb1 100644 --- a/bot-database/src/models.rs +++ b/bot-database/src/models.rs @@ -32,7 +32,7 @@ pub struct GuildPlaylistInsert { } #[derive(Queryable, Debug, Clone)] -pub struct Gif { +pub struct Media { pub id: i64, pub category: Option, pub name: Option, @@ -40,8 +40,8 @@ pub struct Gif { } #[derive(Insertable, Debug)] -#[table_name = "gifs"] -pub struct GifInsert { +#[table_name = "media"] +pub struct MediaInsert { pub category: Option, pub name: Option, pub url: String, diff --git a/bot-database/src/schema.rs b/bot-database/src/schema.rs index 36a9abf..3395b30 100644 --- a/bot-database/src/schema.rs +++ b/bot-database/src/schema.rs @@ -6,15 +6,6 @@ table! { } } -table! { - gifs (id) { - id -> Int8, - category -> Nullable, - name -> Nullable, - url -> Varchar, - } -} - table! { guild_playlists (guild_id, name) { guild_id -> Int8, @@ -31,6 +22,15 @@ table! { } } +table! { + media (id) { + id -> Int8, + category -> Nullable, + name -> Nullable, + url -> Varchar, + } +} + table! { statistics (id) { id -> Int8, @@ -56,9 +56,9 @@ table! { allow_tables_to_appear_in_same_query!( ephemeral_messages, - gifs, guild_playlists, guild_settings, + media, statistics, youtube_songs, ); diff --git a/bot-serenityutils/Cargo.toml b/bot-serenityutils/Cargo.toml index 8e1b28f..a71651c 100644 --- a/bot-serenityutils/Cargo.toml +++ b/bot-serenityutils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bot-serenityutils" -version = "0.2.3" +version = "0.2.4" authors = ["trivernis "] edition = "2018" diff --git a/bot-serenityutils/src/menu/controls.rs b/bot-serenityutils/src/menu/controls.rs index a69c2ee..fab215d 100644 --- a/bot-serenityutils/src/menu/controls.rs +++ b/bot-serenityutils/src/menu/controls.rs @@ -124,7 +124,7 @@ pub async fn toggle_help( } /// Displays the menu page -async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> { +pub async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> { log::debug!("Displaying page {}", menu.current_page); let page = menu .pages diff --git a/src/commands/misc/add_gif.rs b/src/commands/misc/add_media.rs similarity index 75% rename from src/commands/misc/add_gif.rs rename to src/commands/misc/add_media.rs index f0a040c..76b3767 100644 --- a/src/commands/misc/add_gif.rs +++ b/src/commands/misc/add_media.rs @@ -8,14 +8,14 @@ use serenity::framework::standard::{Args, CommandResult}; use serenity::model::channel::Message; #[command] -#[description("Simple ping test command")] +#[description("Adds media to the database")] #[usage(" [] []")] #[bucket("general")] -#[aliases("add-gif", "addgif")] +#[aliases("add_gif", "add-gif", "addgif", "add-media", "addmedia")] #[min_args(1)] #[max_args(3)] #[owners_only] -async fn add_gif(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { +async fn add_media(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let url = args.single::()?; if !url::is_valid(&url) { @@ -26,10 +26,10 @@ async fn add_gif(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult let name = args.single_quoted::().ok(); let database = get_database_from_context(&ctx).await; - database.add_gif(&url, category, name).await?; + database.add_media(&url, category, name).await?; EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |c| { c.reference_message(msg) - .content("Gif added to the database.") + .content("Media entry added to the database.") }) .await?; diff --git a/src/commands/misc/gifs.rs b/src/commands/misc/media.rs similarity index 65% rename from src/commands/misc/gifs.rs rename to src/commands/misc/media.rs index ca0b77d..a4597af 100644 --- a/src/commands/misc/gifs.rs +++ b/src/commands/misc/media.rs @@ -1,4 +1,4 @@ -use crate::messages::gifs::create_gifs_menu; +use crate::messages::gifs::create_media_menu; use crate::utils::context_data::get_database_from_context; use serenity::client::Context; use serenity::framework::standard::macros::command; @@ -9,10 +9,10 @@ use serenity::model::channel::Message; #[description("Displays a list of all gifs used by the bot")] #[bucket("general")] #[only_in(guilds)] -async fn gifs(ctx: &Context, msg: &Message) -> CommandResult { +async fn media(ctx: &Context, msg: &Message) -> CommandResult { let database = get_database_from_context(ctx).await; - let gifs = database.get_all_gifs().await?; - create_gifs_menu(ctx, msg.channel_id, gifs).await?; + let gifs = database.get_all_media().await?; + create_media_menu(ctx, msg.channel_id, gifs).await?; Ok(()) } diff --git a/src/commands/misc/mod.rs b/src/commands/misc/mod.rs index 0e4ad76..494b8d5 100644 --- a/src/commands/misc/mod.rs +++ b/src/commands/misc/mod.rs @@ -1,9 +1,9 @@ use serenity::framework::standard::macros::group; use about::ABOUT_COMMAND; -use add_gif::ADD_GIF_COMMAND; +use add_media::ADD_MEDIA_COMMAND; use clear::CLEAR_COMMAND; -use gifs::GIFS_COMMAND; +use media::MEDIA_COMMAND; use pain::PAIN_COMMAND; use ping::PING_COMMAND; use qalc::QALC_COMMAND; @@ -13,10 +13,10 @@ use time::TIME_COMMAND; use timezones::TIMEZONES_COMMAND; mod about; -mod add_gif; +mod add_media; mod clear; -mod gifs; pub(crate) mod help; +mod media; mod pain; mod ping; mod qalc; @@ -27,6 +27,6 @@ mod timezones; #[group] #[commands( - ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs, pain, clear + ping, stats, shutdown, time, timezones, qalc, about, add_media, media, pain, clear )] pub struct Misc; diff --git a/src/commands/misc/pain.rs b/src/commands/misc/pain.rs index 161b467..933186d 100644 --- a/src/commands/misc/pain.rs +++ b/src/commands/misc/pain.rs @@ -20,23 +20,23 @@ async fn pain(ctx: &Context, msg: &Message, args: Args) -> CommandResult { log::debug!("Got pain command"); let pain_type = args.message().to_lowercase(); let database = get_database_from_context(ctx).await; - let mut gifs = database - .get_gifs_by_category(format!("{}{}", CATEGORY_PREFIX, pain_type).as_str()) + let mut media = database + .get_media_by_category(format!("{}{}", CATEGORY_PREFIX, pain_type).as_str()) .await?; - if gifs.is_empty() { - log::debug!("No gif found for pain {}. Using 404", pain_type); - gifs = database - .get_gifs_by_category(format!("{}{}", CATEGORY_PREFIX, NOT_FOUND_PAIN).as_str()) + if media.is_empty() { + log::debug!("No media found for pain {}. Using 404", pain_type); + media = database + .get_media_by_category(format!("{}{}", CATEGORY_PREFIX, NOT_FOUND_PAIN).as_str()) .await?; } - let gif = gifs + let entry = media .into_iter() .choose(&mut rand::thread_rng()) .ok_or(BotError::from("No gifs found."))?; - log::trace!("Gif for pain is {:?}", gif); - msg.reply(ctx, gif.url).await?; + log::trace!("Gif for pain is {:?}", entry); + msg.reply(ctx, entry.url).await?; Ok(()) } diff --git a/src/commands/music/equalize.rs b/src/commands/music/equalize.rs new file mode 100644 index 0000000..c338984 --- /dev/null +++ b/src/commands/music/equalize.rs @@ -0,0 +1,62 @@ +use serenity::framework::standard::macros::command; +use serenity::framework::standard::{Args, CommandError, CommandResult}; +use serenity::model::channel::Message; +use serenity::prelude::*; + +use crate::commands::common::handle_autodelete; +use crate::commands::music::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; +use bot_serenityutils::core::{MEDIUM_TIMEOUT, SHORT_TIMEOUT}; +use bot_serenityutils::ephemeral_message::EphemeralMessage; + +#[command] +#[only_in(guilds)] +#[description("Loads an equalizer preset")] +#[usage("")] +#[num_args(1)] +#[example("bass")] +#[bucket("general")] +#[checks(DJ)] +async fn equalize(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { + let guild = msg.guild(&ctx.cache).await.unwrap(); + log::debug!("Changing equalizer for {}", guild.id); + let preset = args.single::().unwrap(); + + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; + + let bands = match preset.to_lowercase().as_str() { + "metal" => lavalink_rs::EQ_METAL, + "boost" => lavalink_rs::EQ_BOOST, + "base" => lavalink_rs::EQ_BASE, + "piano" => lavalink_rs::EQ_PIANO, + _ => { + EphemeralMessage::create(&ctx.http, msg.channel_id, MEDIUM_TIMEOUT, |m| { + m.content(format!( + "Unknown preset '{}'. Available are 'metal', 'boost', 'base' and 'piano'", + preset + )) + }) + .await?; + handle_autodelete(ctx, msg).await?; + return Ok(()); + } + }; + { + let mut player = player.lock().await; + player.equalize_all(bands).await?; + } + EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| { + m.content(format!("๐ŸŽ›๏ธ Changed equalizer to '{}'", preset)) + }) + .await?; + + handle_autodelete(ctx, msg).await?; + + Ok(()) +} diff --git a/src/commands/music/equalizer.rs b/src/commands/music/equalizer.rs new file mode 100644 index 0000000..3f68c36 --- /dev/null +++ b/src/commands/music/equalizer.rs @@ -0,0 +1,33 @@ +use serenity::framework::standard::macros::command; +use serenity::framework::standard::{CommandError, CommandResult}; +use serenity::model::channel::Message; +use serenity::prelude::*; + +use crate::commands::common::handle_autodelete; +use crate::commands::music::{get_music_player_for_guild, DJ_CHECK}; +use crate::messages::music::equalizer::create_equalizer_message; +use crate::messages::music::no_voicechannel::create_no_voicechannel_message; + +#[command] +#[only_in(guilds)] +#[description("Displays the equalizer for the music player")] +#[usage("")] +#[bucket("general")] +#[checks(DJ)] +async fn equalizer(ctx: &Context, msg: &Message) -> CommandResult { + let guild = msg.guild(&ctx.cache).await.unwrap(); + log::debug!("Displaying equalizer for guild {}", guild.id); + + let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await { + player + } else { + return create_no_voicechannel_message(&ctx.http, msg.channel_id) + .await + .map_err(CommandError::from); + }; + + create_equalizer_message(&ctx, msg.channel_id, player).await?; + handle_autodelete(ctx, msg).await?; + + Ok(()) +} diff --git a/src/commands/music/mod.rs b/src/commands/music/mod.rs index 1e011c2..63bb3ea 100644 --- a/src/commands/music/mod.rs +++ b/src/commands/music/mod.rs @@ -11,22 +11,6 @@ use serenity::model::user::User; use songbird::Songbird; use tokio::sync::Mutex; -use clear_queue::CLEAR_QUEUE_COMMAND; -use current::CURRENT_COMMAND; -use join::JOIN_COMMAND; -use leave::LEAVE_COMMAND; -use lyrics::LYRICS_COMMAND; -use move_song::MOVE_SONG_COMMAND; -use pause::PAUSE_COMMAND; -use play::PLAY_COMMAND; -use play_next::PLAY_NEXT_COMMAND; -use playlists::PLAYLISTS_COMMAND; -use queue::QUEUE_COMMAND; -use remove_song::REMOVE_SONG_COMMAND; -use save_playlist::SAVE_PLAYLIST_COMMAND; -use shuffle::SHUFFLE_COMMAND; -use skip::SKIP_COMMAND; - use crate::providers::music::player::MusicPlayer; use crate::providers::music::queue::Song; use crate::providers::music::{add_youtube_song_to_database, youtube_dl}; @@ -41,6 +25,8 @@ use youtube_metadata::get_video_information; mod clear_queue; mod current; +mod equalize; +mod equalizer; mod join; mod leave; mod lyrics; @@ -55,6 +41,24 @@ mod save_playlist; mod shuffle; mod skip; +use clear_queue::CLEAR_QUEUE_COMMAND; +use current::CURRENT_COMMAND; +use equalize::EQUALIZE_COMMAND; +use equalizer::EQUALIZER_COMMAND; +use join::JOIN_COMMAND; +use leave::LEAVE_COMMAND; +use lyrics::LYRICS_COMMAND; +use move_song::MOVE_SONG_COMMAND; +use pause::PAUSE_COMMAND; +use play::PLAY_COMMAND; +use play_next::PLAY_NEXT_COMMAND; +use playlists::PLAYLISTS_COMMAND; +use queue::QUEUE_COMMAND; +use remove_song::REMOVE_SONG_COMMAND; +use save_playlist::SAVE_PLAYLIST_COMMAND; +use shuffle::SHUFFLE_COMMAND; +use skip::SKIP_COMMAND; + #[group] #[commands( join, @@ -71,7 +75,9 @@ mod skip; playlists, lyrics, move_song, - remove_song + remove_song, + equalizer, + equalize )] pub struct Music; diff --git a/src/commands/weeb/fubuki.rs b/src/commands/weeb/fubuki.rs new file mode 100644 index 0000000..509de92 --- /dev/null +++ b/src/commands/weeb/fubuki.rs @@ -0,0 +1,14 @@ +use crate::commands::weeb::post_random_media; +use serenity::client::Context; +use serenity::framework::standard::macros::command; +use serenity::framework::standard::CommandResult; +use serenity::model::channel::Message; + +#[command] +#[description("Posts a random fubuki")] +#[usage("")] +#[aliases("scatman")] +#[bucket("general")] +async fn fubuki(ctx: &Context, msg: &Message) -> CommandResult { + post_random_media(ctx, msg, "fubuki").await +} diff --git a/src/commands/weeb/korone.rs b/src/commands/weeb/korone.rs new file mode 100644 index 0000000..182f463 --- /dev/null +++ b/src/commands/weeb/korone.rs @@ -0,0 +1,14 @@ +use crate::commands::weeb::post_random_media; +use serenity::client::Context; +use serenity::framework::standard::macros::command; +use serenity::framework::standard::CommandResult; +use serenity::model::channel::Message; + +#[command] +#[description("Posts a random korone gif")] +#[usage("")] +#[aliases("yubi")] +#[bucket("general")] +async fn korone(ctx: &Context, msg: &Message) -> CommandResult { + post_random_media(ctx, msg, "korone").await +} diff --git a/src/commands/weeb/matsuri.rs b/src/commands/weeb/matsuri.rs index 0ad487f..1aeee57 100644 --- a/src/commands/weeb/matsuri.rs +++ b/src/commands/weeb/matsuri.rs @@ -1,26 +1,13 @@ -use crate::utils::context_data::get_database_from_context; -use crate::utils::error::BotError; -use rand::prelude::IteratorRandom; +use crate::commands::weeb::post_random_media; use serenity::client::Context; use serenity::framework::standard::macros::command; use serenity::framework::standard::CommandResult; use serenity::model::channel::Message; -static GIF_CATEGORY: &str = "matsuri"; - #[command] #[description("Posts a random matsuri gif")] #[usage("")] #[bucket("general")] async fn matsuri(ctx: &Context, msg: &Message) -> CommandResult { - let database = get_database_from_context(ctx).await; - let gifs = database.get_gifs_by_category(GIF_CATEGORY).await?; - let gif = gifs - .into_iter() - .choose(&mut rand::thread_rng()) - .ok_or(BotError::from("No gifs found."))?; - - msg.channel_id.say(ctx, gif.url).await?; - - Ok(()) + post_random_media(ctx, msg, "matsuri").await } diff --git a/src/commands/weeb/miko.rs b/src/commands/weeb/miko.rs new file mode 100644 index 0000000..7244470 --- /dev/null +++ b/src/commands/weeb/miko.rs @@ -0,0 +1,14 @@ +use crate::commands::weeb::post_random_media; +use serenity::client::Context; +use serenity::framework::standard::macros::command; +use serenity::framework::standard::CommandResult; +use serenity::model::channel::Message; + +#[command] +#[description("Posts a random miko")] +#[usage("")] +#[aliases("faq", "elite")] +#[bucket("general")] +async fn miko(ctx: &Context, msg: &Message) -> CommandResult { + post_random_media(ctx, msg, "miko").await +} diff --git a/src/commands/weeb/mod.rs b/src/commands/weeb/mod.rs index 59e6032..de75e24 100644 --- a/src/commands/weeb/mod.rs +++ b/src/commands/weeb/mod.rs @@ -1,13 +1,42 @@ use serenity::framework::standard::macros::group; +mod fubuki; +mod korone; mod matsuri; +mod miko; mod pekofy; +mod rushia; mod sauce; +use crate::utils::context_data::get_database_from_context; +use crate::utils::error::BotError; +use rand::prelude::IteratorRandom; +use serenity::client::Context; +use serenity::framework::standard::CommandResult; +use serenity::model::channel::Message; + +use fubuki::FUBUKI_COMMAND; +use korone::KORONE_COMMAND; use matsuri::MATSURI_COMMAND; +use miko::MIKO_COMMAND; use pekofy::PEKOFY_COMMAND; +use rushia::RUSHIA_COMMAND; use sauce::SAUCE_COMMAND; #[group] -#[commands(pekofy, sauce, matsuri)] +#[commands(pekofy, sauce, matsuri, korone, rushia, fubuki, miko)] pub struct Weeb; + +/// Posts a random media entry with the given category +async fn post_random_media(ctx: &Context, msg: &Message, category: &str) -> CommandResult { + let database = get_database_from_context(ctx).await; + let media = database.get_media_by_category(category).await?; + let gif = media + .into_iter() + .choose(&mut rand::thread_rng()) + .ok_or(BotError::from("No media found."))?; + + msg.channel_id.say(ctx, gif.url).await?; + + Ok(()) +} diff --git a/src/commands/weeb/pekofy.rs b/src/commands/weeb/pekofy.rs index 5e1053d..9722ce5 100644 --- a/src/commands/weeb/pekofy.rs +++ b/src/commands/weeb/pekofy.rs @@ -7,7 +7,7 @@ use serenity::{framework::standard::macros::command, prelude::*}; use crate::utils::context_data::get_database_from_context; use crate::utils::error::{BotError, BotResult}; use crate::utils::get_previous_message_or_reply; -use bot_database::models::Gif; +use bot_database::models::Media; // return a normal peko in most cases static PEKOS: &[&str] = &[ @@ -18,7 +18,7 @@ static PEKOS: &[&str] = &[ "๐Ÿ‡ต ๐Ÿ‡ช ๐Ÿ‡ฐ ๐Ÿ‡ด", "p3k0", ]; -static GIF_CATEGORY: &str = "pain-peko"; +static MEDIA_CATEGORY: &str = "pain-peko"; #[command] #[description("Pekofy messages")] @@ -48,7 +48,7 @@ async fn pekofy(ctx: &Context, msg: &Message, args: Args) -> CommandResult { alpha_lowercase.retain(|c| c.is_alphanumeric()); let pekofied: String = if alpha_lowercase == "pain" { - random_pain_gif(ctx).await?.url + random_pain_media(ctx).await?.url } else if PEKOS.contains(&&*alpha_lowercase) { random_peko() } else { @@ -114,10 +114,10 @@ fn random_peko() -> String { } /// Chooses a random pain peko gif -async fn random_pain_gif(ctx: &Context) -> BotResult { +async fn random_pain_media(ctx: &Context) -> BotResult { let database = get_database_from_context(ctx).await; - let gifs = database.get_gifs_by_category(GIF_CATEGORY).await?; + let gifs = database.get_media_by_category(MEDIA_CATEGORY).await?; gifs.into_iter() .choose(&mut rand::thread_rng()) - .ok_or(BotError::from("No gifs found")) + .ok_or(BotError::from("No media found")) } diff --git a/src/commands/weeb/rushia.rs b/src/commands/weeb/rushia.rs new file mode 100644 index 0000000..f6ae14d --- /dev/null +++ b/src/commands/weeb/rushia.rs @@ -0,0 +1,14 @@ +use crate::commands::weeb::post_random_media; +use serenity::client::Context; +use serenity::framework::standard::macros::command; +use serenity::framework::standard::CommandResult; +use serenity::model::channel::Message; + +#[command] +#[description("Posts a random rushia")] +#[usage("")] +#[aliases("cuttingboard", "cutting-board", "petan")] +#[bucket("general")] +async fn rushia(ctx: &Context, msg: &Message) -> CommandResult { + post_random_media(ctx, msg, "rushia").await +} diff --git a/src/messages/gifs.rs b/src/messages/gifs.rs index 788f91d..d74a4ea 100644 --- a/src/messages/gifs.rs +++ b/src/messages/gifs.rs @@ -1,5 +1,5 @@ use crate::utils::error::BotResult; -use bot_database::models::Gif; +use bot_database::models::Media; use bot_serenityutils::menu::{MenuBuilder, Page}; use serenity::builder::CreateMessage; use serenity::client::Context; @@ -7,16 +7,16 @@ use serenity::model::id::ChannelId; use std::time::Duration; /// Creates a new gifs embed -pub async fn create_gifs_menu( +pub async fn create_media_menu( ctx: &Context, channel_id: ChannelId, - gifs: Vec, + media: Vec, ) -> BotResult<()> { - let total_pages = (gifs.len() as f32 / 10.0).ceil() as usize; - let pages: Vec = gifs + let total_pages = (media.len() as f32 / 10.0).ceil() as usize; + let pages: Vec = media .chunks(10) .enumerate() - .map(|(page, gifs)| create_gifs_page(page + 1, total_pages, gifs.to_vec())) + .map(|(page, media)| create_media_page(page + 1, total_pages, media.to_vec())) .collect(); MenuBuilder::new_paginator() @@ -30,21 +30,21 @@ pub async fn create_gifs_menu( } /// Creates a new gif page -pub fn create_gifs_page(page: usize, total_pages: usize, gifs: Vec) -> Page<'static> { +pub fn create_media_page(page: usize, total_pages: usize, media: Vec) -> Page<'static> { let mut message = CreateMessage::default(); - let description_lines: Vec = gifs + let description_lines: Vec = media .into_iter() - .map(|g| { + .map(|m| { format!( "{} - {} - [Source]({})", - g.category.unwrap_or("*N/A*".to_string()), - g.name.unwrap_or("*N/A*".to_string()), - g.url + m.category.unwrap_or("*N/A*".to_string()), + m.name.unwrap_or("*N/A*".to_string()), + m.url ) }) .collect(); message.embed(|e| { - e.title("Gifs") + e.title("Media") .description(description_lines.join("\n")) .footer(|f| f.text(format!("Page {} of {}", page, total_pages))) }); diff --git a/src/messages/music/equalizer.rs b/src/messages/music/equalizer.rs new file mode 100644 index 0000000..9fc6bae --- /dev/null +++ b/src/messages/music/equalizer.rs @@ -0,0 +1,262 @@ +use crate::commands::music::is_dj; +use crate::providers::music::player::MusicPlayer; +use crate::utils::error::BotResult; +use bot_serenityutils::core::EXTRA_LONG_TIMEOUT; +use bot_serenityutils::error::SerenityUtilsResult; +use bot_serenityutils::menu::{display_page, Menu, MenuBuilder, Page}; +use serenity::builder::{CreateEmbed, CreateMessage}; +use serenity::client::Context; +use serenity::model::channel::Reaction; +use serenity::model::id::ChannelId; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::Arc; +use tokio::sync::Mutex; +use typemap_rev::TypeMapKey; + +static DELETE_BUTTON: &str = "๐Ÿ—‘๏ธ"; +static NEXT_BAND_BUTTON: &str = "โžก๏ธ"; +static PREVIOUS_BAND_BUTTON: &str = "โฌ…๏ธ"; +static ADD_BUTTON: &str = "โž•"; +static SUB_BUTTON: &str = "โž–"; + +struct SelectedBand; + +impl TypeMapKey for SelectedBand { + type Value = Arc; +} + +struct Player; + +impl TypeMapKey for Player { + type Value = Arc>; +} + +/// Creates a new equalizer message +pub async fn create_equalizer_message( + ctx: &Context, + channel_id: ChannelId, + player: Arc>, +) -> BotResult<()> { + let selected_band = Arc::new(AtomicU8::new(0)); + let selected_band_clone = Arc::clone(&selected_band); + let player_clone = Arc::clone(&player); + + MenuBuilder::default() + .add_page(Page::new_builder(move || { + let player = Arc::clone(&player_clone); + let selected_band = Arc::clone(&selected_band_clone); + Box::pin(async move { + let mut page = CreateMessage::default(); + let mut embed = CreateEmbed::default(); + create_equalizer_embed(selected_band.load(Ordering::Relaxed), &mut embed, &player) + .await; + + page.embed(|e| { + e.0.clone_from(&embed.0); + e + }); + + Ok(page) + }) + })) + .add_control(-1, DELETE_BUTTON, |c, m, r| Box::pin(delete_menu(c, m, r))) + .add_help(DELETE_BUTTON, "Deletes this message.") + .add_control(0, PREVIOUS_BAND_BUTTON, |c, m, r| { + Box::pin(previous_band(c, m, r)) + }) + .add_help(PREVIOUS_BAND_BUTTON, "Selects the previous band.") + .add_control(1, NEXT_BAND_BUTTON, |c, m, r| Box::pin(next_band(c, m, r))) + .add_help(NEXT_BAND_BUTTON, "Selects the next band.") + .add_control(3, ADD_BUTTON, |c, m, r| Box::pin(add_to_band(c, m, r))) + .add_help(ADD_BUTTON, "Adds to the selected band.") + .add_control(2, SUB_BUTTON, |c, m, r| { + Box::pin(subtract_from_band(c, m, r)) + }) + .add_help(SUB_BUTTON, "Subtracts from the selected band") + .show_help() + .add_data::(selected_band) + .add_data::(player) + .timeout(EXTRA_LONG_TIMEOUT) + .build(ctx, channel_id) + .await?; + Ok(()) +} + +/// Creates a new equalizer embed +async fn create_equalizer_embed<'a>( + selected_band: u8, + embed: &'a mut CreateEmbed, + player: &Arc>, +) -> &'a mut CreateEmbed { + let mut description = String::new(); + let bands = { + let player = player.lock().await; + player.get_equalizer().clone() + }; + for i in 0..bands.len() { + if i as u8 == selected_band { + description += "โค‹" + } else { + description += " "; + } + } + description += "\n"; + for i in (0..11).rev() { + let eq_value = (i as f64) / 20.0 - 0.25; + + for band in &bands { + if (eq_value > 0. && band >= &eq_value) || (eq_value < 0. && band <= &eq_value) { + description += "โ–ˆ"; + } else if eq_value == 0. { + description += format!("-").as_str(); + } else { + description += " "; + } + } + description += "\n"; + } + for i in 0..bands.len() { + if i as u8 == selected_band { + description += "โคŠ" + } else { + description += " "; + } + } + embed + .title("Equalizer") + .description(format!("```\n{}\n```", description)); + + embed +} + +/// Selects the previous band +async fn next_band( + ctx: &Context, + menu: &mut Menu<'_>, + reaction: Reaction, +) -> SerenityUtilsResult<()> { + let guild_id = reaction.guild_id.unwrap(); + let user = reaction.user(&ctx).await?; + + if !is_dj(ctx, guild_id, &user).await? { + return Ok(()); + } + let selected_band = menu.data.get::().unwrap(); + if selected_band.load(Ordering::SeqCst) >= 14 { + selected_band.store(0, Ordering::SeqCst); + } else { + selected_band.fetch_add(1, Ordering::SeqCst); + } + display_page(ctx, menu).await?; + + Ok(()) +} + +/// Selects the previous band +async fn previous_band( + ctx: &Context, + menu: &mut Menu<'_>, + reaction: Reaction, +) -> SerenityUtilsResult<()> { + let guild_id = reaction.guild_id.unwrap(); + let user = reaction.user(&ctx).await?; + + if !is_dj(ctx, guild_id, &user).await? { + return Ok(()); + } + let selected_band = menu.data.get::().unwrap(); + if selected_band.load(Ordering::SeqCst) <= 0 { + selected_band.store(14, Ordering::SeqCst); + } else { + selected_band.fetch_sub(1, Ordering::SeqCst); + } + display_page(ctx, menu).await?; + + Ok(()) +} + +/// Adds to the selected band +async fn add_to_band( + ctx: &Context, + menu: &mut Menu<'_>, + reaction: Reaction, +) -> SerenityUtilsResult<()> { + let guild_id = reaction.guild_id.unwrap(); + let user = reaction.user(&ctx).await?; + + if !is_dj(ctx, guild_id, &user).await? { + return Ok(()); + } + { + let selected_band = menu + .data + .get::() + .unwrap() + .load(Ordering::Relaxed); + let player = menu.data.get::().unwrap(); + let mut player = player.lock().await; + let equalizer = player.get_equalizer(); + let current_value = equalizer[selected_band as usize]; + + if current_value < 0.25 { + player.equalize(selected_band, current_value + 0.05).await?; + } + } + + display_page(ctx, menu).await?; + + Ok(()) +} + +/// Substracts from the selected band +async fn subtract_from_band( + ctx: &Context, + menu: &mut Menu<'_>, + reaction: Reaction, +) -> SerenityUtilsResult<()> { + let guild_id = reaction.guild_id.unwrap(); + let user = reaction.user(&ctx).await?; + + if !is_dj(ctx, guild_id, &user).await? { + return Ok(()); + } + { + let selected_band = menu + .data + .get::() + .unwrap() + .load(Ordering::Relaxed); + let player = menu.data.get::().unwrap(); + let mut player = player.lock().await; + let equalizer = player.get_equalizer(); + let current_value = equalizer[selected_band as usize]; + + if current_value > -0.25 { + player.equalize(selected_band, current_value - 0.05).await?; + } + } + + display_page(ctx, menu).await?; + + Ok(()) +} + +/// Deletes the menu +async fn delete_menu( + ctx: &Context, + menu: &mut Menu<'_>, + reaction: Reaction, +) -> SerenityUtilsResult<()> { + let guild_id = reaction.guild_id.unwrap(); + let user = reaction.user(&ctx).await?; + + if !is_dj(ctx, guild_id, &user).await? { + return Ok(()); + } + let handle = menu.message.read().await; + ctx.http + .delete_message(handle.channel_id, handle.message_id) + .await?; + + Ok(()) +} diff --git a/src/messages/music/mod.rs b/src/messages/music/mod.rs index 0a2d747..9d0953a 100644 --- a/src/messages/music/mod.rs +++ b/src/messages/music/mod.rs @@ -1,3 +1,4 @@ +pub mod equalizer; pub mod no_voicechannel; pub mod now_playing; pub mod queue; diff --git a/src/providers/music/player.rs b/src/providers/music/player.rs index e4b6945..5ae9c1e 100644 --- a/src/providers/music/player.rs +++ b/src/providers/music/player.rs @@ -3,7 +3,7 @@ use crate::providers::music::lavalink::Lavalink; use crate::providers::music::lyrics::get_lyrics; use crate::providers::music::queue::MusicQueue; use crate::utils::context_data::MusicPlayers; -use crate::utils::error::BotResult; +use crate::utils::error::{BotError, BotResult}; use bot_serenityutils::core::{MessageHandle, SHORT_TIMEOUT}; use bot_serenityutils::ephemeral_message::EphemeralMessage; use lavalink_rs::LavalinkClient; @@ -28,6 +28,7 @@ pub struct MusicPlayer { msg_channel: ChannelId, leave_flag: bool, paused: bool, + equalizer: [f64; 15], } impl MusicPlayer { @@ -47,6 +48,7 @@ impl MusicPlayer { now_playing_msg: None, leave_flag: false, paused: false, + equalizer: [0f64; 15], } } @@ -120,6 +122,9 @@ impl MusicPlayer { /// Plays the next song in the queue pub async fn play_next(&mut self) -> BotResult<()> { while !self.try_play_next().await? {} + if self.paused { + self.client.pause(self.guild_id).await?; + } Ok(()) } @@ -232,6 +237,37 @@ impl MusicPlayer { Ok(()) } + + /// Returns the equalizer + pub fn get_equalizer(&self) -> &[f64; 15] { + &self.equalizer + } + + /// Equalizes a specified band + pub async fn equalize(&mut self, band: u8, value: f64) -> BotResult<()> { + if band > 15 { + return Err(BotError::from("Invalid Equalizer band")); + } + if value < -0.25 || value > 0.25 { + return Err(BotError::from("Invalid Equalizer value")); + } + self.equalizer[band as usize] = value; + self.client + .equalize_all(self.guild_id, self.equalizer) + .await?; + + Ok(()) + } + + /// Equalizes all bands at the same time + pub async fn equalize_all(&mut self, bands: [f64; 15]) -> BotResult<()> { + self.equalizer = bands; + self.client + .equalize_all(self.guild_id, self.equalizer) + .await?; + + Ok(()) + } } /// Stats a tokio coroutine to check for player disconnect conditions