diff options
| -rw-r--r-- | Cargo.lock | 25 | ||||
| -rw-r--r-- | Cargo.toml | 3 | ||||
| -rw-r--r-- | src/bot.rs | 11 | ||||
| -rw-r--r-- | src/commands/game.rs | 14 | ||||
| -rw-r--r-- | src/commands/meme/history.rs | 33 | ||||
| -rw-r--r-- | src/commands/meme/invoke.rs | 23 | ||||
| -rw-r--r-- | src/commands/meme/mod.rs | 16 | ||||
| -rw-r--r-- | src/commands/mod.rs | 10 | ||||
| -rw-r--r-- | src/commands/playback.rs | 12 | ||||
| -rw-r--r-- | src/commands/sound_levels.rs | 71 | ||||
| -rw-r--r-- | src/commands/today/mod.rs | 7 | ||||
| -rw-r--r-- | src/config.rs | 6 | ||||
| -rw-r--r-- | src/db/manual_migrate.rs | 14 | ||||
| -rw-r--r-- | src/db/mod.rs | 89 | ||||
| -rw-r--r-- | src/db/models.rs | 1 | ||||
| -rw-r--r-- | src/util/mod.rs | 31 |
16 files changed, 228 insertions, 138 deletions
@@ -505,6 +505,20 @@ dependencies = [ ] [[package]] +name = "dashmap" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] name = "data-encoding" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1335,7 +1349,7 @@ checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" dependencies = [ "crossbeam-channel", "crossbeam-utils", - "dashmap", + "dashmap 5.5.3", "skeptic", "smallvec", "tagptr", @@ -2433,7 +2447,7 @@ dependencies = [ "bytes", "chrono", "command_attr", - "dashmap", + "dashmap 5.5.3", "flate2", "futures", "fxhash", @@ -2579,7 +2593,7 @@ dependencies = [ "audiopus", "byteorder", "crypto_secretbox", - "dashmap", + "dashmap 5.5.3", "derivative", "discortp", "flume", @@ -2987,12 +3001,13 @@ dependencies = [ [[package]] name = "thulani" -version = "0.3.1" +version = "0.4.0" dependencies = [ "anyhow", "bytemuck", "chrono", "clap", + "dashmap 6.0.1", "deadpool-postgres", "diesel", "diesel-async", @@ -3446,7 +3461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f" dependencies = [ "chrono", - "dashmap", + "dashmap 5.5.3", "hashbrown", "mini-moka", "parking_lot", @@ -9,7 +9,7 @@ edition = "2021" [package] name = "thulani" -version = "0.3.1" +version = "0.4.0" edition.workspace = true authors.workspace = true default-run = "thulani" @@ -55,6 +55,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" timeago = "0.4" fnv = "1.0" +dashmap = "6.0" clap = { version = "4.5", features = ["derive"] } @@ -9,6 +9,7 @@ use std::{ }; use chrono::Datelike; +use dashmap::DashMap; use fnv::{ FnvHashMap, FnvHashSet, @@ -24,7 +25,6 @@ use serenity::{ all::{ Guild, GuildId, - PartialGuild, ReactionType, }, builder::CreateMessage, @@ -63,6 +63,12 @@ impl TypeMapKey for HttpKey { type Value = reqwest::Client; } +pub struct VolumeKey; + +impl TypeMapKey for VolumeKey { + type Value = DashMap<GuildId, f64>; +} + #[cfg(debug_assertions)] const BOTNAME: &str = "thulani (dev)"; @@ -96,7 +102,7 @@ impl EventHandler for Handler { } async fn guild_create(&self, ctx: Context, guild: Guild, _is_new: Option<bool>) { - tracing::info!(guild_id = %guild.id, guild_name = %guild.name, "received guild_create"); + tracing::info!(disc_event = "guild_create", guild_id = %guild.id, guild_name = %guild.name); ready_guild(&ctx, guild.id).await; } @@ -409,6 +415,7 @@ pub async fn run() -> anyhow::Result<()> { .event_handler(Handler) .register_songbird_from_config(sb_config) .type_map_insert::<HttpKey>(reqwest::Client::new()) + .type_map_insert::<VolumeKey>(DashMap::new()) .framework(framework().await) .await?; diff --git a/src/commands/game.rs b/src/commands/game.rs index 78c08ee..3a47b32 100644 --- a/src/commands/game.rs +++ b/src/commands/game.rs @@ -307,14 +307,6 @@ async fn _game( users }; - let inferred = users.is_empty(); - - if inferred && users.len() < 2 || !inferred && users.is_empty() { - tracing::info!("too few known users to make game comparison"); - util::reply(ctx, "yer too lonely").await?; - return Ok(()); - } - let client = { let data = ctx.serenity_context().data.read().await; data.get::<HttpKey>().unwrap().clone() @@ -333,6 +325,12 @@ async fn _game( }) .collect::<FnvHashMap<_, _>>(); + if user_indexes.len() < 2 { + tracing::info!("too few known users to make game comparison"); + util::reply(ctx, "yer too lonely").await?; + return Ok(()); + } + let data_ref = &data; let user_games = user_indexes .iter() diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs index 1acb019..270ae9a 100644 --- a/src/commands/meme/history.rs +++ b/src/commands/meme/history.rs @@ -1,16 +1,3 @@ -use crate::{ - commands::game::get_user_id, - db::{ - self, - connection, - InvocationRecord, - Meme, - Metadata, - }, - util, - PoiseContext, - CONFIG, -}; use chrono::TimeZone; use diesel::{ result::Error as DieselError, @@ -34,7 +21,20 @@ use timeago::{ Formatter, TimeUnit, }; -use windows::core::s; + +use crate::{ + commands::game::get_user_id, + config::CONFIG, + db::{ + self, + connection, + InvocationRecord, + Meme, + Metadata, + }, + util, + PoiseContext, +}; lazy_static! { static ref TIME_FORMATTER: Formatter = { @@ -393,10 +393,7 @@ pub async fn query(ctx: PoiseContext<'_>, rest: util::RestVec) -> anyhow::Result use regex::Regex; use serenity::model::id::UserId; - use crate::{ - db, - CONFIG, - }; + use crate::db; lazy_static! { static ref CREATOR_REGEX: Regex = Regex::new(r"(?i)(?:by|creator)=(.*)").unwrap(); diff --git a/src/commands/meme/invoke.rs b/src/commands/meme/invoke.rs index aff5c23..4b361a3 100644 --- a/src/commands/meme/invoke.rs +++ b/src/commands/meme/invoke.rs @@ -1,6 +1,5 @@ use diesel::{ result::Error as DieselError, - row::NamedRow, NotFound, }; use grate::tracing; @@ -93,10 +92,10 @@ pub(crate) async fn _meme( x.id, false, ) - .await?; + .await?; x - } + }, Err(e) => { return if let Some(NotFound) = e.downcast_ref::<DieselError>() { tracing::info!("requested meme not found in database"); @@ -107,7 +106,7 @@ pub(crate) async fn _meme( util::reply(ctx, "what in ryan's name").await?; Err(e.into()) }; - } + }, }; send_meme(ctx, &mem, &mut conn).await @@ -135,15 +134,15 @@ async fn rand_meme(ctx: PoiseContext<'_>, audio_playback: AudioPlayback) -> anyh mem.id, true, ) - .await?; + .await?; send_meme(ctx, &mem, &mut conn).await?; Ok(()) - } + }, Ok(None) => { tracing::info!("random meme not found"); util::reply(ctx, "i don't know any :(").await?; Ok(()) - } + }, Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { tracing::info!("random meme not found"); @@ -154,7 +153,7 @@ async fn rand_meme(ctx: PoiseContext<'_>, audio_playback: AudioPlayback) -> anyh util::reply(ctx, "HELP").await?; Err(e.into()) - } + }, } } @@ -177,16 +176,16 @@ pub async fn rare_meme(ctx: PoiseContext<'_>) -> anyhow::Result<()> { meme.id, true, ) - .await?; + .await?; send_meme(ctx, &meme, &mut conn).await - } + }, Ok(None) => { tracing::info!("rare meme not found"); util::reply(ctx, "i don't know any :(").await?; Ok(()) - } + }, Err(e) => { if let Some(NotFound) = e.downcast_ref::<DieselError>() { @@ -199,6 +198,6 @@ pub async fn rare_meme(ctx: PoiseContext<'_>) -> anyhow::Result<()> { util::reply(ctx, "THE MEME MARKET IS IN FREEFALL").await?; Err(e.into()) - } + }, } } diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs index eb6aa1d..3883f3e 100644 --- a/src/commands/meme/mod.rs +++ b/src/commands/meme/mod.rs @@ -1,3 +1,8 @@ +use std::{ + borrow::ToOwned, + default::Default, +}; + use diesel_async::AsyncPgConnection; use grate::tracing; use rand::random; @@ -19,10 +24,6 @@ use songbird::input::{ Compose, Input, }; -use std::{ - borrow::ToOwned, - default::Default, -}; pub use self::{ create::*, @@ -38,7 +39,6 @@ use crate::{ }, util, PoiseContext, - CONFIG, }; mod create; @@ -113,6 +113,9 @@ async fn send_meme( return Ok(()); }; + let volume = util::volume(ctx).await; + tracing::debug!(volume); + { let (_sb, call) = songbird(ctx).await?; let mut call = call.lock().await; @@ -121,7 +124,8 @@ async fn send_meme( call.join(voice_channel).await?; } - call.enqueue_input(Input::Lazy(Box::new(audio))).await; + let handle = call.enqueue_input(Input::Lazy(Box::new(audio))).await; + handle.set_volume(volume as _)?; } util::react(ctx, ReactionType::Unicode("📣".to_owned())).await?; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2729580..b0ef83b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -19,8 +19,14 @@ pub(crate) mod today; pub use self::meme::*; pub fn commands() -> Vec<poise::Command<crate::PoiseData, anyhow::Error>> { - let mut commands = - vec![sound_levels::mute(), sound_levels::unmute(), roll::roll(), today::today(), help()]; + let mut commands = vec![ + sound_levels::mute(), + sound_levels::unmute(), + sound_levels::volume(), + roll::roll(), + today::today(), + help(), + ]; commands.extend(playback::commands()); diff --git a/src/commands/playback.rs b/src/commands/playback.rs index 8121136..4d6d0be 100644 --- a/src/commands/playback.rs +++ b/src/commands/playback.rs @@ -14,7 +14,6 @@ use crate::{ util, PoiseContext, PoiseData, - CONFIG, }; pub fn commands() -> impl IntoIterator<Item = poise::Command<PoiseData, anyhow::Error>> { @@ -74,6 +73,9 @@ pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> data.get::<HttpKey>().unwrap().clone() }; + let volume = util::volume(ctx).await; + tracing::debug!(volume); + { let (_sb, call) = songbird(ctx).await?; let mut call = call.lock().await; @@ -86,9 +88,11 @@ pub async fn _play(ctx: PoiseContext<'_>, url: &url::Url) -> anyhow::Result<()> YoutubeDl::new_ytdl_like(&crate::config::YTDL_COMMAND, client.clone(), url.to_string()); let track = input.conv::<songbird::tracks::Track>(); + // TODO: store enqueueing channel so songbird handler can switch channels - call.enqueue(track).await; + let queued = call.enqueue(track).await; + queued.set_volume(volume as _)?; } util::react(ctx, '📣').await?; @@ -197,7 +201,9 @@ pub async fn list(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let call = call.lock().await; let queue = call.queue(); - util::reply(ctx, "(command fix work-in-progress)").await?; + if queue.current_queue().is_empty() { + util::reply(ctx, "nothing queued").await?; + } for track in queue.current_queue().into_iter() { let info = track.get_info().await?; diff --git a/src/commands/sound_levels.rs b/src/commands/sound_levels.rs index 4946f47..2b9048e 100644 --- a/src/commands/sound_levels.rs +++ b/src/commands/sound_levels.rs @@ -1,10 +1,18 @@ +use std::error::Error; + +use serenity::all::{ + Context, + Message, +}; + use crate::{ commands::playback::songbird, + util, PoiseContext, }; /// Mute audio (don't pause). -#[poise::command(prefix_command, guild_only)] +#[poise::command(prefix_command, guild_only, category = "playback")] pub async fn mute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -15,7 +23,7 @@ pub async fn mute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { } /// Unmute audio. -#[poise::command(prefix_command, guild_only)] +#[poise::command(prefix_command, guild_only, category = "playback")] pub async fn unmute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { let (_sb, call) = songbird(ctx).await?; @@ -24,3 +32,62 @@ pub async fn unmute(ctx: PoiseContext<'_>) -> anyhow::Result<()> { Ok(()) } + +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)] +struct PercentArg(f64); + +lazy_static::lazy_static! { + static ref ARG_PCT: regex::Regex = regex::Regex::new(r#"(\d+(?:\.\d+)?)\s*%?(.*)"#).unwrap(); +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, thiserror::Error)] +#[error("expected a number with an optional trailing %")] +struct NonPctError; + +#[poise::async_trait] +impl<'a> poise::PopArgument<'a> for PercentArg { + async fn pop_from( + args: &'a str, + attachment_index: usize, + _ctx: &Context, + _msg: &Message, + ) -> Result<(&'a str, usize, Self), (Box<dyn Error + Send + Sync>, Option<String>)> { + let Some(mtch) = ARG_PCT.captures(args) else { + return Err((Box::new(NonPctError), None)); + }; + + let pct = mtch.get(1).unwrap().as_str().parse::<f64>().unwrap(); + let prop = pct / 100.; + let rest = mtch.get(2).unwrap().as_str(); + + Ok((rest, attachment_index, Self(prop))) + } +} + +/// Set volume by percent. +#[poise::command(prefix_command, guild_only, category = "playback")] +pub async fn volume(ctx: PoiseContext<'_>, volume: Option<PercentArg>) -> anyhow::Result<()> { + let Some(volume) = volume else { + let cur_vol = util::volume(ctx).await * 100.; + + util::reply(ctx, format!("{cur_vol:.0}%")).await?; + return Ok(()); + }; + + { + let data = ctx.serenity_context().data.read().await; + let vol = data.get::<crate::bot::VolumeKey>().unwrap(); + vol.insert(util::guild_id(ctx)?, volume.0); + } + + let (_sb, call) = songbird(ctx).await?; + let call = call.lock().await; + + call.queue().modify_queue(|q| { + for elt in q { + elt.set_volume(volume.0 as _)?; + } + + anyhow::Ok(()) + }) +} diff --git a/src/commands/today/mod.rs b/src/commands/today/mod.rs index e48983c..f8f38c7 100644 --- a/src/commands/today/mod.rs +++ b/src/commands/today/mod.rs @@ -13,7 +13,6 @@ use crate::{ commands::playback::songbird, util, PoiseContext, - CONFIG, }; mod prelude; @@ -103,6 +102,9 @@ pub async fn today(ctx: PoiseContext<'_>, #[rest] _rest: Option<String>) -> anyh return Ok(()); }; + let volume = util::volume(ctx).await; + tracing::debug!(volume); + let (_sb, call) = songbird(ctx).await?; let mut call = call.lock().await; @@ -118,7 +120,8 @@ pub async fn today(ctx: PoiseContext<'_>, #[rest] _rest: Option<String>) -> anyh let input = YoutubeDl::new_ytdl_like("yt-dlp", client.clone(), play_args.url.conv::<String>()); - call.enqueue_input(input.into()).await; + let handle = call.enqueue_input(input.into()).await; + handle.set_volume(volume as _)?; let q = call.queue(); q.pause()?; diff --git a/src/config.rs b/src/config.rs index fae5a23..ea75cef 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,11 +4,7 @@ use dotenv::dotenv; use envconfig::Envconfig; use grate::tracing; use lazy_static::lazy_static; -use serenity::model::id::{ - ChannelId, - GuildId, - UserId, -}; +use serenity::model::id::UserId; lazy_static! { pub static ref CONFIG: Config = { diff --git a/src/db/manual_migrate.rs b/src/db/manual_migrate.rs index 851f0d8..b3e8a9c 100644 --- a/src/db/manual_migrate.rs +++ b/src/db/manual_migrate.rs @@ -1,17 +1,5 @@ -use crate::{ - db::{ - do_migrate, - schema::*, - POOL, - }, - guild_id, -}; -use anyhow::anyhow; use diesel::{ - associations::HasTable, - backend::Backend, pg::Pg, - query_builder::AsQuery, ExpressionMethods, }; use diesel_async::{ @@ -21,6 +9,8 @@ use diesel_async::{ RunQueryDsl, }; +use crate::db::schema::*; + #[inline] pub async fn connection_no_migrate() -> anyhow::Result<diesel_async::pooled_connection::deadpool::Object<AsyncPgConnection>> { diff --git a/src/db/mod.rs b/src/db/mod.rs index 5f19d54..3362e33 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,6 +1,12 @@ -pub use self::models::*; -use self::schema::*; -use crate::db::schema::memes::title; +use std::{ + convert::{ + AsRef, + From, + }, + env, + str::FromStr, +}; + use anyhow::{ anyhow, Error, @@ -19,7 +25,6 @@ use deadpool_postgres::{ }; use diesel::{ prelude::*, - row::NamedRow, BoolExpressionMethods, ExpressionMethods, NotFound, @@ -37,22 +42,18 @@ use diesel_async::{ RunQueryDsl, }; use grate::tracing; -use rand::Rng; use serenity::FutureExt; -use std::{ - convert::{ - AsRef, - From, - }, - env, - str::FromStr, -}; use tokio_postgres::types::FromSql; +use self::schema::*; + pub mod manual_migrate; + mod models; mod schema; +pub use self::models::*; + static MIGRATIONS: diesel_async_migrations::EmbeddedMigrations = diesel_async_migrations::embed_migrations!(); @@ -82,7 +83,7 @@ lazy_static::lazy_static! { #[inline] pub async fn connection() - -> Result<diesel_async::pooled_connection::deadpool::Object<AsyncPgConnection>> { +-> Result<diesel_async::pooled_connection::deadpool::Object<AsyncPgConnection>> { POOL.get() .then(|mut conn| async move { if let Ok(ref mut conn) = conn { @@ -194,8 +195,8 @@ pub async fn query_meme<T: AsRef<str>>( }; let metadata = Metadata { - id: row.get(5), - created: row.get(6), + id: row.get(5), + created: row.get(6), created_by: row.get(7), }; @@ -294,19 +295,19 @@ pub async fn delete_meme<T: AsRef<str>>( } let tombstone = NewTombstone { - guild: guild as i64, - deleted_by: deleted_by as i64, + guild: guild as i64, + deleted_by: deleted_by as i64, metadata_id: deleted.metadata_id, - meme_id: deleted.id, + meme_id: deleted.id, }; let _ = diesel::insert_into(tombstones::table).values(&tombstone).execute(tx).await?; Ok(()) } - .scope_boxed() + .scope_boxed() }) - .await + .await } pub async fn rare_meme( @@ -472,32 +473,32 @@ pub async fn rand_silent_meme(conn: &mut AsyncPgConnection, guild: u64) -> Resul #[derive(Debug, Clone)] pub struct Stats { - pub memes_overall: usize, - pub audio_memes: usize, - pub image_memes: usize, - pub started_recording: Option<DateTime<Utc>>, - pub total_meme_invocations: usize, - pub audio_meme_invocations: usize, + pub memes_overall: usize, + pub audio_memes: usize, + pub image_memes: usize, + pub started_recording: Option<DateTime<Utc>>, + pub total_meme_invocations: usize, + pub audio_meme_invocations: usize, pub random_meme_invocations: usize, - pub most_active_day: Option<NaiveDate>, + pub most_active_day: Option<NaiveDate>, pub most_active_day_count: usize, - pub most_audio_active_day: Option<NaiveDate>, + pub most_audio_active_day: Option<NaiveDate>, pub most_audio_active_count: usize, - pub most_random_meme_user: Option<u64>, - pub most_random_meme_user_count: usize, - pub most_directly_named_meme_user: Option<u64>, + pub most_random_meme_user: Option<u64>, + pub most_random_meme_user_count: usize, + pub most_directly_named_meme_user: Option<u64>, pub most_directly_named_meme_count: usize, - pub most_popular_named_meme: Option<String>, + pub most_popular_named_meme: Option<String>, pub most_popular_named_meme_count: usize, - pub most_popular_random_meme: Option<String>, + pub most_popular_random_meme: Option<String>, pub most_popular_random_meme_count: usize, - pub most_popular_meme_overall: Option<String>, + pub most_popular_meme_overall: Option<String>, pub most_popular_meme_overall_count: usize, } @@ -579,7 +580,7 @@ pub async fn stats(conn: &mut AsyncPgConnection, guild: u64) -> Result<Stats> { #[inline] fn option_first_count<T>(rows: Vec<tokio_postgres::Row>) -> (Option<T>, i64) where - for<'a> T: FromSql<'a>, + for<'a> T: FromSql<'a>, { let Some(row) = <[_]>::first(&rows) else { return (None, 0); @@ -730,10 +731,10 @@ pub async fn stats(conn: &mut AsyncPgConnection, guild: u64) -> Result<Stats> { #[derive(Clone, Debug, Hash, PartialEq, Eq, Default)] pub struct MemerInfo { - pub user_id: u64, - pub random_memes: usize, - pub specific_memes: usize, - pub most_used_meme: String, + pub user_id: u64, + pub random_memes: usize, + pub specific_memes: usize, + pub most_used_meme: String, pub most_used_meme_count: usize, } @@ -790,10 +791,10 @@ pub async fn memers(guild: u64) -> Result<Vec<MemerInfo>> { let most_memed_count: i64 = row.get(4); MemerInfo { - user_id: user_id as u64, - random_memes: random_count as usize, - specific_memes: specific_count as usize, - most_used_meme: most_memed_meme, + user_id: user_id as u64, + random_memes: random_count as usize, + specific_memes: specific_count as usize, + most_used_meme: most_memed_meme, most_used_meme_count: most_memed_count as usize, } }) diff --git a/src/db/models.rs b/src/db/models.rs index 33fa34e..e699630 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -4,7 +4,6 @@ use anyhow::{ }; use chrono::naive::NaiveDateTime; use diesel::{ - associations::HasTable, prelude::*, Identifiable, Insertable, diff --git a/src/util/mod.rs b/src/util/mod.rs index 38f4e55..474d36d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,19 +1,9 @@ use std::{ - cmp::max_by, collections::HashMap, - process::{ - id, - Stdio, - }, + process::Stdio, }; -use anyhow::anyhow; use chrono::Duration; -use diesel::row::NamedRow; -use fnv::{ - FnvHashMap, - FnvHashSet, -}; use grate::tracing; use lazy_static::lazy_static; use poise::{ @@ -26,7 +16,6 @@ use regex::{ }; use serenity::{ all::{ - ChannelType, CreateMessage, GuildId, GuildRef, @@ -83,9 +72,9 @@ pub async fn users_listening(ctx: PoiseContext<'_>) -> anyhow::Result<bool> { pub fn msg<U, E>(ctx: poise::Context<'_, U, E>) -> Option<&Message> { match ctx { poise::Context::Prefix(poise::PrefixContext { - msg, - .. - }) => Some(msg), + msg, + .. + }) => Some(msg), _ => None, } } @@ -126,6 +115,18 @@ pub fn unwrap_tts<U, E>(ctx: poise::Context<'_, U, E>) -> bool { } #[inline] +pub async fn volume(ctx: PoiseContext<'_>) -> f64 { + let Some(guild_id) = ctx.guild_id() else { + return 1.; + }; + + let data = ctx.serenity_context().data.read().await; + let vol = data.get::<crate::bot::VolumeKey>().unwrap(); + + vol.get(&guild_id).map(|g| *g).unwrap_or(1.) +} + +#[inline] pub async fn send( ctx: &Context, channel: ChannelId, |
