From 015f7e10e2be1b0eb6355d179711aba3cb9fab29 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Wed, 12 Jun 2024 00:38:36 +0700 Subject: [PATCH 01/12] Bumped to `0.8.0`, Fixed confirmation cancellation, Added more unit tests --- Cargo.lock | 10 +-- lyra/Cargo.toml | 4 +- lyra/src/command/util.rs | 21 +++--- lyra/src/component/config/access/clear.rs | 18 ++--- lyra/src/component/config/access/edit.rs | 6 +- lyra/src/component/config/access/mode.rs | 4 +- lyra/src/error.rs | 4 ++ lyra/src/error/command.rs | 6 +- lyra/src/error/command/util.rs | 10 +-- lyra/src/error/gateway.rs | 9 --- lyra/src/gateway/interaction.rs | 50 ++++++++------ lyra_ext/Cargo.toml | 2 +- lyra_ext/src/as_grapheme.rs | 56 +++++++++++++++- lyra_ext/src/iter/multi_interleave.rs | 31 ++++++--- lyra_ext/src/pretty/duration_display.rs | 14 ++-- lyra_ext/src/pretty/truncate.rs | 81 ++++++++++++++++++++--- lyra_proc/Cargo.toml | 2 +- lyra_proc/src/config_access.rs | 14 ++-- 18 files changed, 226 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 538f22b..ed4eab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "lyra" -version = "0.7.1" +version = "0.8.0" dependencies = [ "aho-corasick", "anyhow", @@ -1350,7 +1350,7 @@ dependencies = [ [[package]] name = "lyra_ext" -version = "0.7.1" +version = "0.8.0" dependencies = [ "bitflags 2.5.0", "const-str", @@ -1367,7 +1367,7 @@ dependencies = [ [[package]] name = "lyra_proc" -version = "0.7.1" +version = "0.8.0" dependencies = [ "heck 0.4.1", "itertools 0.12.1", @@ -1862,9 +1862,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", diff --git a/lyra/Cargo.toml b/lyra/Cargo.toml index 060391b..d272d69 100644 --- a/lyra/Cargo.toml +++ b/lyra/Cargo.toml @@ -2,7 +2,7 @@ name = "lyra" readme = "../README.md" description = "A featureful, self-hostable Discord music bot." -version = "0.7.1" +version = "0.8.0" edition = "2021" license = "GPL-3.0" repository = "https://github.com/lyra-music/lyra" @@ -60,7 +60,7 @@ tokio = { version = "1.38.0", features = [ ] } serde = "1.0.203" serde_json = "1.0.117" -regex = "1.10.4" +regex = "1.10.5" linkify = "0.10.0" fuzzy-matcher = "0.3.7" log = "0.4.21" diff --git a/lyra/src/command/util.rs b/lyra/src/command/util.rs index 2894168..6d19c35 100644 --- a/lyra/src/command/util.rs +++ b/lyra/src/command/util.rs @@ -38,11 +38,11 @@ use crate::{ command::{ check::NotSuppressedError, util::{ - AutoJoinOrCheckInVoiceWithUserError, AutoJoinSuppressedError, ConfirmationError, + AutoJoinOrCheckInVoiceWithUserError, AutoJoinSuppressedError, HandleSuppressedAutoJoinError, PromptForConfirmationError, }, }, - Suppressed as SuppressedError, + ConfirmationTimedOut, Suppressed as SuppressedError, }, gateway::GuildIdAware, lavalink::{DelegateMethods, LavalinkAware}, @@ -279,7 +279,7 @@ async fn handle_suppressed_auto_join( pub async fn prompt_for_confirmation( mut ctx: GuildCtx, -) -> Result { +) -> Result<(GuildModalCtx, bool), PromptForConfirmationError> { let text_input = TextInput { custom_id: String::new(), label: String::from("This is a destructive command. Are you sure?"), @@ -325,26 +325,23 @@ pub async fn prompt_for_confirmation( ) .await; - let modal_ctx = match wait_for_modal_submit { + let ctx_and_confirmed = match wait_for_modal_submit { Ok(Ok(Event::InteractionCreate(interaction))) => { let ctx = ctx.into_modal_interaction(interaction); - if ctx.submit_data().components[0].components[0] + let confirmed = ctx.submit_data().components[0].components[0] .value .as_ref() - .is_some_and(|s| s == "YES") - { - Err(ConfirmationError::Cancelled)?; - } - ctx + .is_some_and(|s| s == "YES"); + (ctx, confirmed) } // SAFETY: the future has been filtered to only match modal submit interaction // so this branch is unreachable Ok(Ok(_)) => unsafe { std::hint::unreachable_unchecked() }, Ok(Err(e)) => Err(e)?, - Err(_) => Err(ConfirmationError::TimedOut)?, + Err(_) => Err(ConfirmationTimedOut)?, }; - Ok(modal_ctx) + Ok(ctx_and_confirmed) } pub async fn auto_new_player(ctx: &GuildCtx) -> LavalinkResult { diff --git a/lyra/src/component/config/access/clear.rs b/lyra/src/component/config/access/clear.rs index 0fbfc43..c3783f4 100644 --- a/lyra/src/component/config/access/clear.rs +++ b/lyra/src/component/config/access/clear.rs @@ -6,7 +6,7 @@ use super::AccessCategory; use crate::{ command::{ check, - macros::{out, sus}, + macros::{note, out, sus}, model::BotSlashCommand, require, util::prompt_for_confirmation, @@ -39,17 +39,17 @@ impl BotSlashCommand for Clear { let g = ctx.guild_id().get() as i64; set.spawn(async move { - sqlx::query(&format!( - "--sql - DELETE FROM {c} WHERE guild = $1;" - )) - .bind(g) - .execute(&db) - .await + sqlx::query(&format!("DELETE FROM {c} WHERE guild = $1;")) + .bind(g) + .execute(&db) + .await }); }); - let mut ctx = prompt_for_confirmation(ctx).await?; + let (mut ctx, confirmed) = prompt_for_confirmation(ctx).await?; + if !confirmed { + note!("Cancelled executing this command", ctx); + } let mut rows_affected = 0; while let Some(res) = set.join_next().await { diff --git a/lyra/src/component/config/access/edit.rs b/lyra/src/component/config/access/edit.rs index cace494..678666f 100644 --- a/lyra/src/component/config/access/edit.rs +++ b/lyra/src/component/config/access/edit.rs @@ -47,8 +47,7 @@ fn add_access( join_set.spawn(async move { sqlx::query(&format!( - "--sql - INSERT INTO {column} + "INSERT INTO {column} SELECT ch_new.guild, ch_new.id FROM (VALUES {values_clause}) AS ch_new (guild, id) WHERE NOT EXISTS @@ -76,8 +75,7 @@ fn remove_access( join_set.spawn(async move { sqlx::query(&format!( - "--sql - DELETE FROM {column} + "DELETE FROM {column} WHERE guild = $1 AND ({where_clause}); ", )) diff --git a/lyra/src/component/config/access/mode.rs b/lyra/src/component/config/access/mode.rs index 427ec28..fdecea5 100644 --- a/lyra/src/component/config/access/mode.rs +++ b/lyra/src/component/config/access/mode.rs @@ -118,9 +118,7 @@ impl BotSlashCommand for Mode { .join(" OR "); let res = sqlx::query(&format!( - "--sql - UPDATE guild_configs SET {set_statements} WHERE id = $1 AND ({where_clause}); - " + "UPDATE guild_configs SET {set_statements} WHERE id = $1 AND ({where_clause});" )) .bind(ctx.guild_id().get() as i64) .bind(access_mode) diff --git a/lyra/src/error.rs b/lyra/src/error.rs index 1db7468..eb45205 100644 --- a/lyra/src/error.rs +++ b/lyra/src/error.rs @@ -173,3 +173,7 @@ pub enum RunError { #[derive(Error, Debug)] #[error("not in a guild")] pub struct NotInGuild; + +#[derive(Error, Debug)] +#[error("confirmation timed out")] +pub struct ConfirmationTimedOut; diff --git a/lyra/src/error/command.rs b/lyra/src/error/command.rs index eb975e5..8f9640c 100644 --- a/lyra/src/error/command.rs +++ b/lyra/src/error/command.rs @@ -82,7 +82,7 @@ pub enum FlattenedError<'a> { PollLoss(&'a check::PollLossError), PollVoided(&'a check::PollVoidedError), StandbyCanceled(&'a twilight_standby::future::Canceled), - Confirmation(&'a util::ConfirmationError), + ConfirmationTimedOut(&'a super::ConfirmationTimedOut), GatewaySend(&'a twilight_gateway::error::ChannelError), AutoJoinSuppressed(&'a util::AutoJoinSuppressedError), AutoJoinAttemptFailed(&'a super::AutoJoinAttemptFailed), @@ -246,7 +246,9 @@ impl<'a> Fe<'a> { const fn from_prompt_for_confirmation(error: &'a util::PromptForConfirmationError) -> Fe<'a> { match error { util::PromptForConfirmationError::StandbyCanceled(e) => Self::StandbyCanceled(e), - util::PromptForConfirmationError::Confirmation(e) => Self::Confirmation(e), + util::PromptForConfirmationError::ConfirmationTimedout(e) => { + Self::ConfirmationTimedOut(e) + } util::PromptForConfirmationError::Respond(e) => Self::from_respond(e), } } diff --git a/lyra/src/error/command/util.rs b/lyra/src/error/command/util.rs index 760764b..cc7a8e9 100644 --- a/lyra/src/error/command/util.rs +++ b/lyra/src/error/command/util.rs @@ -6,15 +6,7 @@ use twilight_model::id::{marker::MessageMarker, Id}; pub enum PromptForConfirmationError { StandbyCanceled(#[from] twilight_standby::future::Canceled), Respond(#[from] super::RespondError), - Confirmation(#[from] ConfirmationError), -} - -#[derive(Error, Debug)] -pub enum ConfirmationError { - #[error("confirmation cancelled")] - Cancelled, - #[error("confirmation timed out")] - TimedOut, + ConfirmationTimedout(#[from] crate::error::ConfirmationTimedOut), } #[derive(thiserror::Error, Debug)] diff --git a/lyra/src/error/gateway.rs b/lyra/src/error/gateway.rs index 62a8366..242c455 100644 --- a/lyra/src/error/gateway.rs +++ b/lyra/src/error/gateway.rs @@ -1,12 +1,5 @@ use thiserror::Error; -#[derive(Error, Debug)] -#[error("handling confirmation error failed: {:?}", .0)] -pub enum MatchConfirmationError { - Http(#[from] twilight_http::Error), - Followup(#[from] super::core::FollowupError), -} - #[derive(Error, Debug)] pub enum ProcessError { #[error(transparent)] @@ -26,8 +19,6 @@ pub enum ProcessError { #[error(transparent)] HandleVoiceStateUpdate(#[from] super::component::connection::HandleVoiceStateUpdateError), #[error(transparent)] - MatchConfirmation(#[from] MatchConfirmationError), - #[error(transparent)] Respond(#[from] super::command::RespondError), #[error("error executing command `/{}`: {:?}", .name, .source)] CommandExecute { diff --git a/lyra/src/gateway/interaction.rs b/lyra/src/gateway/interaction.rs index fec9e72..2b2a48c 100644 --- a/lyra/src/gateway/interaction.rs +++ b/lyra/src/gateway/interaction.rs @@ -32,10 +32,10 @@ use crate::{ AlternateVoteResponse, AnotherPollOngoingError, PollLossError, PollLossErrorKind, }, declare::{CommandExecuteError, Fuunacee}, - util::{AutoJoinSuppressedError, ConfirmationError}, + util::AutoJoinSuppressedError, Error as CommandError, Fe, RespondError, }, - gateway::{MatchConfirmationError, ProcessError, ProcessResult}, + gateway::{ProcessError, ProcessResult}, AutoJoinAttemptFailed as AutoJoinAttemptFailedError, EPrint, PositionOutOfRange as PositionOutOfRangeError, Suppressed as SuppressedError, }, @@ -76,7 +76,9 @@ impl Process for Context { } // SAFETY: `self.inner.kind` is `MessageComponent`, so this is safe InteractionType::MessageComponent => unsafe { self.process_as_component() }.await, - InteractionType::ModalSubmit | InteractionType::Ping => Ok(()), // ignored + // SAFETY: `self.inner.kind` is `ModalSubmit`, so this is safe + InteractionType::ModalSubmit => unsafe { self.process_as_modal() }.await, + InteractionType::Ping => Ok(()), // ignored _ => unimplemented!(), } } @@ -160,7 +162,8 @@ impl Context { async unsafe fn process_as_autocomplete(mut self) -> ProcessResult { let Some(InteractionData::ApplicationCommand(data)) = self.inner.data.take() else { - // SAFETY: + // SAFETY: interaction is of type `ApplicationCommandAutocomplete`, + // so `self.inner.data.take()` will always be `InteractionData::ApplicationCommand(_)` unsafe { unreachable_unchecked() } }; @@ -182,8 +185,27 @@ impl Context { } #[allow(clippy::unused_async)] - async unsafe fn process_as_component(self) -> ProcessResult { + async unsafe fn process_as_component(mut self) -> ProcessResult { + let Some(InteractionData::MessageComponent(data)) = self.inner.data.take() else { + // SAFETY: interaction is of type `MessageComponent`, + // so `self.inner.data.take()` will always be `InteractionData::MessageComponent(_)` + unsafe { unreachable_unchecked() } + }; + tracing::trace!(?data); // TODO: implement controller + + Ok(()) + } + + #[allow(clippy::unused_async)] + async unsafe fn process_as_modal(mut self) -> ProcessResult { + let Some(InteractionData::ModalSubmit(data)) = self.inner.data.take() else { + // SAFETY: interaction is of type `ModalSubmit`, + // so `self.inner.data.take()` will always be `InteractionData::ModalSubmit(_)` + unsafe { unreachable_unchecked() } + }; + tracing::trace!(?data); + Ok(()) } } @@ -258,7 +280,9 @@ async fn match_error( i ); } - Fe::Confirmation(e) => Ok(match_confirmation(e, i).await?), + Fe::ConfirmationTimedOut(_) => { + sus_fol!("Confirmation timed out.", i); + } Fe::NoPlayer(_) => { let play = InteractionClient::mention_command::(); caut!(format!("Not yet played anything. Use {} first.", play), i); @@ -363,20 +387,6 @@ async fn match_position_out_of_range( bad!(message, i); } -async fn match_confirmation( - error: &ConfirmationError, - i: InteractionInterface<'_>, -) -> Result<(), MatchConfirmationError> { - match error { - ConfirmationError::Cancelled => { - note!("Cancelled executing this command.", i); - } - ConfirmationError::TimedOut => { - sus_fol!("Confirmation timed out.", i); - } - } -} - async fn match_another_poll_ongoing( error: &AnotherPollOngoingError, i: InteractionInterface<'_>, diff --git a/lyra_ext/Cargo.toml b/lyra_ext/Cargo.toml index 9d7d2f7..fba9ba6 100644 --- a/lyra_ext/Cargo.toml +++ b/lyra_ext/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lyra_ext" -version = "0.7.1" +version = "0.8.0" edition = "2021" [lints.clippy] diff --git a/lyra_ext/src/as_grapheme.rs b/lyra_ext/src/as_grapheme.rs index e50cfcc..f9f893c 100644 --- a/lyra_ext/src/as_grapheme.rs +++ b/lyra_ext/src/as_grapheme.rs @@ -10,7 +10,7 @@ pub trait AsGrapheme: UnicodeSegmentation { fn grapheme_truncate(&self, new_len: usize) -> Cow where Self: ToOwned, - ::Owned: for<'a> FromIterator<&'a str>, + Self::Owned: for<'a> FromIterator<&'a str>, { (self.grapheme_len() <= new_len) .then_some(Cow::Borrowed(self)) @@ -21,4 +21,56 @@ pub trait AsGrapheme: UnicodeSegmentation { impl AsGrapheme for str {} #[cfg(test)] -mod test {} +mod test { + use rstest::rstest; + + use crate::as_grapheme::AsGrapheme; + + #[rstest] + #[case("", 0)] + #[case("1", 1)] + #[case("โค๏ธโ€๐Ÿ”ฅ", 1)] + #[case("โค๏ธ๐Ÿ”ฅ", 2)] + #[case("๐Ÿ‡น๐Ÿ‡ญ", 1)] + #[case("๐Ÿ‡น+๐Ÿ‡ญ", 3)] + #[case("๐Ÿณ๏ธโ€โšง๏ธ she/her", 9)] + fn grapheme_len(#[case] input: &str, #[case] expected: usize) { + assert_eq!(input.grapheme_len(), expected); + } + + #[rstest] + #[case("", "")] + #[case("?", "")] + #[case("๐Ÿ„โ€๐ŸŸซ", "")] + #[case("๐Ÿ„๐ŸŸซ", "")] + #[case("๐Ÿ‡ฌ๐Ÿ‡ง", "")] + #[case("๐Ÿ‡ฌ+๐Ÿ‡ง", "")] + #[case("๐Ÿ™‚โ€โ†”๏ธ Nope!", "")] + fn grapheme_truncate_0(#[case] input: &str, #[case] expected: &str) { + assert_eq!(input.grapheme_truncate(0), expected); + } + + #[rstest] + #[case("", "")] + #[case("!", "!")] + #[case("๐Ÿ‹โ€๐ŸŸฉ", "๐Ÿ‹โ€๐ŸŸฉ")] + #[case("๐Ÿ‹๐ŸŸฉ", "๐Ÿ‹")] + #[case("๐Ÿ‡บ๐Ÿ‡ธ", "๐Ÿ‡บ๐Ÿ‡ธ")] + #[case("๐Ÿ‡บ+๐Ÿ‡ธ", "๐Ÿ‡บ")] + #[case("๐Ÿ™‚โ€โ†•๏ธ Yep!", "๐Ÿ™‚โ€โ†•๏ธ")] + fn grapheme_truncate_1(#[case] input: &str, #[case] expected: &str) { + assert_eq!(input.grapheme_truncate(1), expected); + } + + #[rstest] + #[case("", "")] + #[case("#", "#")] + #[case("๐Ÿฆโ€๐Ÿ”ฅ", "๐Ÿฆโ€๐Ÿ”ฅ")] + #[case("๐Ÿฆ๐Ÿ”ฅ", "๐Ÿฆ๐Ÿ”ฅ")] + #[case("๐Ÿ‡ฏ๐Ÿ‡ต", "๐Ÿ‡ฏ๐Ÿ‡ต")] + #[case("๐Ÿ‡ฏ+๐Ÿ‡ต", "๐Ÿ‡ฏ+")] + #[case("๐Ÿงš๐Ÿปโ€โ™€๏ธI'm an angel!", "๐Ÿงš๐Ÿปโ€โ™€๏ธI")] + fn grapheme_truncate_2(#[case] input: &str, #[case] expected: &str) { + assert_eq!(input.grapheme_truncate(2), expected); + } +} diff --git a/lyra_ext/src/iter/multi_interleave.rs b/lyra_ext/src/iter/multi_interleave.rs index 3137bbf..60ccc51 100644 --- a/lyra_ext/src/iter/multi_interleave.rs +++ b/lyra_ext/src/iter/multi_interleave.rs @@ -1,18 +1,24 @@ -pub fn multi_interleave(iters: impl IntoIterator) -> MultiInterleave +pub fn multi_interleave(iters: A) -> MultiInterleave<::IntoIter> where - I: IntoIterator, - J: Iterator, - Box<[J]>: FromIterator, + A: IntoIterator, + A::Item: IntoIterator, + ::IntoIter: Iterator, { MultiInterleave::new(iters.into_iter().map(IntoIterator::into_iter).collect()) } -pub struct MultiInterleave { +pub struct MultiInterleave +where + I: Iterator, +{ iterators: Box<[I]>, current: usize, } -impl MultiInterleave { +impl MultiInterleave +where + I: Iterator, +{ fn new(iterators: Box<[I]>) -> Self { Self { iterators, @@ -21,7 +27,10 @@ impl MultiInterleave { } } -impl Iterator for MultiInterleave { +impl Iterator for MultiInterleave +where + I: Iterator, +{ type Item = I::Item; fn next(&mut self) -> Option { @@ -52,7 +61,7 @@ mod tests { #[rstest] #[case([], vec![])] - fn multi_interleave_0(#[case] input: [Vec; N], #[case] expected: Vec) { + fn multi_interleave_0(#[case] input: [Vec; 0], #[case] expected: Vec) { assert_eq!(multi_interleave(input).collect::>(), expected); } @@ -61,7 +70,7 @@ mod tests { #[case([vec![1]] , vec![1])] #[case([vec![1, 2]] , vec![1, 2])] #[case([vec![1, 2, 3]], vec![1, 2, 3])] - fn multi_interleave_1(#[case] input: [Vec; N], #[case] expected: Vec) { + fn multi_interleave_1(#[case] input: [Vec; 1], #[case] expected: Vec) { assert_eq!(multi_interleave(input).collect::>(), expected); } @@ -82,7 +91,7 @@ mod tests { #[case([vec![1] , vec![1, 2, 3]], vec![1, 1, 2, 3])] #[case([vec![1, 2] , vec![1, 2, 3]], vec![1, 1, 2, 2, 3])] #[case([vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 2, 2, 3, 3])] - fn multi_interleave_2(#[case] input: [Vec; N], #[case] expected: Vec) { + fn multi_interleave_2(#[case] input: [Vec; 2], #[case] expected: Vec) { assert_eq!(multi_interleave(input).collect::>(), expected); } @@ -151,7 +160,7 @@ mod tests { #[case([vec![1] , vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 3, 3])] #[case([vec![1, 2] , vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 2, 3, 3])] #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 2, 3, 3, 3])] - fn multi_interleave_3(#[case] input: [Vec; N], #[case] expected: Vec) { + fn multi_interleave_3(#[case] input: [Vec; 3], #[case] expected: Vec) { assert_eq!(multi_interleave(input).collect::>(), expected); } } diff --git a/lyra_ext/src/pretty/duration_display.rs b/lyra_ext/src/pretty/duration_display.rs index 4c07f81..95c29d1 100644 --- a/lyra_ext/src/pretty/duration_display.rs +++ b/lyra_ext/src/pretty/duration_display.rs @@ -34,17 +34,15 @@ impl PrettyDurationDisplay for Duration { } } -trait FromPrettyStr { - fn from_pretty_str(value: &str) -> Result - where - Self: Sized; +trait FromPrettyStr +where + Self: Sized, +{ + fn from_pretty_str(value: &str) -> Result; } impl FromPrettyStr for Duration { - fn from_pretty_str(value: &str) -> Result - where - Self: Sized, - { + fn from_pretty_str(value: &str) -> Result { let captures = if let Some(captures) = timestamp().captures(value) { captures } else if let Some(captures) = timestamp_2().captures(value) { diff --git a/lyra_ext/src/pretty/truncate.rs b/lyra_ext/src/pretty/truncate.rs index 7889b05..e4ddc5d 100644 --- a/lyra_ext/src/pretty/truncate.rs +++ b/lyra_ext/src/pretty/truncate.rs @@ -6,6 +6,7 @@ pub trait PrettyTruncator: AsGrapheme + ToOwned + 'static where for<'a> Cow<'a, Self>: Add<&'a Self, Output = Cow<'a, Self>>, { + fn empty() -> &'static Self; fn trail() -> &'static Self; fn pretty_truncate(&self, new_len: usize) -> Cow where @@ -15,11 +16,23 @@ where (self.grapheme_len() <= new_len) .then_some(Cow::Borrowed(self)) - .unwrap_or_else(|| self.grapheme_truncate(new_len - trail.grapheme_len()) + trail) + .unwrap_or_else(|| { + let Some(len) = new_len.checked_sub(trail.grapheme_len()) else { + return Cow::Borrowed(Self::empty()); + }; + if len == 0 { + return Cow::Borrowed(trail); + } + self.grapheme_truncate(len) + trail + }) } } impl PrettyTruncator for str { + fn empty() -> &'static Self { + "" + } + fn trail() -> &'static Self { "โ€ฆ" } @@ -33,13 +46,65 @@ mod test { #[rstest] #[case("", "")] - #[case("1", "1")] - #[case("234", "234")] - #[case("5678", "56โ€ฆ")] - #[case("็ซช็ดใ‚’ๅผพใ", "็ซช็ดโ€ฆ")] - #[case("เธเธฒเธฃเน€เธ‚เธตเธขเธ™เน‚เธ›เธฃเนเธเธฃเธก", "เธเธฒโ€ฆ")] - #[case("๐Ÿ˜ถโ€๐ŸŒซ๏ธ๐Ÿ˜ฎโ€๐Ÿ’จ๐Ÿ˜ตโ€๐Ÿ’ซโค๏ธโ€๐Ÿ”ฅโค๏ธโ€๐Ÿฉน๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ", "๐Ÿ˜ถโ€๐ŸŒซ๏ธ๐Ÿ˜ฎโ€๐Ÿ’จโ€ฆ")] - fn string_pretty_truncate(#[case] input: &str, #[case] expected: &str) { + #[case("1", "")] + #[case("23", "")] + #[case("456", "")] + #[case("เธ•เธต", "")] + #[case("เธ‡เธธเธ‡เธด", "")] + #[case("เธญเธดเธญเธดเธญเธด", "")] + #[case("โค๏ธโ€๐Ÿฉน", "")] + #[case("๐Ÿ‘๏ธโ€๐Ÿ—จ๏ธ๐Ÿ˜ถโ€๐ŸŒซ๏ธ", "")] + #[case("๐Ÿ„โ€โ™‚๏ธ๐Ÿฑโ€๐Ÿš€๐Ÿณ๏ธโ€๐ŸŒˆ", "")] + #[case("Hello there!", "")] + fn pretty_truncate_0(#[case] input: &str, #[case] expected: &str) { + assert_eq!(input.pretty_truncate(0), expected); + } + + #[rstest] + #[case("", "")] + #[case("a", "a")] + #[case("bc", "โ€ฆ")] + #[case("def", "โ€ฆ")] + #[case("เธ”เธต", "เธ”เธต")] + #[case("เธกเธธเธกเธด", "โ€ฆ")] + #[case("เธˆเธฑเธ•เธธเธฃเธฑเธช", "โ€ฆ")] + #[case("๐Ÿคน๐Ÿผโ€โ™€๏ธ", "๐Ÿคน๐Ÿผโ€โ™€๏ธ")] + #[case("๐Ÿ‘จ๐Ÿปโ€๐Ÿš’๐Ÿณโ€๐ŸŸงโ€โฌ›โ€๐ŸŸง", "โ€ฆ")] + #[case("๐Ÿฑโ€๐Ÿ‘“๐Ÿ‘ฏ๐Ÿพโ€โ™‚๏ธ๐Ÿฆโ€โฌ›", "โ€ฆ")] + #[case("ๆˆ‘ๆƒณๆˆไธบๅ“ฅ็‰นๅฅณๅญฉใ€‚ใ€‚ใ€‚", "โ€ฆ")] + fn pretty_truncate_1(#[case] input: &str, #[case] expected: &str) { + assert_eq!(input.pretty_truncate(1), expected); + } + + #[rstest] + #[case("", "")] + #[case("[", "[")] + #[case("{}", "{}")] + #[case("<=>", "<โ€ฆ")] + #[case("๐Ÿ‡ณ๐Ÿ‡ด", "๐Ÿ‡ณ๐Ÿ‡ด")] + #[case("๐Ÿ‡ฟ๐Ÿ‡ฆ๐Ÿ‡ฒ๐Ÿ‡ณ", "๐Ÿ‡ฟ๐Ÿ‡ฆ๐Ÿ‡ฒ๐Ÿ‡ณ")] + #[case("๐Ÿ‡ธ๐Ÿ‡จ๐Ÿ‡ท๐Ÿ‡ด๐Ÿ‡ฑ๐Ÿ‡พ", "๐Ÿ‡ธ๐Ÿ‡จโ€ฆ")] + #[case("๐Ÿ‘จโ€๐Ÿ‘ถโ€๐Ÿ‘ฆ", "๐Ÿ‘จโ€๐Ÿ‘ถโ€๐Ÿ‘ฆ")] + #[case("๐ŸŠ๐Ÿผโ€โ™‚๏ธ๐Ÿคผโ€โ™€๏ธ", "๐ŸŠ๐Ÿผโ€โ™‚๏ธ๐Ÿคผโ€โ™€๏ธ")] + #[case("๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ๐Ÿคธ๐Ÿผโ€โ™‚๏ธ๐Ÿ˜ตโ€๐Ÿ’ซ", "๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆโ€ฆ")] + #[case("ๅ‰ฃๅ…‰ใ‚ˆใ€ไธ–ใฎไนฑใ‚Œใ‚’ๆ–ฌใ‚Šๅฐฝใใ›๏ผ", "ๅ‰ฃโ€ฆ")] + fn pretty_truncate_2(#[case] input: &str, #[case] expected: &str) { + assert_eq!(input.pretty_truncate(2), expected); + } + + #[rstest] + #[case("", "")] + #[case(";", ";")] + #[case("//", "//")] + #[case("===", "===")] + #[case("๐Ÿ‡ธ๐Ÿ‡ด", "๐Ÿ‡ธ๐Ÿ‡ด")] + #[case("๐Ÿ‡ท๐Ÿ‡ด๐Ÿ‡ซ๐Ÿ‡ฎ", "๐Ÿ‡ท๐Ÿ‡ด๐Ÿ‡ซ๐Ÿ‡ฎ")] + #[case("๐Ÿ‡ฌ๐Ÿ‡ฌ๐Ÿ‡ฆ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡จ", "๐Ÿ‡ฌ๐Ÿ‡ฌ๐Ÿ‡ฆ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡จ")] + #[case("๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ", "๐Ÿ’†๐Ÿฟโ€โ™‚๏ธ")] + #[case("๐Ÿคนโ€โ™€๏ธ๐Ÿฑโ€๐Ÿ", "๐Ÿคนโ€โ™€๏ธ๐Ÿฑโ€๐Ÿ")] + #[case("๐Ÿดโ€โ˜ ๏ธ๐Ÿ‘ฎโ€โ™‚๏ธ๐Ÿปโ€โ„๏ธ", "๐Ÿดโ€โ˜ ๏ธ๐Ÿ‘ฎโ€โ™‚๏ธ๐Ÿปโ€โ„๏ธ")] + #[case("เธฅเธฒเธฅเธฒเธฅเธฒ เธฅเธฒเธฅเธฒ เธฅเธฒเธฅเธฒเธฅเธฒ เธฅเธฒ เธฅเธฒ เธฅเธฒ~", "เธฅเธฒโ€ฆ")] + fn pretty_truncate_3(#[case] input: &str, #[case] expected: &str) { assert_eq!(input.pretty_truncate(3), expected); } } diff --git a/lyra_proc/Cargo.toml b/lyra_proc/Cargo.toml index fd8a20c..7940ec2 100644 --- a/lyra_proc/Cargo.toml +++ b/lyra_proc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lyra_proc" -version = "0.7.1" +version = "0.8.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/lyra_proc/src/config_access.rs b/lyra_proc/src/config_access.rs index 2ddb38e..c129c97 100644 --- a/lyra_proc/src/config_access.rs +++ b/lyra_proc/src/config_access.rs @@ -20,18 +20,12 @@ pub fn impl_view_access_ids(Args(categories): &Args) -> TokenStream { }) .map(|c| format!("{c}_access")); - let access_queries = column_names.clone().map(|t| { - format!( - r"--sql - SELECT id FROM {t} WHERE guild = $1; - ", - ) - }); + let access_queries = column_names + .clone() + .map(|t| format!("SELECT id FROM {t} WHERE guild = $1;",)); let mode_queries = format!( - r"--sql - SELECT {} FROM guild_configs WHERE id = $1 - ", + "SELECT {} FROM guild_configs WHERE id = $1", column_names.clone().join(", ") ); From 9c18c182d5974125f1add7295d44b25277c82259 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Mon, 29 Jul 2024 01:32:37 +0700 Subject: [PATCH 02/12] whatever I did here --- Cargo.lock | 667 ++++++++++++------ flake.lock | 18 +- flake.nix | 1 + lavalink/application.yml | 4 +- lyra/Cargo.toml | 45 +- lyra/src/client.rs | 5 +- lyra/src/command/check.rs | 202 ++++-- lyra/src/command/declare.rs | 6 + lyra/src/command/model.rs | 17 +- lyra/src/command/model/ctx.rs | 99 +-- lyra/src/command/model/ctx/autocomplete.rs | 12 +- lyra/src/command/model/ctx/command_data.rs | 18 +- lyra/src/command/model/ctx/menu.rs | 20 +- lyra/src/command/model/ctx/message.rs | 15 +- lyra/src/command/model/ctx/modal.rs | 20 +- lyra/src/command/poll.rs | 61 +- lyra/src/command/require.rs | 142 +++- lyra/src/command/util.rs | 33 +- lyra/src/component/config/access.rs | 23 +- lyra/src/component/config/access/clear.rs | 4 +- lyra/src/component/config/access/edit.rs | 6 +- lyra/src/component/config/access/mode.rs | 6 +- lyra/src/component/config/now_playing.rs | 3 +- lyra/src/component/connection.rs | 48 +- lyra/src/component/connection/join.rs | 57 +- lyra/src/component/connection/leave.rs | 19 +- lyra/src/component/playback.rs | 138 ++-- lyra/src/component/playback/back.rs | 1 + lyra/src/component/playback/jump.rs | 27 + lyra/src/component/playback/jump/backward.rs | 58 ++ lyra/src/component/playback/jump/first.rs | 47 ++ lyra/src/component/playback/jump/forward.rs | 67 ++ lyra/src/component/playback/jump/to.rs | 121 ++++ lyra/src/component/playback/play_pause.rs | 37 + lyra/src/component/playback/restart.rs | 30 + lyra/src/component/playback/seek.rs | 17 + lyra/src/component/playback/seek/backward.rs | 55 ++ lyra/src/component/playback/seek/forward.rs | 68 ++ lyra/src/component/playback/seek/to.rs | 96 +++ lyra/src/component/playback/skip.rs | 1 + lyra/src/component/queue.rs | 137 ++-- lyra/src/component/queue/clear.rs | 19 +- lyra/src/component/queue/fair_queue.rs | 8 +- lyra/src/component/queue/move.rs | 55 +- lyra/src/component/queue/play.rs | 23 +- lyra/src/component/queue/remove.rs | 45 +- lyra/src/component/queue/remove_range.rs | 45 +- lyra/src/component/queue/repeat.rs | 39 +- lyra/src/component/queue/shuffle.rs | 10 +- lyra/src/component/tuning.rs | 12 +- lyra/src/component/tuning/equaliser.rs | 1 + lyra/src/component/tuning/equaliser/preset.rs | 1 + lyra/src/component/tuning/filter/pitch.rs | 4 +- lyra/src/component/tuning/filter/pitch/set.rs | 2 +- lyra/src/component/tuning/speed.rs | 20 +- lyra/src/component/tuning/volume/down.rs | 5 +- lyra/src/component/tuning/volume/set.rs | 8 +- .../component/tuning/volume/toggle_mute.rs | 2 +- lyra/src/component/tuning/volume/up.rs | 16 +- lyra/src/core/const.rs | 11 +- lyra/src/core/model.rs | 7 +- lyra/src/core/model/interaction.rs | 48 +- lyra/src/core/traced.rs | 10 +- lyra/src/error.rs | 73 +- lyra/src/error/command.rs | 303 ++++---- lyra/src/error/command/check.rs | 83 ++- lyra/src/error/command/declare.rs | 26 +- lyra/src/error/command/require.rs | 15 + lyra/src/error/command/util.rs | 2 +- lyra/src/error/component.rs | 1 + lyra/src/error/component/playback.rs | 8 + lyra/src/error/component/queue.rs | 14 +- lyra/src/error/gateway.rs | 6 +- lyra/src/gateway/guild.rs | 3 +- lyra/src/gateway/interaction.rs | 53 +- lyra/src/gateway/model.rs | 20 +- lyra/src/gateway/shard.rs | 3 +- lyra/src/gateway/voice.rs | 26 +- lyra/src/lavalink.rs | 8 +- lyra/src/lavalink/model.rs | 87 ++- lyra/src/lavalink/model/connection.rs | 12 +- lyra/src/lavalink/model/pitch.rs | 8 +- lyra/src/lavalink/model/queue.rs | 125 ++-- lyra/src/lavalink/model/queue_indexer.rs | 4 +- lyra/src/lavalink/track.rs | 59 +- lyra/src/main.rs | 5 + lyra/src/runner.rs | 12 +- lyra_ext/Cargo.toml | 11 +- lyra_ext/src/image/limit_file_size.rs | 83 +-- lyra_ext/src/iter/chunked_range.rs | 360 +++++----- lyra_ext/src/iter/multi_interleave.rs | 181 ++--- lyra_ext/src/lib.rs | 3 + lyra_ext/src/num.rs | 13 + lyra_ext/src/pretty/duration_display.rs | 61 +- lyra_ext/src/pretty/flags_display.rs | 10 +- lyra_ext/src/pretty/join.rs | 28 +- lyra_ext/src/rgb_hex.rs | 54 +- lyra_ext/src/time.rs | 3 +- lyra_ext/src/time/iso8601.rs | 10 + lyra_ext/src/time/rfc3339.rs | 10 - lyra_ext/src/time/track_timestamp.rs | 460 ++++++++++++ lyra_ext/src/time/unix.rs | 6 +- lyra_proc/Cargo.toml | 9 +- lyra_proc/src/command.rs | 59 +- lyra_proc/src/lib.rs | 9 +- scripts/get-lavalink | 12 + 106 files changed, 3470 insertions(+), 1710 deletions(-) create mode 100644 lyra/src/component/playback/back.rs create mode 100644 lyra/src/component/playback/jump.rs create mode 100644 lyra/src/component/playback/jump/backward.rs create mode 100644 lyra/src/component/playback/jump/first.rs create mode 100644 lyra/src/component/playback/jump/forward.rs create mode 100644 lyra/src/component/playback/jump/to.rs create mode 100644 lyra/src/component/playback/play_pause.rs create mode 100644 lyra/src/component/playback/restart.rs create mode 100644 lyra/src/component/playback/seek.rs create mode 100644 lyra/src/component/playback/seek/backward.rs create mode 100644 lyra/src/component/playback/seek/forward.rs create mode 100644 lyra/src/component/playback/seek/to.rs create mode 100644 lyra/src/component/playback/skip.rs create mode 100644 lyra/src/error/command/require.rs create mode 100644 lyra/src/error/component/playback.rs create mode 100644 lyra_ext/src/num.rs create mode 100644 lyra_ext/src/time/iso8601.rs delete mode 100644 lyra_ext/src/time/rfc3339.rs create mode 100644 lyra_ext/src/time/track_timestamp.rs create mode 100755 scripts/get-lavalink diff --git a/Cargo.lock b/Cargo.lock index ed4eab5..3b3fb11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,7 +24,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom", "once_cell", "version_check", "zerocopy", @@ -198,6 +197,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.6.0" @@ -238,9 +243,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" dependencies = [ "jobserver", "libc", @@ -286,6 +291,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -415,9 +429,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -425,27 +439,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] name = "darling_macro" -version = "0.14.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -461,6 +475,20 @@ dependencies = [ "parking_lot_core", ] +[[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" @@ -489,33 +517,33 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.13.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f59169f400d8087f238c5c0c7db6a28af18681717f3b623227d92f397e938c7" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.13.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ec317cc3e7ef0928b0ca6e4a634a4d6c001672ae210438cf114a83e56b018d" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] name = "derive_builder_macro" -version = "0.13.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870368c3fb35b8031abb378861d4460f573b92238ec2152c927a21f77e3e0127" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] @@ -530,6 +558,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -586,9 +625,14 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.5.3" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] [[package]] name = "eyre" @@ -773,20 +817,6 @@ dependencies = [ "thread_local", ] -[[package]] -name = "generator" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186014d53bc231d0090ef8d6f03e0920c54d85a5ed22f4f2f74315ec56cf83fb" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows 0.54.0", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -838,9 +868,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "git2" -version = "0.18.3" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "232e6a7bfe35766bf715e55a88b39a700596c0ccfd88cd3680b4cdb40d66ef70" +checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags 2.5.0", "libc", @@ -886,22 +916,13 @@ dependencies = [ [[package]] name = "hashlink" -version = "0.8.4" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -970,12 +991,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -983,9 +1004,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" [[package]] name = "hyper" @@ -1035,7 +1056,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.9", + "rustls 0.23.10", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -1063,6 +1084,124 @@ dependencies = [ "tracing", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1071,22 +1210,24 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] name = "image" -version = "0.25.1" +version = "0.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11" +checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10" dependencies = [ "bytemuck", - "byteorder", + "byteorder-lite", "color_quant", "gif", "num-traits", @@ -1112,15 +1253,6 @@ dependencies = [ "hashbrown", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -1170,7 +1302,7 @@ source = "git+https://gitlab.com/vicky5124/lavalink-rs?rev=56fd9191#56fd919119c7 dependencies = [ "arc-swap", "bytes", - "dashmap", + "dashmap 5.5.3", "futures", "http", "http-body-util", @@ -1218,9 +1350,9 @@ checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libgit2-sys" -version = "0.16.2+1.7.2" +version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee4126d8b4ee5c9d9ea891dd875cfdc1e9d0950437179104b183d7d8a74d24e8" +checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", @@ -1236,9 +1368,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" dependencies = [ "cc", "pkg-config", @@ -1272,6 +1404,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lock_api" version = "0.4.12" @@ -1284,23 +1422,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "loom" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "pin-utils", - "scoped-tls", - "tracing", - "tracing-subscriber", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "lyra" @@ -1312,12 +1436,12 @@ dependencies = [ "color-eyre", "const-str", "const_panic", - "dashmap", + "dashmap 6.0.1", "dotenvy", "dotenvy_macro", "futures", "fuzzy-matcher", - "itertools 0.13.0", + "itertools", "lavalink-rs", "linkify", "log", @@ -1354,9 +1478,10 @@ version = "0.8.0" dependencies = [ "bitflags 2.5.0", "const-str", - "heck 0.5.0", + "heck", "image", "kmeans_colors", + "mock_instant", "palette", "rayon", "regex", @@ -1369,8 +1494,8 @@ dependencies = [ name = "lyra_proc" version = "0.8.0" dependencies = [ - "heck 0.4.1", - "itertools 0.12.1", + "heck", + "itertools", "quote", "serde", "syn 2.0.66", @@ -1398,9 +1523,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -1420,13 +1545,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1438,6 +1564,12 @@ dependencies = [ "libm", ] +[[package]] +name = "mock_instant" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcebb6db83796481097dedc7747809243cc81d9ed83e6a938b76d4ea0b249cf" + [[package]] name = "nom" version = "7.1.3" @@ -1520,16 +1652,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_threads" version = "0.1.7" @@ -1556,12 +1678,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oneshot" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071d1cf3298ad8e543dca18217d198cb6a3884443d204757b9624b935ef09fa0" -dependencies = [ - "loom", -] +checksum = "e296cf87e61c9cfc1a61c3c63a0f7f286ed4554e0e22be84e8a38e1d264a2a29" [[package]] name = "openssl-probe" @@ -1613,6 +1732,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1631,7 +1756,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall 0.5.2", "smallvec", "windows-targets 0.52.5", ] @@ -1853,9 +1978,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags 2.5.0", ] @@ -1868,8 +1993,8 @@ checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -1883,13 +2008,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -1900,9 +2025,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "relative-path" @@ -2030,9 +2155,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.9" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ "once_cell", "ring", @@ -2122,12 +2247,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -2178,9 +2297,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] @@ -2197,9 +2316,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -2208,9 +2327,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", @@ -2248,6 +2367,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2330,6 +2461,9 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2368,20 +2502,19 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools 0.12.1", "nom", "unicode_categories", ] [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2392,11 +2525,10 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b" dependencies = [ - "ahash", "atoi", "byteorder", "bytes", @@ -2409,6 +2541,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", + "hashbrown", "hashlink", "hex", "indexmap", @@ -2434,26 +2567,26 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.66", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776" dependencies = [ "dotenvy", "either", - "heck 0.4.1", + "heck", "hex", "once_cell", "proc-macro2", @@ -2465,7 +2598,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.66", "tempfile", "tokio", "url", @@ -2473,12 +2606,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.5.0", "byteorder", "bytes", @@ -2515,12 +2648,12 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.5.0", "byteorder", "crc", @@ -2553,9 +2686,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee" dependencies = [ "atoi", "flume", @@ -2568,12 +2701,18 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "tracing", "url", - "urlencoding", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "stringprep" version = "0.1.5" @@ -2587,9 +2726,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" @@ -2619,18 +2758,29 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "sysinfo" -version = "0.30.12" +version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732ffa00f53e6b2af46208fba5718d9662a421049204e156328b66791ffa15ae" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" dependencies = [ "cfg-if", "core-foundation-sys", "libc", "ntapi", "once_cell", - "windows 0.52.0", + "windows", ] [[package]] @@ -2647,18 +2797,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", @@ -2719,6 +2869,16 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -2736,27 +2896,26 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.0" +version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", @@ -2780,7 +2939,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.9", + "rustls 0.23.10", "rustls-pki-types", "tokio", ] @@ -2891,7 +3050,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.12", + "winnow 0.6.13", ] [[package]] @@ -3033,7 +3192,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a997fdd14e32ebbe80ee87887720e756efed0a7dbf97bd1170eb7c9a3956378b" dependencies = [ "bitflags 2.5.0", - "dashmap", + "dashmap 5.5.3", "serde", "twilight-model", "twilight-util", @@ -3152,7 +3311,7 @@ version = "0.16.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e968572b4e0c2b0e7a05b4b31b1cbc767c7ec0df0407410045ae8c267af6e1fd" dependencies = [ - "dashmap", + "dashmap 5.5.3", "futures-core", "tokio", "tracing", @@ -3252,9 +3411,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", "idna", @@ -3273,6 +3432,18 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "valuable" version = "0.1.0" @@ -3287,9 +3458,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "vergen" -version = "9.0.0-beta.2" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "107dc53b443fe8cc380798abb75ad6b7038281165109afea1f1b28bb47047ed5" +checksum = "c32e7318e93a9ac53693b6caccfb05ff22e04a44c7cf8a279051f24c09da286f" dependencies = [ "anyhow", "cargo_metadata", @@ -3305,9 +3476,9 @@ dependencies = [ [[package]] name = "vergen-git2" -version = "1.0.0-beta.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8875c5d71074bb67118774e3d795ab6fe77c3ae3161cb54e19104cabc49487f1" +checksum = "a62c52cd2b2b8b7ec75fc20111b3022ac3ff83e4fc14b9497cfcfd39c54f9c67" dependencies = [ "anyhow", "derive_builder", @@ -3320,9 +3491,9 @@ dependencies = [ [[package]] name = "vergen-lib" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26ebfba72ba904559f25f41ea1512335b5a46459084258cea0857549d9645187" +checksum = "e06bee42361e43b60f363bad49d63798d0f42fb1768091812270eca00c784720" dependencies = [ "anyhow", "derive_builder", @@ -3407,17 +3578,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.5", -] - -[[package]] -name = "windows" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" -dependencies = [ - "windows-core 0.54.0", + "windows-core", "windows-targets 0.52.5", ] @@ -3430,25 +3591,6 @@ dependencies = [ "windows-targets 0.52.5", ] -[[package]] -name = "windows-core" -version = "0.54.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" -dependencies = [ - "windows-result", - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-result" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" -dependencies = [ - "windows-targets 0.52.5", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3599,13 +3741,49 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ff33f391015ecab21cd092389215eb265ef9496a9a07b6bee7d3529831deda" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -3626,12 +3804,55 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "zune-core" version = "0.4.12" diff --git a/flake.lock b/flake.lock index 2b1bf47..e0afe5f 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1717917954, - "narHash": "sha256-2p4dWAywA85r/4zDBQt8dLUErIVSitdDMEow5xxpU0g=", + "lastModified": 1722187037, + "narHash": "sha256-z7KBGvH/CCW6LojddUac0kSizfUpDAoHbLWPnDUxVNs=", "owner": "cachix", "repo": "devenv", - "rev": "8dab95bb60b6ece337450d80cc67515429652876", + "rev": "0779ee801db9ea0d7f969ac0185ec96b28a4aaf7", "type": "github" }, "original": { @@ -93,11 +93,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1717827974, - "narHash": "sha256-ixopuTeTouxqTxfMuzs6IaRttbT8JqRW5C9Q/57WxQw=", + "lastModified": 1722148092, + "narHash": "sha256-5QS64rfIFDzU1jmZrOK6wyZOCi6Vn/90apWRI6Hy+xk=", "owner": "nix-community", "repo": "fenix", - "rev": "ab655c627777ab5f9964652fe23bbb1dfbd687a8", + "rev": "b39d8959f286dc7b9da91ae92f6af56de0169e87", "type": "github" }, "original": { @@ -417,11 +417,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1717583671, - "narHash": "sha256-+lRAmz92CNUxorqWusgJbL9VE1eKCnQQojglRemzwkw=", + "lastModified": 1722099723, + "narHash": "sha256-61f+rvQAObm/TuBEqYFNUTngm/wXcuNhGtQbAmfZVvY=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "48bbdd6a74f3176987d5c809894ac33957000d19", + "rev": "a46788318cce3b62e14606f70a14896b223ee5ec", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index cc2047d..95c277d 100644 --- a/flake.nix +++ b/flake.nix @@ -57,6 +57,7 @@ # scripts.hello.exec = ""; enterShell = '' + ./scripts/get-lavalink ''; # https://devenv.sh/tests/ diff --git a/lavalink/application.yml b/lavalink/application.yml index 45faf2d..699647e 100644 --- a/lavalink/application.yml +++ b/lavalink/application.yml @@ -29,11 +29,11 @@ plugins: lavalink: plugins: - - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.0.1" + - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.1.1" snapshot: false - dependency: "com.github.topi314.lavasearch:lavasearch-plugin:1.0.0" snapshot: false - - dependency: "dev.lavalink.youtube:youtube-plugin:1.1.0" + - dependency: "dev.lavalink.youtube:youtube-plugin:1.3.0" snapshot: false server: sources: diff --git a/lyra/Cargo.toml b/lyra/Cargo.toml index d272d69..6893f6d 100644 --- a/lyra/Cargo.toml +++ b/lyra/Cargo.toml @@ -11,7 +11,7 @@ build = "build.rs" [build-dependencies] anyhow = "1" -vergen-git2 = { version = "1.0.0-beta.2", features = [ +vergen-git2 = { version = "1.0.0", features = [ "build", "cargo", "rustc", @@ -19,25 +19,18 @@ vergen-git2 = { version = "1.0.0-beta.2", features = [ ] } [lints.rust] -# dead_code = "allow" unsafe_op_in_unsafe_fn = "forbid" -# unsafe_code = "forbid" +# dead_code = "allow" [lints.clippy] multiple_unsafe_ops_per_block = "forbid" undocumented_unsafe_blocks = "forbid" enum_glob_use = "forbid" unwrap_used = "forbid" +try_err = "forbid" pedantic = { level = "deny", priority = -1 } nursery = { level = "deny", priority = -1 } -cast_possible_truncation = "allow" -cast_possible_wrap = "allow" -cast_sign_loss = "allow" -cast_precision_loss = "allow" -significant_drop_tightening = { level = "allow", priority = 1 } -module_name_repetitions = "allow" - [dependencies] lyra_proc = { path = "../lyra_proc" } lyra_ext = { path = "../lyra_ext" } @@ -46,47 +39,47 @@ paste = "1.0.15" const-str = "0.5.7" const_panic = { version = "0.2.8", features = ["derive"] } bitflags = "2.5.0" -dashmap = "5.5.3" +dashmap = "6.0.1" dotenvy = "0.15.7" dotenvy_macro = "0.15.7" -thiserror = "1.0.61" +thiserror = "1.0.63" color-eyre = "0.6.3" futures = "0.3.30" -tokio = { version = "1.38.0", features = [ +tokio = { version = "1.39.2", features = [ "sync", "signal", "rt-multi-thread", "macros", ] } -serde = "1.0.203" -serde_json = "1.0.117" +serde = "1.0.204" +serde_json = "1.0.120" regex = "1.10.5" linkify = "0.10.0" fuzzy-matcher = "0.3.7" -log = "0.4.21" +log = "0.4.22" tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } rand = "0.8.5" itertools = "0.13.0" rayon = "1.10.0" -sqlx = { version = "0.7.4", features = ["postgres", "runtime-tokio-rustls"] } +sqlx = { version = "0.8.0", features = ["postgres", "runtime-tokio-rustls"] } mixbox = "2.0.0" lavalink-rs = { git = "https://gitlab.com/vicky5124/lavalink-rs", rev = "56fd9191", features = [ "twilight16", ] } aho-corasick = "1.1.3" -twilight = "=0.16.0-rc.1" -twilight-cache-inmemory = { version = "=0.16.0-rc.1", features = [ +twilight = "0.16.0-rc.1" +twilight-cache-inmemory = { version = "0.16.0-rc.1", features = [ "permission-calculator", ] } -twilight-gateway = "=0.16.0-rc.1" -twilight-http = "=0.16.0-rc.1" -twilight-model = "=0.16.0-rc.1" -twilight-standby = "=0.16.0-rc.1" -twilight-validate = "=0.16.0-rc.1" -twilight-mention = "=0.16.0-rc.1" -twilight-util = { version = "=0.16.0-rc.1", features = [ +twilight-gateway = "0.16.0-rc.1" +twilight-http = "0.16.0-rc.1" +twilight-model = "0.16.0-rc.1" +twilight-standby = "0.16.0-rc.1" +twilight-validate = "0.16.0-rc.1" +twilight-mention = "0.16.0-rc.1" +twilight-util = { version = "0.16.0-rc.1", features = [ "permission-calculator", "builder", ] } diff --git a/lyra/src/client.rs b/lyra/src/client.rs index dc56775..dbe8dd6 100644 --- a/lyra/src/client.rs +++ b/lyra/src/client.rs @@ -7,7 +7,7 @@ fn parse_directive(parsed: &str) -> tracing_subscriber::filter::Directive { } #[tracing::instrument(err)] -pub async fn run() -> Result<(), super::error::RunError> { +pub async fn run() -> Result<(), super::error::Run> { color_eyre::install()?; dotenvy::dotenv()?; @@ -15,8 +15,7 @@ pub async fn run() -> Result<(), super::error::RunError> { .with_env_filter( tracing_subscriber::EnvFilter::builder() .with_default_directive(tracing::level_filters::LevelFilter::INFO.into()) - .from_env_lossy(), // .add_directive(parse_directive("lyra=trace")), - // .add_directive(parse_directive("lavalink_rs=trace")), + .from_env_lossy(), ) .init(); diff --git a/lyra/src/command/check.rs b/lyra/src/command/check.rs index 53c347f..b04e9f3 100644 --- a/lyra/src/command/check.rs +++ b/lyra/src/command/check.rs @@ -32,7 +32,7 @@ use crate::{ self, AlternateVoteResponse, PollResolvableError, SendSupersededWinNoticeError, UserOnlyInError, }, - CacheResult, + Cache, InVoiceWithSomeoneElse as InVoiceWithSomeoneElseError, InVoiceWithoutUser as InVoiceWithoutUserError, NotUsersTrack as NotUsersTrackError, @@ -47,16 +47,16 @@ use crate::{ CorrectTrackInfo, // DelegateMethods, Event, - LavalinkAware, // PlayerAware, QueueItem, }, + LavalinkAware, }; use super::{ model::{GuildCtx, RespondViaMessage}, poll::{self, Resolution as PollResolution, Topic as PollTopic}, - require::{self, someone_else_in, InVoice, PartialInVoice}, + require::{self, someone_else_in, CurrentTrack, InVoice, PartialInVoice}, }; pub const DJ_PERMISSIONS: Permissions = Permissions::MOVE_MEMBERS.union(Permissions::MUTE_MEMBERS); @@ -107,7 +107,7 @@ pub fn user_is_stage_manager( } pub async fn user_allowed_in(ctx: &Ctx) -> Result<(), check::UserAllowedError> { - let Some(weak) = require::guild_weak(ctx).ok() else { + let Some(weak) = require::guild_ref(ctx).ok() else { return Ok(()); }; @@ -146,7 +146,7 @@ pub async fn user_allowed_in(ctx: &Ctx) -> Result<(), check::UserA let user_allowed_to_use_commands = access_calculator_builder.build().await?.calculate(); if !user_allowed_to_use_commands { - Err(UserNotAllowedError)?; + return Err(UserNotAllowedError.into()); } Ok(()) } @@ -171,7 +171,7 @@ pub async fn user_allowed_to_use( let allowed_to_use_channel = access_calculator_builder.build().await?.calculate(); if !allowed_to_use_channel { - Err(UserNotAllowedError)?; + return Err(UserNotAllowedError.into()); }; Ok(()) } @@ -187,77 +187,96 @@ pub enum InVoiceWithUserResultKind { } pub fn noone_else_in( - channel_id: Id, + in_voice: PartialInVoice, ctx: &GuildCtx, ) -> Result<(), check::UserOnlyInError> { if is_user_dj(ctx) { return Ok(()); } - if someone_else_in(channel_id, ctx)? { - Err(InVoiceWithSomeoneElseError(channel_id))?; + if someone_else_in(in_voice.channel_id(), ctx)? { + return Err(InVoiceWithSomeoneElseError(in_voice).into()); } Ok(()) } +pub struct PollStarter(PollStarterInfo); + pub struct PollStarterInfo { topic: PollTopic, error: PollResolvableError, in_voice: PartialInVoice, } -pub enum PollStarter { - NotRequired, - Required(PollStarterInfo), -} - -impl PollStarter { - pub async fn start( - self, - ctx: &mut GuildCtx, - ) -> Result<(), check::HandlePollError> { - if let Self::Required(s) = self { - return handle_poll(s.error, &s.topic, ctx, &s.in_voice).await; - }; - - Ok(()) - } -} +type InVoiceWithUserOnlyResult = Result<(), check::UserOnlyInError>; impl<'a> InVoiceWithUserResult<'a> { - pub fn only(self) -> Result<(), check::UserOnlyInError> { + pub fn only(self) -> InVoiceWithUserOnlyResult { if matches!(self.kind, InVoiceWithUserResultKind::UserIsDj) { return Ok(()); } - let InVoiceWithUserResult { in_voice, .. } = self; - let channel_id = in_voice.channel_id(); - if someone_else_in(channel_id, &in_voice)? { - Err(InVoiceWithSomeoneElseError(channel_id))?; - } - Ok(()) + user_only_in(&self.in_voice) } +} - pub fn only_else_poll(self, topic: PollTopic) -> CacheResult { - let in_voice = From::from(&self.in_voice); - let Err(error) = self.only() else { - return Ok(PollStarter::NotRequired); +pub trait ResolveWithPoll { + type Error; + fn or_else_try_resolve_with(self, topic: PollTopic) + -> Result, Self::Error>; +} + +impl ResolveWithPoll for InVoiceWithUserOnlyResult { + type Error = Cache; + + fn or_else_try_resolve_with( + self, + topic: PollTopic, + ) -> Result, Self::Error> { + let Err(error) = self else { + return Ok(None); }; match error { check::UserOnlyInError::InVoiceWithSomeoneElse(e) => { - Ok(PollStarter::Required(PollStarterInfo { + let in_voice = e.0.clone(); + Ok(Some(PollStarter(PollStarterInfo { topic, error: check::PollResolvableError::InVoiceWithSomeoneElse(e), in_voice, - })) + }))) } - check::UserOnlyInError::Cache(e) => Err(e)?, + check::UserOnlyInError::Cache(e) => Err(e), } } } -pub fn in_voice_with_user( +pub trait StartPoll { + async fn and_then_start( + self, + ctx: &mut GuildCtx, + ) -> Result<(), check::HandlePollError> + where + Self: Sized; +} + +impl StartPoll for Option { + async fn and_then_start( + self, + ctx: &mut GuildCtx, + ) -> Result<(), check::HandlePollError> + where + Self: Sized, + { + let Some(PollStarter(info)) = self else { + return Ok(()); + }; + + handle_poll(info.error, &info.topic, ctx, &info.in_voice).await + } +} + +pub fn user_in( in_voice: InVoice<'_>, ) -> Result, InVoiceWithoutUserError> { if is_user_dj(&in_voice) { @@ -280,34 +299,25 @@ pub fn in_voice_with_user( }) } -pub fn all_users_track( - positions: impl Iterator, - in_voice_with_user: InVoiceWithUserResult, - queue: &lavalink::Queue, - ctx: &impl AuthorIdAware, -) -> Result<(), check::UsersTrackError> { - if let (Some((position, track)), Err(e)) = ( - positions - .map(|p| (p, &queue[p.get() - 1])) - .find(|(_, t)| t.requester() != ctx.author_id()), - in_voice_with_user.only(), - ) { - return Err(impl_users_track(position, track, e)); +fn user_only_in(in_voice: &InVoice<'_>) -> InVoiceWithUserOnlyResult { + let channel_id = in_voice.channel_id(); + if someone_else_in(channel_id, in_voice)? { + return Err(InVoiceWithSomeoneElseError(PartialInVoice::from(in_voice)).into()); } Ok(()) } fn impl_users_track( - position: NonZeroUsize, track: &QueueItem, + position: NonZeroUsize, user_only_in: UserOnlyInError, ) -> check::UsersTrackError { let channel_id = match user_only_in { - check::UserOnlyInError::InVoiceWithSomeoneElse(e) => e.0, + check::UserOnlyInError::InVoiceWithSomeoneElse(e) => e.0.channel_id(), check::UserOnlyInError::Cache(e) => return e.into(), }; - let title = track.track().info.corrected_title().into(); + let title = track.data().info.corrected_title().into(); let requester = track.requester(); NotUsersTrackError { @@ -319,22 +329,50 @@ fn impl_users_track( .into() } -pub fn users_track( +pub fn track_is_users( + track: &QueueItem, position: NonZeroUsize, in_voice_with_user: InVoiceWithUserResult, - queue: &lavalink::Queue, - ctx: &impl AuthorIdAware, ) -> Result<(), check::UsersTrackError> { - if let Err(e) = in_voice_with_user.only() { - let track = &queue[position.get() - 1]; - if track.requester() != ctx.author_id() { - return Err(impl_users_track(position, track, e)); + let author_id = in_voice_with_user.in_voice.author_id; + if let Err(user_only_in) = in_voice_with_user.only() { + if track.requester() != author_id { + return Err(impl_users_track(track, position, user_only_in)); } } Ok(()) } +pub fn current_track_is_users( + current_track: &CurrentTrack, + in_voice_with_user: InVoiceWithUserResult, +) -> Result<(), check::UsersTrackError> { + track_is_users( + current_track.track, + current_track.position, + in_voice_with_user, + ) +} + +pub fn all_users_track( + queue: &lavalink::Queue, + positions: impl Iterator, + in_voice_with_user: InVoiceWithUserResult, +) -> Result<(), check::UsersTrackError> { + let author_id = in_voice_with_user.in_voice.author_id; + if let (Some((position, track)), Err(user_only_in)) = ( + positions + .map(|p| (p, &queue[p])) + .find(|(_, t)| t.requester() != author_id), + in_voice_with_user.only(), + ) { + return Err(impl_users_track(track, position, user_only_in)); + } + + Ok(()) +} + // async fn currently_playing(ctx: &Ctx) -> Result { // let guild_id = ctx.guild_id(); // let lavalink = ctx.lavalink(); @@ -721,10 +759,11 @@ async fn handle_poll( if is_user_dj(ctx) { connection.dispatch(Event::AlternateVoteDjCast); - Err(check::AnotherPollOngoingError { + return Err(check::AnotherPollOngoingError { message: message.clone(), alternate_vote: Some(AlternateVoteResponse::DjCasted), - })?; + } + .into()); } connection.dispatch(Event::AlternateVoteCast(ctx.author_id().into())); @@ -739,25 +778,29 @@ async fn handle_poll( .await? { if let Event::AlternateVoteCastedAlready(casted) = event { - Err(check::AnotherPollOngoingError { + return Err(check::AnotherPollOngoingError { message: message.clone(), alternate_vote: Some(AlternateVoteResponse::CastedAlready(casted)), - })?; + } + .into()); } - Err(check::AnotherPollOngoingError { + return Err(check::AnotherPollOngoingError { message: message.clone(), alternate_vote: Some(AlternateVoteResponse::CastDenied), - })?; + } + .into()); } - Err(check::AnotherPollOngoingError { + return Err(check::AnotherPollOngoingError { message: message.clone(), alternate_vote: Some(AlternateVoteResponse::Casted), - })?; + } + .into()); } - Err(check::AnotherPollOngoingError { + return Err(check::AnotherPollOngoingError { message, alternate_vote: None, - })?; + } + .into()); } drop(connection); @@ -768,12 +811,14 @@ async fn handle_poll( PollResolution::UnanimousLoss => Err(check::PollLossError { source: error, kind: check::PollLossErrorKind::UnanimousLoss, - })?, + } + .into()), PollResolution::TimedOut => Err(check::PollLossError { source: error, kind: check::PollLossErrorKind::TimedOut, - })?, - PollResolution::Voided(e) => Err(check::PollVoidedError(e))?, + } + .into()), + PollResolution::Voided(e) => Err(check::PollVoidedError(e).into()), PollResolution::SupersededWinViaDj => { traced::tokio_spawn(send_superseded_win_notice( ctx.interaction_token().to_owned(), @@ -784,7 +829,8 @@ async fn handle_poll( PollResolution::SupersededLossViaDj => Err(check::PollLossError { source: error, kind: check::PollLossErrorKind::SupersededLossViaDj, - })?, + } + .into()), } } diff --git a/lyra/src/command/declare.rs b/lyra/src/command/declare.rs index c57178c..1f68de8 100644 --- a/lyra/src/command/declare.rs +++ b/lyra/src/command/declare.rs @@ -15,6 +15,7 @@ use crate::{ config::Config, connection::{Join, Leave}, misc::Ping, + playback::{Jump, JumpAutocomplete, PlayPause, Restart, Seek}, queue::{ AddToQueue, Clear, FairQueue, Move, MoveAutocomplete, Play, PlayAutocomplete, PlayFile, Remove, RemoveAutocomplete, RemoveRange, RemoveRangeAutocomplete, Repeat, Shuffle, @@ -191,6 +192,10 @@ declare_slash_commands![ Filter, Speed, Equaliser, + PlayPause, + Seek, + Restart, + Jump, ]; declare_message_commands![AddToQueue,]; @@ -199,4 +204,5 @@ declare_autocomplete![ Remove => RemoveAutocomplete, RemoveRange => RemoveRangeAutocomplete, Move => MoveAutocomplete, + Jump => JumpAutocomplete, ]; diff --git a/lyra/src/command/model.rs b/lyra/src/command/model.rs index 7950aa3..86195af 100644 --- a/lyra/src/command/model.rs +++ b/lyra/src/command/model.rs @@ -10,21 +10,22 @@ use twilight_model::{ channel::Channel, guild::{PartialMember, Permissions}, id::{ - marker::{ChannelMarker, CommandMarker, GenericMarker, UserMarker}, + marker::{ChannelMarker, GenericMarker, UserMarker}, Id, }, - user::User, + user::User as TwilightUser, }; use crate::error::{command::AutocompleteResult, CommandResult}; pub use self::ctx::{ - AutocompleteCtx, CommandDataAware, Ctx, CtxKind, GuildCtx, GuildModalCtx, MessageCtx, - RespondViaMessage, RespondViaModal, SlashCtx, UserCtx, WeakGuildCtx, + Autocomplete as AutocompleteCtx, CommandDataAware, Ctx, Guild as GuildCtx, + GuildModal as GuildModalCtx, GuildRef as GuildCtxRef, Kind as CtxKind, Message as MessageCtx, + RespondViaMessage, RespondViaModal, Slash as SlashCtx, User, }; pub trait NonPingInteraction { - unsafe fn author_unchecked(&self) -> &User; + unsafe fn author_unchecked(&self) -> &TwilightUser; unsafe fn author_id_unchecked(&self) -> Id { // SAFETY: interaction type is not `Ping`, so an author exists let author = unsafe { self.author_unchecked() }; @@ -39,7 +40,7 @@ pub trait NonPingInteraction { } impl NonPingInteraction for Interaction { - unsafe fn author_unchecked(&self) -> &User { + unsafe fn author_unchecked(&self) -> &TwilightUser { // SAFETY: interaction type is not `Ping`, so an author exists unsafe { self.author().unwrap_unchecked() } } @@ -69,7 +70,6 @@ impl GuildInteraction for Interaction { #[derive(Debug)] pub struct PartialCommandData { - pub id: Id, pub name: Arc, pub target_id: Option>, pub resolved: Option, @@ -79,7 +79,6 @@ pub struct PartialCommandData { impl PartialCommandData { pub fn new(data: &CommandData) -> Self { Self { - id: data.id, name: data.name.to_string().into(), target_id: data.target_id, resolved: data.resolved.clone(), @@ -103,7 +102,7 @@ pub trait BotSlashCommand: CommandInfoAware { } pub trait BotUserCommand: CommandInfoAware { - async fn run(ctx: UserCtx) -> CommandResult; + async fn run(ctx: User) -> CommandResult; } pub trait BotMessageCommand: CommandInfoAware { diff --git a/lyra/src/command/model/ctx.rs b/lyra/src/command/model/ctx.rs index 9812b7a..84df11c 100644 --- a/lyra/src/command/model/ctx.rs +++ b/lyra/src/command/model/ctx.rs @@ -14,10 +14,10 @@ use twilight_model::{ gateway::payload::incoming::InteractionCreate, guild::{PartialMember, Permissions}, id::{ - marker::{ChannelMarker, GuildMarker, UserMarker}, + marker::{ChannelMarker, GuildMarker as TwilightGuildMarker, UserMarker}, Id, }, - user::User, + user::User as TwilightUser, }; use crate::{ @@ -30,25 +30,26 @@ use crate::{ command::RespondError, core::DeserializeBodyFromHttpError, Cache, CacheResult, NotInGuild, }, gateway::{GuildIdAware, OptionallyGuildIdAware, SenderAware}, - lavalink::{Lavalink, LavalinkAware, PlayerAware}, + lavalink::Lavalink, + LavalinkAndGuildIdAware, LavalinkAware, }; use super::PartialInteractionData; -use self::modal::ModalMarker; +use self::modal::Marker as ModalMarker; pub use self::{ - autocomplete::AutocompleteCtx, - command_data::CommandDataAware, - menu::{MessageCtx, UserCtx}, - message::RespondViaMessage, - modal::{GuildModalCtx, RespondViaModal}, + autocomplete::Autocomplete, + command_data::Aware as CommandDataAware, + menu::{Message, User}, + message::RespondVia as RespondViaMessage, + modal::{Guild as GuildModal, RespondVia as RespondViaModal}, }; type RespondResult = Result; type UnitRespondResult = RespondResult<()>; -type CachedBotMember<'a> = Reference<'a, (Id, Id), CachedMember>; +type CachedBotMember<'a> = Reference<'a, (Id, Id), CachedMember>; -pub trait CtxKind {} +pub trait Kind {} pub trait AppCtxKind {} @@ -56,27 +57,31 @@ pub struct SlashAppMarker; impl AppCtxKind for SlashAppMarker {} pub struct AppCtxMarker(PhantomData T>); -impl CtxKind for AppCtxMarker {} +impl Kind for AppCtxMarker {} pub type SlashMarker = AppCtxMarker; -pub type SlashCtx = Ctx; -pub type GuildSlashCtx = Ctx; +pub type Slash = Ctx; +pub type GuildSlash = Ctx; pub struct ComponentMarker; -impl CtxKind for ComponentMarker {} +impl Kind for ComponentMarker {} -pub type ComponentCtx = Ctx; +pub type Component = Ctx; -pub trait CtxLocation {} +pub trait Location {} pub struct Unknown; -impl CtxLocation for Unknown {} +impl Location for Unknown {} -pub struct Guild; -impl CtxLocation for Guild {} -pub type GuildCtx = Ctx; +pub struct GuildMarker; +impl Location for GuildMarker {} +pub type Guild = Ctx; -pub struct Ctx { +pub struct Ctx +where + Of: Kind, + In: Location, +{ inner: Box, bot: OwnedBotState, latency: Latency, @@ -87,7 +92,7 @@ pub struct Ctx { location: PhantomData In>, } -impl TryFrom> for Ctx { +impl TryFrom> for Ctx { type Error = NotInGuild; fn try_from(value: Ctx) -> Result { @@ -100,12 +105,12 @@ impl TryFrom> for Ctx { data: value.data, acknowledged: value.acknowledged, kind: value.kind, - location: PhantomData:: Guild>, + location: PhantomData:: GuildMarker>, }) } } -impl Ctx { +impl Ctx { pub fn into_modal_interaction(self, inner: Box) -> Ctx { Ctx { inner, @@ -145,7 +150,7 @@ impl Ctx { unsafe { self.inner.channel_unchecked() } } - pub fn author(&self) -> &User { + pub fn author(&self) -> &TwilightUser { // SAFETY: Interaction type is not `Ping`, so `author()` is present. unsafe { self.inner.author_unchecked() } } @@ -162,7 +167,7 @@ impl Ctx { Ok(self.bot.interaction().await?.interfaces(&self.inner)) } - pub unsafe fn guild_id_unchecked(&self) -> Id { + pub unsafe fn guild_id_unchecked(&self) -> Id { // SAFETY: this interaction was invoked in a guild, // so `self.inner.guild_id` is present unsafe { self.get_guild_id().unwrap_unchecked() } @@ -190,7 +195,7 @@ impl Ctx { } } -impl Ctx { +impl Ctx { pub fn bot_member(&self) -> CacheResult { self.cache() .member(self.guild_id(), self.bot().user_id()) @@ -249,66 +254,66 @@ impl Ctx { } } -impl BotStateAware for Ctx { +impl BotStateAware for Ctx { fn bot(&self) -> &BotState { &self.bot } } -impl OwnedBotStateAware for Ctx { +impl OwnedBotStateAware for Ctx { fn bot_owned(&self) -> Arc { self.bot.clone() } } -impl SenderAware for Ctx { +impl SenderAware for Ctx { fn sender(&self) -> &MessageSender { &self.sender } } -impl CacheAware for Ctx { +impl CacheAware for Ctx { fn cache(&self) -> &InMemoryCache { self.bot.cache() } } -impl HttpAware for Ctx { +impl HttpAware for Ctx { fn http(&self) -> &HttpClient { self.bot.http() } } -impl LavalinkAware for Ctx { +impl LavalinkAware for Ctx { fn lavalink(&self) -> &Lavalink { self.bot.lavalink() } } -impl PlayerAware for GuildCtx {} +impl LavalinkAndGuildIdAware for Guild {} -impl OptionallyGuildIdAware for Ctx { - fn get_guild_id(&self) -> Option> { +impl OptionallyGuildIdAware for Ctx { + fn get_guild_id(&self) -> Option> { self.inner.guild_id } } -impl AuthorIdAware for Ctx { +impl AuthorIdAware for Ctx { fn author_id(&self) -> Id { self.author().id } } -impl GuildIdAware for Ctx { +impl GuildIdAware for Ctx { #[inline] - fn guild_id(&self) -> Id { + fn guild_id(&self) -> Id { // SAFETY: `Ctx<_, Guild>` is proven to be of an interaction that was invoked in a guild, // so `self.guild_id_unchecked()` is safe. unsafe { self.guild_id_unchecked() } } } -impl AuthorPermissionsAware for Ctx { +impl AuthorPermissionsAware for Ctx { fn author_permissions(&self) -> Permissions { // SAFETY: `Ctx<_, Guild>` is proven to be of an interaction that was invoked in a guild, // so `self.author_permissions_unchecked()` is safe. @@ -316,9 +321,9 @@ impl AuthorPermissionsAware for Ctx { } } -pub struct WeakGuildCtx<'a, T: CtxKind>(&'a Ctx); +pub struct GuildRef<'a, T: Kind>(&'a Ctx); -impl<'a, T: CtxKind> TryFrom<&'a Ctx> for WeakGuildCtx<'a, T> { +impl<'a, T: Kind> TryFrom<&'a Ctx> for GuildRef<'a, T> { type Error = NotInGuild; fn try_from(value: &'a Ctx) -> Result { @@ -327,8 +332,8 @@ impl<'a, T: CtxKind> TryFrom<&'a Ctx> for WeakGuildCtx<'a, T> { } } -impl GuildIdAware for WeakGuildCtx<'_, T> { - fn guild_id(&self) -> Id { +impl GuildIdAware for GuildRef<'_, T> { + fn guild_id(&self) -> Id { // SAFETY: `self.0.get_guild_id()` is proven to be present from `Self::try_from`, // proving that this was an interaction invoked in a guild, // so `self.0.guild_id_unchecked()` is safe. @@ -336,7 +341,7 @@ impl GuildIdAware for WeakGuildCtx<'_, T> { } } -impl WeakGuildCtx<'_, T> { +impl GuildRef<'_, T> { pub fn member(&self) -> &PartialMember { // SAFETY: `self.0.get_guild_id()` is proven to be present from `Self::try_from`, // proving that this was an interaction invoked in a guild, @@ -345,7 +350,7 @@ impl WeakGuildCtx<'_, T> { } } -impl AuthorPermissionsAware for WeakGuildCtx<'_, T> { +impl AuthorPermissionsAware for GuildRef<'_, T> { fn author_permissions(&self) -> Permissions { // SAFETY: `self.0.get_guild_id()` is proven to be present from `Self::try_from`, // proving that this was an interaction invoked in a guild, diff --git a/lyra/src/command/model/ctx/autocomplete.rs b/lyra/src/command/model/ctx/autocomplete.rs index 33aa2c5..3b889fb 100644 --- a/lyra/src/command/model/ctx/autocomplete.rs +++ b/lyra/src/command/model/ctx/autocomplete.rs @@ -1,13 +1,13 @@ use twilight_model::application::command::CommandOptionChoice; -use super::{Ctx, CtxKind, CtxLocation, Guild, UnitRespondResult}; +use super::{Ctx, GuildMarker, Kind, Location, UnitRespondResult}; -pub struct AutocompleteMarker; -impl CtxKind for AutocompleteMarker {} -pub type AutocompleteCtx = Ctx; -pub type GuildAutocompleteCtx = Ctx; +pub struct Marker; +impl Kind for Marker {} +pub type Autocomplete = Ctx; +pub type GuildAutocompleteCtx = Ctx; -impl Ctx { +impl Ctx { pub async fn autocomplete( &mut self, choices: impl IntoIterator + Send, diff --git a/lyra/src/command/model/ctx/command_data.rs b/lyra/src/command/model/ctx/command_data.rs index 1da9bc9..56869e4 100644 --- a/lyra/src/command/model/ctx/command_data.rs +++ b/lyra/src/command/model/ctx/command_data.rs @@ -13,15 +13,13 @@ use crate::{ core::model::OwnedBotState, }; -use super::{ - autocomplete::AutocompleteMarker, AppCtxKind, AppCtxMarker, Ctx, CtxKind, CtxLocation, -}; +use super::{autocomplete::Marker, AppCtxKind, AppCtxMarker, Ctx, Kind, Location}; -pub trait CommandDataAware: CtxKind {} -impl CommandDataAware for AppCtxMarker {} -impl CommandDataAware for AutocompleteMarker {} +pub trait Aware: Kind {} +impl Aware for AppCtxMarker {} +impl Aware for Marker {} -impl Ctx { +impl Ctx { pub fn from_partial_data( inner: Box, data: &CommandData, @@ -44,7 +42,7 @@ impl Ctx { } } -impl Ctx { +impl Ctx { pub fn command_data(&self) -> &PartialCommandData { // SAFETY: `self` is `Ctx`, // so `self.data` is present @@ -94,8 +92,4 @@ impl Ctx { .join(" ") .into() } - - pub fn command_mention_full(&self) -> Box { - format!("", self.command_name_full(), self.command_data().id).into() - } } diff --git a/lyra/src/command/model/ctx/menu.rs b/lyra/src/command/model/ctx/menu.rs index bd9b1f3..d4b6480 100644 --- a/lyra/src/command/model/ctx/menu.rs +++ b/lyra/src/command/model/ctx/menu.rs @@ -1,32 +1,32 @@ use twilight_model::{ application::interaction::InteractionDataResolved, - channel::Message, + channel::Message as TwilightMessage, id::{ marker::{ GenericMarker, MessageMarker as TwilightMessageMarker, UserMarker as TwilightUserMarker, }, Id, }, - user::User, + user::User as TwilightUser, }; -use super::{AppCtxKind, AppCtxMarker, Ctx, CtxLocation}; +use super::{AppCtxKind, AppCtxMarker, Ctx, Location}; pub struct UserAppMarker; impl AppCtxKind for UserAppMarker {} pub type UserMarker = AppCtxMarker; -pub type UserCtx = Ctx; +pub type User = Ctx; pub struct MessageAppMarker; impl AppCtxKind for MessageAppMarker {} pub type MessageMarker = AppCtxMarker; -pub type MessageCtx = Ctx; +pub type Message = Ctx; pub trait TargetIdAware: AppCtxKind {} impl TargetIdAware for UserAppMarker {} impl TargetIdAware for MessageAppMarker {} -impl Ctx, U> { +impl Ctx, U> { pub fn target_id(&self) -> Id { // SAFETY: `self` is `Ctx`, // so `self.partial_command_data().target_id` is present @@ -40,13 +40,13 @@ impl Ctx, U> { } } -impl Ctx { +impl Ctx { #[inline] pub fn target_user_id(&self) -> Id { self.target_id().cast() } - pub fn target_user(&self) -> &User { + pub fn target_user(&self) -> &TwilightUser { // SAFETY: `self` is `Ctx`, // so `self.resolved_data().users.get(&self.target_user_id())` is present unsafe { @@ -58,13 +58,13 @@ impl Ctx { } } -impl Ctx { +impl Ctx { #[inline] pub fn target_message_id(&self) -> Id { self.target_id().cast() } - pub fn target_message(&self) -> &Message { + pub fn target_message(&self) -> &TwilightMessage { // SAFETY: `self` is `Ctx`, // so `self.resolved_data().messages.get(&self.target_message_id())` is present unsafe { diff --git a/lyra/src/command/model/ctx/message.rs b/lyra/src/command/model/ctx/message.rs index e509238..fbe68d0 100644 --- a/lyra/src/command/model/ctx/message.rs +++ b/lyra/src/command/model/ctx/message.rs @@ -7,19 +7,18 @@ use twilight_util::builder::InteractionResponseDataBuilder; use crate::{core::model::MessageResponse, error::command::FollowupError}; use super::{ - AppCtxKind, AppCtxMarker, ComponentMarker, Ctx, CtxKind, CtxLocation, ModalMarker, - RespondResult, + AppCtxKind, AppCtxMarker, ComponentMarker, Ctx, Kind, Location, ModalMarker, RespondResult, }; type MessageRespondResult = RespondResult; type MessageFollowupResult = Result; -pub trait RespondViaMessage: CtxKind {} -impl RespondViaMessage for AppCtxMarker {} -impl RespondViaMessage for ModalMarker {} -impl RespondViaMessage for ComponentMarker {} +pub trait RespondVia: Kind {} +impl RespondVia for AppCtxMarker {} +impl RespondVia for ModalMarker {} +impl RespondVia for ComponentMarker {} -impl Ctx { +impl Ctx { fn base_response_data_builder() -> InteractionResponseDataBuilder { InteractionResponseDataBuilder::new().allowed_mentions(AllowedMentions::default()) } @@ -38,7 +37,7 @@ impl Ctx { self.respond_with(Some(data)).await } - pub async fn update_no_components_embeds(&mut self, content: &str) -> MessageFollowupResult { + pub async fn update_no_components_embeds(&self, content: &str) -> MessageFollowupResult { Ok(self .interface() .await? diff --git a/lyra/src/command/model/ctx/modal.rs b/lyra/src/command/model/ctx/modal.rs index 135beed..91611bf 100644 --- a/lyra/src/command/model/ctx/modal.rs +++ b/lyra/src/command/model/ctx/modal.rs @@ -4,19 +4,19 @@ use twilight_model::{ }; use super::{ - AppCtxKind, AppCtxMarker, ComponentMarker, Ctx, CtxKind, CtxLocation, Guild, UnitRespondResult, + AppCtxKind, AppCtxMarker, ComponentMarker, Ctx, GuildMarker, Kind, Location, UnitRespondResult, }; -pub struct ModalMarker; -impl CtxKind for ModalMarker {} -pub type ModalCtx = Ctx; -pub type GuildModalCtx = Ctx; +pub struct Marker; +impl Kind for Marker {} +pub type Modal = Ctx; +pub type Guild = Ctx; -pub trait RespondViaModal: CtxKind {} -impl RespondViaModal for AppCtxMarker {} -impl RespondViaModal for ComponentMarker {} +pub trait RespondVia: Kind {} +impl RespondVia for AppCtxMarker {} +impl RespondVia for ComponentMarker {} -impl Ctx { +impl Ctx { pub fn submit_data(&self) -> &ModalInteractionData { let Some(InteractionData::ModalSubmit(ref data)) = self.inner.data else { // SAFETY: `self` is `Ctx`, @@ -27,7 +27,7 @@ impl Ctx { } } -impl Ctx { +impl Ctx { pub async fn modal( &mut self, custom_id: impl Into + Send, diff --git a/lyra/src/command/poll.rs b/lyra/src/command/poll.rs index 80d992a..c15fd2c 100644 --- a/lyra/src/command/poll.rs +++ b/lyra/src/command/poll.rs @@ -41,7 +41,8 @@ use crate::{ Cache as CacheError, }, gateway::GuildIdAware, - lavalink::{Event, EventRecvResult, LavalinkAware}, + lavalink::{Event, EventRecvResult}, + LavalinkAware, }; use super::{ @@ -373,10 +374,10 @@ impl EmbedUpdate<'_> { } } -fn calculate_vote_ratios_and_votes( +fn calculate_votes_and_ratios( votes: &HashMap, Vote>, threshold: usize, -) -> ((usize, usize), (f32, f32, f32)) { +) -> ((usize, usize), (f64, f64, f64)) { let total_votes = votes.len(); let upvotes = votes .values() @@ -385,9 +386,18 @@ fn calculate_vote_ratios_and_votes( .count(); let downvotes = total_votes - upvotes; let votes_left = threshold - total_votes; - let upvote_ratio = upvotes as f32 / threshold as f32; - let downvote_ratio = downvotes as f32 / threshold as f32; - let votes_left_ratio = votes_left as f32 / threshold as f32; + + #[allow(clippy::cast_precision_loss)] + let (threshold_f64, upvotes_f64, downvotes_f64, votes_left_f64) = ( + threshold as f64, + upvotes as f64, + downvotes as f64, + votes_left as f64, + ); + + let upvote_ratio = upvotes_f64 / threshold_f64; + let downvote_ratio = downvotes_f64 / threshold_f64; + let votes_left_ratio = votes_left_f64 / threshold_f64; ( (upvotes, downvotes), @@ -398,8 +408,8 @@ fn calculate_vote_ratios_and_votes( fn calculate_vote_ratios( votes: &HashMap, Vote>, threshold: usize, -) -> (f32, f32, f32) { - calculate_vote_ratios_and_votes(votes, threshold).1 +) -> (f64, f64, f64) { + calculate_votes_and_ratios(votes, threshold).1 } fn generate_embed_colour( @@ -411,11 +421,16 @@ fn generate_embed_colour( let mut z_mix = [0.0; mixbox::LATENT_SIZE]; for (i, z) in z_mix.iter_mut().enumerate() { *z = votes_left_ratio.mul_add( - latent.base[i], - upvote_ratio.mul_add(latent.upvote[i], downvote_ratio * latent.downvote[i]), + f64::from(latent.base[i]), + upvote_ratio.mul_add( + f64::from(latent.upvote[i]), + downvote_ratio * f64::from(latent.downvote[i]), + ), ); } - mixbox::latent_to_rgb(&z_mix) + + #[allow(clippy::cast_possible_truncation)] + mixbox::latent_to_rgb(&z_mix.map(|f| f as f32)) } async fn update_embed( @@ -447,7 +462,12 @@ pub async fn start( let users_in_voice = get_users_in_voice(ctx, in_voice)?; let votes = HashMap::from([(ctx.author_id(), Vote(true))]); - let threshold = ((users_in_voice.len() + 1) as f64 / 2.).round() as usize; + + #[allow(clippy::cast_precision_loss)] + let users_in_voice_plus_1 = (users_in_voice.len() + 1) as f64; + + #[allow(clippy::cast_possible_truncation)] + let threshold = ((users_in_voice_plus_1 / 2.).round() as isize).unsigned_abs(); let embed = generate_embed( topic, @@ -504,7 +524,7 @@ fn calculate_vote_resolution( threshold: usize, ) -> Option { let res = votes.values().copied().map(Vote::value).sum::(); - if res.max(0) as usize == threshold { + if res.unsigned_abs() == threshold { if res.is_positive() { return Some(Resolution::UnanimousWin); } @@ -515,11 +535,16 @@ fn calculate_vote_resolution( fn generate_poll_description(votes: &HashMap, Vote>, threshold: usize) -> String { let ((upvotes, downvotes), (upvote_ratio, downvote_ratio, _)) = - calculate_vote_ratios_and_votes(votes, threshold); - let ratio_bar_size = RATIO_BAR_SIZE as f32; + calculate_votes_and_ratios(votes, threshold); - let upvote_char_n = (upvote_ratio * ratio_bar_size) as usize; - let downvote_char_n = (downvote_ratio * ratio_bar_size) as usize; + #[allow(clippy::cast_precision_loss)] + let ratio_bar_size = RATIO_BAR_SIZE as f64; + + #[allow(clippy::cast_possible_truncation)] + let (upvote_char_n, downvote_char_n) = ( + ((upvote_ratio * ratio_bar_size) as isize).unsigned_abs(), + ((downvote_ratio * ratio_bar_size) as isize).unsigned_abs(), + ); let votes_left_char_n = RATIO_BAR_SIZE - upvote_char_n - downvote_char_n; format!( @@ -630,7 +655,7 @@ async fn wait_for_votes( PollAction::Void(e) => return Ok(Resolution::Voided(e)), }, Ok(Ok(None)) => {} - Ok(Err(e)) => Err(e)?, + Ok(Err(e)) => return Err(e.into()), Err(_) => return Ok(Resolution::TimedOut), } } diff --git a/lyra/src/command/require.rs b/lyra/src/command/require.rs index 85690d1..914fb50 100644 --- a/lyra/src/command/require.rs +++ b/lyra/src/command/require.rs @@ -1,3 +1,5 @@ +use std::{num::NonZeroUsize, time::Duration}; + use lavalink_rs::{ error::LavalinkResult, model::player::Player as PlayerInfo, player_context::PlayerContext, }; @@ -14,54 +16,83 @@ use twilight_model::{ use crate::{ core::model::{AuthorIdAware, AuthorPermissionsAware, CacheAware}, error::{ - command::check, lavalink::NoPlayerError, Cache, CacheResult, InVoiceWithoutSomeoneElse, - NotInGuild, NotInVoice, QueueEmpty, Suppressed, + command::require::{InVoiceWithSomeoneElseError, UnsuppressedError}, + lavalink::NoPlayerError, + Cache, CacheResult, InVoiceWithoutSomeoneElse, NotInGuild, NotInVoice, NotPlaying, + QueueEmpty, Suppressed, }, gateway::GuildIdAware, - lavalink::{PlayerAware, PlayerDataRwLockArc, UnwrappedPlayerData}, + lavalink::{ + OwnedPlayerData, PlayerDataRead, PlayerDataWrite, Queue, QueueItem, UnwrappedPlayerData, + }, + LavalinkAndGuildIdAware, }; -use super::model::{Ctx, CtxKind, GuildCtx, WeakGuildCtx}; +use super::model::{Ctx, CtxKind, GuildCtx, GuildCtxRef}; pub fn guild(ctx: Ctx) -> Result, NotInGuild> { GuildCtx::try_from(ctx) } -pub fn guild_weak(ctx: &Ctx) -> Result, NotInGuild> { - WeakGuildCtx::try_from(ctx) +pub fn guild_ref(ctx: &Ctx) -> Result, NotInGuild> { + GuildCtxRef::try_from(ctx) +} + +pub fn player(cx: &impl LavalinkAndGuildIdAware) -> Result { + let context = cx.get_player().ok_or(NoPlayerError)?; + Ok(PlayerInterface { context }) } -pub struct Player { +pub struct PlayerInterface { pub context: PlayerContext, } -impl Player { +impl PlayerInterface { pub async fn info(&self) -> LavalinkResult { self.context.get_player().await } - pub fn data(&self) -> PlayerDataRwLockArc { + pub fn data(&self) -> OwnedPlayerData { self.context.data_unwrapped() } - pub async fn and_queue_not_empty(self) -> Result { - if self.data().read().await.queue().is_empty() { - return Err(QueueEmpty); - } + pub async fn acquire_advance_lock_and_stop_with(&self, queue: &Queue) -> LavalinkResult<()> { + queue.acquire_advance_lock(); + self.context.stop_now().await?; + Ok(()) + } - Ok(self) + pub async fn seek_to_with<'data, 'guard>( + &self, + timestamp: Duration, + data_w: &'guard mut PlayerDataWrite<'data>, + ) -> LavalinkResult<()> { + data_w.seek_to(timestamp); + self.context.set_position(timestamp).await?; + Ok(()) + } + + pub async fn set_pause(&self, state: bool) -> LavalinkResult<()> { + let data = self.data(); + let mut data_w = data.write().await; + self.set_pause_with(state, &mut data_w).await } -} -pub fn player(ctx: &impl PlayerAware) -> Result { - let context = ctx.get_player().ok_or(NoPlayerError)?; - Ok(Player { context }) + pub async fn set_pause_with<'data, 'guard>( + &self, + state: bool, + data_w: &'guard mut PlayerDataWrite<'data>, + ) -> LavalinkResult<()> { + data_w.set_pause(state); + self.context.set_pause(state).await?; + Ok(()) + } } pub type CachedVoiceStateRef<'a> = Reference<'a, (Id, Id), CachedVoiceState>; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct InVoiceCachedVoiceState { guild_id: Id, channel_id: Id, @@ -89,16 +120,21 @@ pub struct InVoice<'a> { } #[must_use] +#[derive(Debug, Clone)] pub struct PartialInVoice { state: InVoiceCachedVoiceState, - pub author_id: Id, +} + +impl PartialInVoice { + pub const fn channel_id(&self) -> Id { + self.state.channel_id + } } impl From<&InVoice<'_>> for PartialInVoice { fn from(value: &InVoice<'_>) -> Self { Self { state: value.state.clone(), - author_id: value.author_id, } } } @@ -132,27 +168,27 @@ impl<'a> InVoice<'a> { self.state.channel_id } - pub fn and_unsuppressed(self) -> Result { + pub fn and_unsuppressed(self) -> Result { let state = &self.state; - let voice_state_channel = self.cache.channel(state.channel_id).ok_or(Cache)?; + let voice_state_channel_kind = self.cache.channel(state.channel_id).ok_or(Cache)?.kind; if state.mute { - Err(Suppressed::Muted)?; + return Err(Suppressed::Muted.into()); } let speaker_in_stage = - state.suppress && matches!(voice_state_channel.kind, ChannelType::GuildStageVoice); + state.suppress && matches!(voice_state_channel_kind, ChannelType::GuildStageVoice); if speaker_in_stage { - Err(Suppressed::NotSpeaker)?; + return Err(Suppressed::NotSpeaker.into()); } Ok(self) } - pub fn and_with_someone_else(self) -> Result { + pub fn and_with_someone_else(self) -> Result { let channel_id = self.channel_id(); if !someone_else_in(channel_id, &self)? { - Err(InVoiceWithoutSomeoneElse(channel_id))?; + return Err(InVoiceWithoutSomeoneElse(channel_id).into()); } Ok(self) } @@ -184,14 +220,14 @@ impl GuildIdAware for InVoice<'_> { pub fn someone_else_in( channel_id: Id, - ctx: &(impl CacheAware + AuthorIdAware), + cx: &(impl CacheAware + AuthorIdAware), ) -> CacheResult { - let cache = ctx.cache(); + let cache = cx.cache(); cache .voice_channel_states(channel_id) .and_then(|states| { for state in states { - if !cache.user(state.user_id())?.bot && state.user_id() != ctx.author_id() { + if !cache.user(state.user_id())?.bot && state.user_id() != cx.author_id() { return Some(true); } } @@ -199,3 +235,47 @@ pub fn someone_else_in( }) .ok_or(Cache) } + +fn impl_queue_not_empty(queue: &Queue) -> Result<(), QueueEmpty> { + if queue.is_empty() { + return Err(QueueEmpty); + } + Ok(()) +} + +pub fn queue_not_empty<'guard, 'data, 'borrow>( + data_r: &'guard PlayerDataRead<'data>, +) -> Result<&'borrow Queue, QueueEmpty> +where + 'guard: 'borrow, + 'data: 'borrow, +{ + let queue = data_r.queue(); + impl_queue_not_empty(queue)?; + Ok(queue) +} + +pub fn queue_not_empty_mut<'guard, 'data, 'borrow>( + data_w: &'guard mut PlayerDataWrite<'data>, +) -> Result<&'borrow mut Queue, QueueEmpty> +where + 'guard: 'borrow, + 'data: 'borrow, +{ + let queue = data_w.queue_mut(); + impl_queue_not_empty(queue)?; + Ok(queue) +} + +pub fn current_track(queue: &Queue) -> Result { + let (current, position) = queue.current_and_position(); + Ok(CurrentTrack { + track: current.ok_or(NotPlaying)?, + position, + }) +} + +pub struct CurrentTrack<'a> { + pub track: &'a QueueItem, + pub position: NonZeroUsize, +} diff --git a/lyra/src/command/util.rs b/lyra/src/command/util.rs index 6d19c35..9fbd2cb 100644 --- a/lyra/src/command/util.rs +++ b/lyra/src/command/util.rs @@ -1,5 +1,5 @@ use lavalink_rs::{error::LavalinkResult, player_context::PlayerContext}; -use lyra_ext::time::unix::unix_time; +use lyra_ext::unix_time; use rand::{distributions::Alphanumeric, Rng}; use twilight_gateway::Event; use twilight_model::{ @@ -36,7 +36,7 @@ use crate::{ }, error::{ command::{ - check::NotSuppressedError, + require::UnsuppressedError, util::{ AutoJoinOrCheckInVoiceWithUserError, AutoJoinSuppressedError, HandleSuppressedAutoJoinError, PromptForConfirmationError, @@ -45,7 +45,8 @@ use crate::{ ConfirmationTimedOut, Suppressed as SuppressedError, }, gateway::GuildIdAware, - lavalink::{DelegateMethods, LavalinkAware}, + lavalink::DelegateMethods, + LavalinkAware, }; pub trait MessageLinkAware { @@ -132,7 +133,8 @@ pub trait AvatarUrlAware { let avatar = self.avatar()?; let ext = if avatar.is_animated() { "gif" } else { "png" }; - format!("{}/avatars/{}/{}.{}", CDN_URL, self.id(), avatar, ext).into() + let formatted = format!("{}/avatars/{}/{}.{}", CDN_URL, self.id(), avatar, ext); + Some(formatted) } } @@ -152,15 +154,15 @@ pub trait GuildAvatarUrlAware { let avatar = self.avatar()?; let ext = if avatar.is_animated() { "gif" } else { "png" }; - format!( + let formatted = format!( "{}/guilds/{}/users/{}/avatars/{}.{}", CDN_URL, guild_id, self.id(), avatar, ext, - ) - .into() + ); + Some(formatted) } } @@ -207,14 +209,14 @@ pub async fn auto_join_or_check_in_voice_with_user_and_check_not_suppressed( ) -> Result<(), AutoJoinOrCheckInVoiceWithUserError> { if let Ok(in_voice) = require::in_voice(ctx) { let in_voice = in_voice.and_unsuppressed()?; - check::in_voice_with_user(in_voice)?; + check::user_in(in_voice)?; return Ok(()); } match auto_join(ctx).await { - Err(e) => Err(e.unflatten_into_auto_join_attempt())?, + Err(e) => Err(e.unflatten_into_auto_join_attempt().into()), Ok(state) => { - let Err(NotSuppressedError::Suppressed(suppressed)) = { + let Err(UnsuppressedError::Suppressed(suppressed)) = { // SAFETY: as `auto_join` was called and ran successfully, // there must now be an active voice connection. unsafe { InVoice::new(state, ctx) } @@ -234,7 +236,7 @@ async fn handle_suppressed_auto_join( ) -> Result<(), HandleSuppressedAutoJoinError> { let bot_user_id = ctx.bot().user_id(); match error { - SuppressedError::Muted => Err(AutoJoinSuppressedError::Muted)?, + SuppressedError::Muted => Err(AutoJoinSuppressedError::Muted.into()), SuppressedError::NotSpeaker => { let bot = ctx.bot_owned(); let wait_for_speaker = @@ -268,9 +270,10 @@ async fn handle_suppressed_auto_join( ); if wait_for_speaker.await.is_err() { - Err(AutoJoinSuppressedError::StillNotSpeaker { + return Err(AutoJoinSuppressedError::StillNotSpeaker { last_followup_id: requested_to_speak_message.id, - })?; + } + .into()); } Ok(()) } @@ -337,8 +340,8 @@ pub async fn prompt_for_confirmation( // SAFETY: the future has been filtered to only match modal submit interaction // so this branch is unreachable Ok(Ok(_)) => unsafe { std::hint::unreachable_unchecked() }, - Ok(Err(e)) => Err(e)?, - Err(_) => Err(ConfirmationTimedOut)?, + Ok(Err(e)) => return Err(e.into()), + Err(_) => return Err(ConfirmationTimedOut.into()), }; Ok(ctx_and_confirmed) diff --git a/lyra/src/component/config/access.rs b/lyra/src/component/config/access.rs index 4e97a51..5f52a2b 100644 --- a/lyra/src/component/config/access.rs +++ b/lyra/src/component/config/access.rs @@ -6,7 +6,9 @@ mod view; use bitflags::bitflags; use const_str::concat as const_str_concat; use itertools::Itertools; -use lyra_ext::{logical_bind::LogicalBind, pretty::flags_display::PrettyFlagsDisplay}; +use lyra_ext::{ + logical_bind::LogicalBind, num::u64_to_i64_truncating, pretty::flags_display::FlagsDisplay, +}; use sqlx::{Pool, Postgres}; use tokio::task::JoinSet; use twilight_interactions::command::{CommandModel, CommandOption, CreateCommand, CreateOption}; @@ -54,7 +56,7 @@ pub struct CalculatorBuilder { impl CalculatorBuilder { pub fn new(guild_id: Id, db: Pool) -> Self { - let guild_id = guild_id.get() as i64; + let guild_id = u64_to_i64_truncating(guild_id.get()); Self { set: JoinSet::new(), db, @@ -126,27 +128,27 @@ impl CalculatorBuilder { } pub fn user(self, user_id: Id) -> Self { - let id = user_id.get() as i64; + let id = u64_to_i64_truncating(user_id.get()); self.query(&AccessCategoryFlag::Users, id) } pub fn thread(self, thread_id: Id) -> Self { - let id = thread_id.get() as i64; + let id = u64_to_i64_truncating(thread_id.get()); self.query(&AccessCategoryFlag::Threads, id) } pub fn text_channel(self, text_channel_id: Id) -> Self { - let id = text_channel_id.get() as i64; + let id = u64_to_i64_truncating(text_channel_id.get()); self.query(&AccessCategoryFlag::TextChannels, id) } pub fn voice_channel(self, voice_channel_id: Id) -> Self { - let id = voice_channel_id.get() as i64; + let id = u64_to_i64_truncating(voice_channel_id.get()); self.query(&AccessCategoryFlag::VoiceChannels, id) } pub fn category_channel(self, category_channel_id: Id) -> Self { - let id = category_channel_id.get() as i64; + let id = u64_to_i64_truncating(category_channel_id.get()); self.query(&AccessCategoryFlag::CategoryChannels, id) } @@ -187,7 +189,7 @@ impl TryFrom for AccessCategoryFlag { // SAFETY: `value` is guruanteed to only have one flag, // so this transmute is safe - Ok(unsafe { std::mem::transmute(value.bits()) }) + Ok(unsafe { std::mem::transmute::(value.bits()) }) } } @@ -231,11 +233,12 @@ bitflags! { } } -impl PrettyFlagsDisplay for AccessCategoryFlags {} +impl FlagsDisplay for AccessCategoryFlags {} impl From for AccessCategoryFlags { fn from(category: AccessCategory) -> Self { - Self::from_bits_retain(category.value() as u8) + #[allow(clippy::cast_possible_truncation)] + Self::from_bits_retain(category.value().unsigned_abs() as u8) } } diff --git a/lyra/src/component/config/access/clear.rs b/lyra/src/component/config/access/clear.rs index c3783f4..6cf7cae 100644 --- a/lyra/src/component/config/access/clear.rs +++ b/lyra/src/component/config/access/clear.rs @@ -1,4 +1,4 @@ -use lyra_ext::pretty::flags_display::PrettyFlagsDisplay; +use lyra_ext::{num::u64_to_i64_truncating, pretty::flags_display::FlagsDisplay}; use tokio::task::JoinSet; use twilight_interactions::command::{CommandModel, CreateCommand}; @@ -36,7 +36,7 @@ impl BotSlashCommand for Clear { let mut set = JoinSet::new(); category_flags.iter_as_columns().for_each(|c| { let db = ctx.db().clone(); - let g = ctx.guild_id().get() as i64; + let g = u64_to_i64_truncating(ctx.guild_id().get()); set.spawn(async move { sqlx::query(&format!("DELETE FROM {c} WHERE guild = $1;")) diff --git a/lyra/src/component/config/access/edit.rs b/lyra/src/component/config/access/edit.rs index 678666f..82c512c 100644 --- a/lyra/src/component/config/access/edit.rs +++ b/lyra/src/component/config/access/edit.rs @@ -1,6 +1,7 @@ use std::collections::{HashMap, HashSet}; use itertools::Itertools; +use lyra_ext::num::u64_to_i64_truncating; use sqlx::{postgres::PgQueryResult, Pool, Postgres}; use tokio::task::JoinSet; use twilight_interactions::command::{ @@ -180,7 +181,7 @@ impl BotSlashCommand for MemberRole { let input_mentionables_len = input_mentionables.values().fold(0, |acc, v| acc + v.len()); let database = ctx.db(); - let guild_id = ctx.guild_id().get() as i64; + let guild_id = u64_to_i64_truncating(ctx.guild_id().get()); let mut set = JoinSet::new(); match self.action { EditAction::Add => { @@ -261,7 +262,6 @@ pub struct Channel { target_5: Option, } -#[allow(clippy::too_many_lines)] impl BotSlashCommand for Channel { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; @@ -297,7 +297,7 @@ impl BotSlashCommand for Channel { let input_channels_len = input_channels.values().fold(0, |acc, v| acc + v.len()); let database = ctx.db(); - let guild_id = ctx.guild_id().get() as i64; + let guild_id = u64_to_i64_truncating(ctx.guild_id().get()); let mut set = JoinSet::new(); match self.action { EditAction::Add => { diff --git a/lyra/src/component/config/access/mode.rs b/lyra/src/component/config/access/mode.rs index fdecea5..dbf9493 100644 --- a/lyra/src/component/config/access/mode.rs +++ b/lyra/src/component/config/access/mode.rs @@ -1,7 +1,7 @@ use std::fmt::Display; use itertools::Itertools; -use lyra_ext::pretty::flags_display::PrettyFlagsDisplay; +use lyra_ext::{num::u64_to_i64_truncating, pretty::flags_display::FlagsDisplay}; use twilight_interactions::command::{CommandModel, CommandOption, CreateCommand, CreateOption}; use super::AccessCategory; @@ -105,7 +105,7 @@ impl BotSlashCommand for Mode { check::user_is_access_manager(&ctx)?; let access_mode = >::from(self.mode); - let sql_access_mode = access_mode.map_or_else(|| "null".into(), |b| b.to_string()); + let sql_access_mode = access_mode.map_or_else(|| String::from("null"), |b| b.to_string()); let category_flags = AccessCategoryFlags::from(self.category); let set_statements = category_flags @@ -120,7 +120,7 @@ impl BotSlashCommand for Mode { let res = sqlx::query(&format!( "UPDATE guild_configs SET {set_statements} WHERE id = $1 AND ({where_clause});" )) - .bind(ctx.guild_id().get() as i64) + .bind(u64_to_i64_truncating(ctx.guild_id().get())) .bind(access_mode) .execute(ctx.db()) .await?; diff --git a/lyra/src/component/config/now_playing.rs b/lyra/src/component/config/now_playing.rs index 4ec6bf5..871a47f 100644 --- a/lyra/src/component/config/now_playing.rs +++ b/lyra/src/component/config/now_playing.rs @@ -1,3 +1,4 @@ +use lyra_ext::num::u64_to_i64_truncating; use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ @@ -24,7 +25,7 @@ impl BotSlashCommand for Toggle { let mut ctx = require::guild(ctx)?; let new_now_playing = sqlx::query!( "UPDATE guild_configs SET now_playing = NOT now_playing WHERE id = $1 RETURNING now_playing;", - ctx.guild_id().get() as i64, + u64_to_i64_truncating(ctx.guild_id().get()), ) .fetch_one(ctx.db()) .await? diff --git a/lyra/src/component/connection.rs b/lyra/src/component/connection.rs index fb22507..aec14d9 100644 --- a/lyra/src/component/connection.rs +++ b/lyra/src/component/connection.rs @@ -3,7 +3,7 @@ mod leave; pub use join::{auto as auto_join, Join}; pub use leave::Leave; -use lyra_ext::time::{rfc3339::rfc3339_time, unix::unix_time}; +use lyra_ext::{iso8601_time, unix_time}; use std::sync::Arc; @@ -34,15 +34,16 @@ use crate::{ }, }, gateway::{voice, GuildIdAware, SenderAware}, - lavalink::{self, LavalinkAware}, + lavalink::Lavalink, + LavalinkAndGuildIdAware, LavalinkAware, }; -fn users_in_voice(ctx: &impl CacheAware, channel_id: Id) -> Option { - ctx.cache() +pub fn users_in_voice(cx: &impl CacheAware, channel_id: Id) -> Option { + cx.cache() .voice_channel_states(channel_id) .map_or(Some(0), |voice_states| { let mut users = voice_states - .map(|v| ctx.cache().user(v.user_id())) + .map(|v| cx.cache().user(v.user_id())) .collect::>>()?; users.retain(|u| !u.bot); Some(users.len()) @@ -56,11 +57,11 @@ struct InactivityTimeoutContext { } impl InactivityTimeoutContext { - fn new_via(ctx: &(impl OwnedBotStateAware + SenderAware + GuildIdAware)) -> Self { + fn new_via(cx: &(impl OwnedBotStateAware + SenderAware + GuildIdAware)) -> Self { Self { - inner: ctx.bot_owned(), - sender: ctx.sender().clone(), - guild_id: ctx.guild_id(), + inner: cx.bot_owned(), + sender: cx.sender().clone(), + guild_id: cx.guild_id(), } } } @@ -77,8 +78,8 @@ impl CacheAware for InactivityTimeoutContext { } } -impl lavalink::LavalinkAware for InactivityTimeoutContext { - fn lavalink(&self) -> &lavalink::Lavalink { +impl LavalinkAware for InactivityTimeoutContext { + fn lavalink(&self) -> &Lavalink { self.inner.lavalink() } } @@ -95,6 +96,8 @@ impl GuildIdAware for InactivityTimeoutContext { } } +impl LavalinkAndGuildIdAware for InactivityTimeoutContext {} + async fn start_inactivity_timeout( ctx: InactivityTimeoutContext, channel_id: Id, @@ -114,7 +117,7 @@ async fn start_inactivity_timeout( } } - let Some(connection) = ctx.lavalink().get_connection(guild_id) else { + let Some(connection) = ctx.get_connection() else { return Ok(()); }; connection.notify_change(); @@ -138,24 +141,25 @@ async fn start_inactivity_timeout( #[tracing::instrument(skip_all, name = "voice_state_update")] pub async fn handle_voice_state_update( ctx: &voice::Context, + connection_changed: bool, ) -> Result<(), HandleVoiceStateUpdateError> { let state = &ctx.inner; let maybe_old_state = ctx.old_voice_state(); let guild_id = ctx.guild_id(); - let lavalink = ctx.lavalink(); tracing::trace!("handling voice state update"); let (connected_channel_id, text_channel_id) = { - let Some(connection) = lavalink.get_connection(guild_id) else { + let Some(connection) = ctx.get_connection() else { + tracing::trace!("no active connection"); return Ok(()); }; - if connection.changed().await { - tracing::trace!("connection changed"); + if connection_changed { + tracing::trace!("received connection change notification"); return Ok(()); } - tracing::trace!("connection forced"); + tracing::trace!("no connection change notification"); (connection.channel_id, connection.text_channel_id) }; @@ -231,11 +235,11 @@ async fn match_state_channel_id( unix_time().as_secs() + u64::from(const_connection::INACTIVITY_TIMEOUT_SECS) ) } else { - "`(Bot was forcefully moved)`".into() + String::from("`(Bot was forcefully moved)`") }; let stage_emoji = match joined.kind { - JoinedChannelType::Stage => "๐ŸŽญ".into(), + JoinedChannelType::Stage => String::from("๐ŸŽญ"), JoinedChannelType::Voice => String::new(), }; @@ -253,12 +257,16 @@ async fn match_state_channel_id( )) .await?; + if let Some(mut connection) = ctx.get_connection_mut() { + connection.channel_id = channel_id; + } + if matches!(joined.kind, JoinedChannelType::Stage) { ctx.bot() .http() .update_current_user_voice_state(guild_id) .channel_id(channel_id) - .request_to_speak_timestamp(&rfc3339_time()) + .request_to_speak_timestamp(&iso8601_time()) .await?; } diff --git a/lyra/src/component/connection/join.rs b/lyra/src/component/connection/join.rs index fada778..ff4561b 100644 --- a/lyra/src/component/connection/join.rs +++ b/lyra/src/component/connection/join.rs @@ -1,6 +1,6 @@ use std::{borrow::Cow, fmt::Display, sync::Arc}; -use lyra_ext::time::{rfc3339::rfc3339_time, unix::unix_time}; +use lyra_ext::{iso8601_time, unix_time}; use twilight_gateway::Event; use twilight_interactions::command::{CommandModel, CreateCommand}; use twilight_mention::Mention; @@ -41,7 +41,8 @@ use crate::{ Cache as CacheError, CommandResult, UserNotInVoice as UserNotInVoiceError, }, gateway::{GuildIdAware, SenderAware}, - lavalink::{Connection, LavalinkAware}, + lavalink::Connection, + LavalinkAware, }; pub(super) enum Response { @@ -101,17 +102,13 @@ type GetUsersVoiceChannelResult = Result<(Id, ChannelType, Option>), GetUsersVoiceChannelError>; fn get_users_voice_channel(ctx: &GuildCtx) -> GetUsersVoiceChannelResult { - let voice_state = ctx + let channel_id = ctx .cache() .voice_state(ctx.author_id(), ctx.guild_id()) - .ok_or(UserNotInVoiceError)?; - - let channel_id = voice_state.channel_id(); + .ok_or(UserNotInVoiceError)? + .channel_id(); let voice = ctx.cache().channel(channel_id).ok_or(CacheError)?; - let channel_type = voice.kind; - let channel_parent_id = voice.parent_id; - - Ok((channel_id, channel_type, channel_parent_id)) + Ok((channel_id, voice.kind, voice.parent_id)) } async fn impl_join( @@ -169,19 +166,19 @@ async fn connect_to( ) -> Result { check_user_is_stage_manager(channel_type, ctx)?; - let old_channel_id = ctx - .current_voice_state() - .map(|voice_state| { - let old_channel_id = voice_state.channel_id(); + let old_channel_id = match require::in_voice(ctx) { + Ok(ref in_voice) => { + let old_channel_id = in_voice.channel_id(); if old_channel_id == channel_id { - Err(error::InVoiceAlready(channel_id))?; + return Err(error::InVoiceAlready(channel_id).into()); } - check::noone_else_in(old_channel_id, ctx)?; + check::noone_else_in(in_voice.into(), ctx)?; - Ok::<_, ConnectToError>(old_channel_id) - }) - .transpose()?; + Some(old_channel_id) + } + Err(_) => None, + }; Ok(impl_connect_to( channel_id, @@ -206,7 +203,7 @@ async fn impl_connect_to( .bot_permissions_for(channel_id)? .contains(Permissions::CONNECT) { - Err(error::ConnectionForbidden(channel_id))?; + return Err(error::ConnectionForbidden(channel_id).into()); } check::user_allowed_to_use(channel_id, channel_parent_id, ctx).await?; @@ -215,11 +212,12 @@ async fn impl_connect_to( let voice_is_empty = users_in_voice(ctx, channel_id).ok_or(CacheError)? == 0; + let lavalink = ctx.lavalink(); let response = old_channel_id.map_or_else( || { let connection = Connection::new(channel_id, ctx.channel_id()); connection.notify_change(); - ctx.lavalink().new_connection_with(guild_id, connection); + lavalink.new_connection_with(guild_id, connection); Response::Joined { voice: joined, empty: voice_is_empty, @@ -228,13 +226,11 @@ async fn impl_connect_to( |from| { // SAFETY: `old_channel_id` is of variant `Some`, meaning another connection exists, // so `ctx.lavalink().get_connection_mut(guild_id).unwrap_unchecked()` is safe - let mut connection = unsafe { - ctx.lavalink() - .get_connection_mut(guild_id) - .unwrap_unchecked() - }; + let mut connection = + unsafe { lavalink.get_connection_mut(guild_id).unwrap_unchecked() }; connection.channel_id = channel_id; connection.notify_change(); + drop(connection); Response::Moved { from, to: joined, @@ -251,7 +247,7 @@ async fn impl_connect_to( .http() .update_current_user_voice_state(guild_id) .channel_id(channel_id) - .request_to_speak_timestamp(&rfc3339_time()) + .request_to_speak_timestamp(&iso8601_time()) .await?; } @@ -322,7 +318,10 @@ async fn handle_response( let (joined, empty) = match response { Response::Joined { voice, empty } => { let stage = matches!(voice.kind, JoinedChannelType::Stage); - out!(stage_fmt(&format!("๐Ÿ–‡๏ธ {}", voice.id.mention()), stage), ?ctx); + out!( + stage_fmt(&format!("๐Ÿ–‡๏ธ {}", voice.id.mention()), stage), + ?ctx + ); (voice, empty) } Response::Moved { from, to, empty } => { @@ -419,7 +418,7 @@ impl BotSlashCommand for Join { ctx ); } - Pfe::Other(e) => Err(e)?, + Pfe::Other(e) => Err(e.into()), } } } diff --git a/lyra/src/component/connection/leave.rs b/lyra/src/component/connection/leave.rs index 51e5fd3..d4eb825 100644 --- a/lyra/src/component/connection/leave.rs +++ b/lyra/src/component/connection/leave.rs @@ -20,7 +20,8 @@ use crate::{ CommandResult, }, gateway::{GuildIdAware, SenderAware}, - lavalink::{self, Event, LavalinkAware}, + lavalink::Event, + LavalinkAware, }; pub(super) struct LeaveResponse(pub(super) Id); @@ -31,18 +32,18 @@ impl Display for LeaveResponse { } } -pub(super) fn disconnect(ctx: &(impl SenderAware + GuildIdAware)) -> Result<(), ChannelError> { - ctx.sender() - .command(&UpdateVoiceState::new(ctx.guild_id(), None, false, false))?; +pub(super) fn disconnect(cx: &(impl SenderAware + GuildIdAware)) -> Result<(), ChannelError> { + cx.sender() + .command(&UpdateVoiceState::new(cx.guild_id(), None, false, false))?; Ok(()) } pub(super) async fn pre_disconnect_cleanup( - ctx: &(impl GuildIdAware + lavalink::LavalinkAware + Sync), + cx: &(impl GuildIdAware + LavalinkAware + Sync), ) -> Result<(), PreDisconnectCleanupError> { - let guild_id = ctx.guild_id(); - let lavalink = ctx.lavalink(); + let guild_id = cx.guild_id(); + let lavalink = cx.lavalink(); if let Some(connection) = lavalink.get_connection(guild_id) { connection.dispatch(Event::QueueClear); @@ -59,7 +60,7 @@ async fn leave(ctx: &GuildCtx) -> Result { caut!("Not currently connected to a voice channel.", ctx); } - leave::NotInVoiceMatchedError::Other(e) => Err(e)?, + leave::NotInVoiceMatchedError::Other(e) => Err(e.into()), }, } } diff --git a/lyra/src/component/playback.rs b/lyra/src/component/playback.rs index 2321a55..67b8d92 100644 --- a/lyra/src/component/playback.rs +++ b/lyra/src/component/playback.rs @@ -1,71 +1,67 @@ -// use anyhow::Result; -// use twilight_lavalink::model::{Pause, Seek, Stop}; -// use twilight_model::gateway::payload::incoming::MessageCreate; - -// use crate::{ -// commands::{models::App, Context}, -// lavalink::LavalinkAware, -// }; - -// pub async fn pause(ctx: Context) -> Result<()> { -// tracing::debug!( -// "pause command in channel {} by {}", -// ctx.channel_id(), -// ctx.author().name -// ); - -// let guild_id = ctx.guild_id().unwrap(); -// let player = ctx.lavalink().player(guild_id).await.unwrap(); -// let paused = player.paused(); -// player.send(Pause::from((guild_id, !paused)))?; - -// let action = if paused { "Unpaused " } else { "Paused" }; - -// ctx.respond(&format!("{action} the track")).await?; - -// Ok(()) -// } - -// pub async fn seek(ctx: Context) -> Result<()> { -// let bot = ctx.bot(); -// let (author, channel_id) = (ctx.author(), ctx.channel_id()); - -// tracing::debug!("seek command in channel {} by {}", channel_id, author.name); -// ctx.http() -// .create_message(channel_id) -// .content("Where in the track do you want to seek to (in seconds)?")? -// .await?; - -// let author_id = author.id; -// let msg = bot -// .standby() -// .wait_for_message(channel_id, move |new_msg: &MessageCreate| { -// new_msg.author.id == author_id -// }) -// .await?; -// let guild_id = ctx.guild_id().unwrap(); -// let position = msg.content.parse::()?; - -// let player = ctx.lavalink().player(guild_id).await.unwrap(); -// player.send(Seek::from((guild_id, position * 1000)))?; - -// ctx.respond(&format!("Seeked to {position}s")).await?; - -// Ok(()) -// } - -// pub async fn stop(ctx: Context) -> Result<()> { -// tracing::debug!( -// "stop command in channel {} by {}", -// ctx.channel_id(), -// ctx.author().name -// ); - -// let guild_id = ctx.guild_id().unwrap(); -// let player = ctx.lavalink().player(guild_id).await.unwrap(); -// player.send(Stop::from(guild_id))?; - -// ctx.respond("Stopped the track").await?; - -// Ok(()) -// } +mod back; +mod jump; +mod play_pause; +mod restart; +mod seek; +mod skip; + +pub use jump::{Autocomplete as JumpAutocomplete, Jump}; +pub use play_pause::PlayPause; +pub use restart::Restart; +pub use seek::Seek; + +use crate::{ + command::require, + core::model::{BotStateAware, HttpAware}, + error::component::playback::HandleVoiceStateUpdateError, + gateway::voice::Context, + LavalinkAndGuildIdAware, +}; + +use super::connection::users_in_voice; + +#[tracing::instrument(skip_all, name = "voice_state_update")] +pub async fn handle_voice_state_update( + ctx: &Context, + connection_changed: bool, +) -> Result<(), HandleVoiceStateUpdateError> { + let state = ctx.inner.as_ref(); + let maybe_old_state = ctx.old_voice_state(); + + tracing::trace!("handling voice state update"); + let (connected_channel_id, text_channel_id) = { + let Some(connection) = ctx.get_connection() else { + tracing::trace!("no active connection"); + return Ok(()); + }; + + if connection_changed { + tracing::trace!("received connection change notification"); + return Ok(()); + } + tracing::trace!("no connection change notification"); + + (connection.channel_id, connection.text_channel_id) + }; + + let Ok(player) = require::player(ctx) else { + return Ok(()); + }; + + if let Some(old_state) = maybe_old_state { + let old_channel_id = old_state.channel_id(); + if state.user_id != ctx.bot().user_id() + && old_channel_id == connected_channel_id + && state.channel_id.is_some_and(|c| c != old_channel_id) + && users_in_voice(ctx, connected_channel_id).is_some_and(|n| n == 0) + { + player.set_pause(true).await?; + ctx.http() + .create_message(text_channel_id) + .content("โšกโ–ถ Paused `(Bot is not used by anyone)`") + .await?; + } + } + + Ok(()) +} diff --git a/lyra/src/component/playback/back.rs b/lyra/src/component/playback/back.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lyra/src/component/playback/back.rs @@ -0,0 +1 @@ + diff --git a/lyra/src/component/playback/jump.rs b/lyra/src/component/playback/jump.rs new file mode 100644 index 0000000..f7951bb --- /dev/null +++ b/lyra/src/component/playback/jump.rs @@ -0,0 +1,27 @@ +use lyra_proc::{BotAutocompleteGroup, BotCommandGroup}; +use twilight_interactions::command::{CommandModel, CreateCommand}; + +mod backward; +mod first; +mod forward; +mod to; + +#[derive(CommandModel, CreateCommand, BotCommandGroup)] +#[command(name = "jump", desc = ".", dm_permission = false)] +pub enum Jump { + #[command(name = "to")] + To(to::To), + #[command(name = "forward")] + Forward(forward::Forward), + #[command(name = "backward")] + Backward(backward::Backward), + #[command(name = "first")] + First(first::First), +} + +#[derive(CommandModel, BotAutocompleteGroup)] +#[command(autocomplete = true)] +pub enum Autocomplete { + #[command(name = "to")] + To(to::Autocomplete), +} diff --git a/lyra/src/component/playback/jump/backward.rs b/lyra/src/component/playback/jump/backward.rs new file mode 100644 index 0000000..4928a95 --- /dev/null +++ b/lyra/src/component/playback/jump/backward.rs @@ -0,0 +1,58 @@ +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::command::{ + check, + macros::{bad, out}, + model::BotSlashCommand, + require, +}; + +/// Jumps to a new track at least two tracks earlier. +#[derive(CreateCommand, CommandModel)] +#[command(name = "backward")] +pub struct Backward { + /// Jump by how many tracks? + #[command(min_value = 2)] + tracks: i64, +} + +impl BotSlashCommand for Backward { + #[allow(clippy::significant_drop_tightening)] + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let mut data_w = data.write().await; + let queue = require::queue_not_empty_mut(&mut data_w)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + + #[allow(clippy::cast_possible_truncation)] + let tracks = self.tracks.unsigned_abs() as usize; + let queue_index = queue.index(); + let Some(index) = queue_index.checked_sub(tracks) else { + if queue_index == 0 { + bad!("No where else to jump to", ctx); + } + bad!( + format!( + "Cannot jump past the start of the queue. Maximum backward jump is {} tracks.", + queue_index, + ), + ctx + ); + }; + + queue.downgrade_repeat_mode(); + queue.acquire_advance_lock(); + + let track = queue[index].data(); + let txt = format!("โ†ฉ๏ธ Jumped to `{}` (`#{}`)", track.info.title, index + 1); + player.context.play_now(track).await?; + + *queue.index_mut() = index; + out!(txt, ctx); + } +} diff --git a/lyra/src/component/playback/jump/first.rs b/lyra/src/component/playback/jump/first.rs new file mode 100644 index 0000000..08a68d7 --- /dev/null +++ b/lyra/src/component/playback/jump/first.rs @@ -0,0 +1,47 @@ +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::command::{ + check, + macros::{bad, out}, + model::BotSlashCommand, + require, +}; + +/// Jumps to a the first track in the queue +#[derive(CreateCommand, CommandModel)] +#[command(name = "first")] +pub struct First; + +impl BotSlashCommand for First { + #[allow(clippy::significant_drop_tightening)] + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let mut data_w = data.write().await; + let queue = require::queue_not_empty_mut(&mut data_w)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + + let queue_len = queue.len(); + if queue_len == 1 { + bad!("No where else to jump to", ctx); + } + + if queue.position().get() == 1 { + bad!("Cannot jump to the current track", ctx); + } + + queue.downgrade_repeat_mode(); + queue.acquire_advance_lock(); + + let track = queue[0].data(); + let txt = format!("โฌ…๏ธ Jumped to `{}` (`#1`)", track.info.title); + player.context.play_now(track).await?; + + *queue.index_mut() = 0; + out!(txt, ctx); + } +} diff --git a/lyra/src/component/playback/jump/forward.rs b/lyra/src/component/playback/jump/forward.rs new file mode 100644 index 0000000..96cbb9c --- /dev/null +++ b/lyra/src/component/playback/jump/forward.rs @@ -0,0 +1,67 @@ +use std::num::NonZeroUsize; + +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::command::{ + check, + macros::{bad, out}, + model::BotSlashCommand, + require, +}; + +/// Jumps to a new track at least two tracks later. +#[derive(CreateCommand, CommandModel)] +#[command(name = "forward")] +pub struct Forward { + /// Jump by how many tracks? + #[command(min_value = 2)] + tracks: i64, +} + +impl BotSlashCommand for Forward { + #[allow(clippy::significant_drop_tightening)] + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let mut data_w = data.write().await; + let queue = require::queue_not_empty_mut(&mut data_w)?; + let current_track = require::current_track(queue)?; + + #[allow(clippy::cast_possible_truncation)] + let jump = self.tracks.unsigned_abs() as usize; + let queue_len = queue.len(); + + let queue_position = queue.position(); + let new_position = queue_position.saturating_add(jump); + if new_position.get() > queue_len { + let maximum_jump = queue_len - queue_position.get(); + if maximum_jump == 0 { + bad!("No where else to jump to", ctx); + } + bad!( + format!( + "Cannot jump past the end of the queue. Maximum forward jump is {} tracks.", + maximum_jump, + ), + ctx + ); + } + + let skipped = + (current_track.position.get()..new_position.get()).filter_map(NonZeroUsize::new); + check::all_users_track(queue, skipped, in_voice_with_user)?; + + queue.downgrade_repeat_mode(); + queue.acquire_advance_lock(); + + let track = queue[new_position].data(); + let txt = format!("โ†ช๏ธ Jumped to `{}` (`#{}`)", track.info.title, new_position); + player.context.play_now(track).await?; + + *queue.index_mut() = new_position.get() - 1; + out!(txt, ctx); + } +} diff --git a/lyra/src/component/playback/jump/to.rs b/lyra/src/component/playback/jump/to.rs new file mode 100644 index 0000000..da048f3 --- /dev/null +++ b/lyra/src/component/playback/jump/to.rs @@ -0,0 +1,121 @@ +use std::{ + collections::HashSet, + num::{IntErrorKind, NonZeroUsize}, +}; + +use twilight_interactions::command::{AutocompleteValue, CommandModel, CreateCommand}; +use twilight_model::application::command::CommandOptionChoice; + +use crate::{ + command::{ + check, + macros::{bad, out}, + model::{BotAutocomplete, BotSlashCommand}, + require, + }, + component::queue::{ + generate_position_choices, generate_position_choices_from_fuzzy_match, + generate_position_choices_from_input, validate_input_position, + }, + core::model::CacheAware, + LavalinkAndGuildIdAware, +}; + +#[allow(clippy::significant_drop_tightening)] +async fn generate_skip_to_choices( + track: String, + cx: &(impl CacheAware + LavalinkAndGuildIdAware + Sync), +) -> Vec { + let Ok(player) = require::player(cx) else { + return Vec::new(); + }; + let data = player.data(); + let data_r = data.read().await; + let (queue, Some(queue_len)) = (data_r.queue(), NonZeroUsize::new(data_r.queue().len())) else { + return Vec::new(); + }; + + let excluded = HashSet::from([queue.position()]); + let queue_iter = queue.iter_positions_and_items(); + + match track.parse::() { + Ok(input) => { + generate_position_choices_from_input(input, queue_len, queue_iter, &excluded, cx) + } + Err(e) if matches!(e.kind(), IntErrorKind::Empty) => { + generate_position_choices(queue.position(), queue_len, queue_iter, &excluded, cx) + } + Err(_) => generate_position_choices_from_fuzzy_match(&track, queue_iter, &excluded, cx), + } +} + +#[derive(CommandModel)] +#[command(autocomplete = true)] +pub struct Autocomplete { + track: AutocompleteValue, +} + +impl BotAutocomplete for Autocomplete { + async fn execute( + self, + ctx: crate::command::AutocompleteCtx, + ) -> crate::error::command::AutocompleteResult { + let mut ctx = require::guild(ctx)?; + let AutocompleteValue::Focused(track) = self.track else { + // SAFETY: exactly one autocomplete option is focused, so this is unreachable + unsafe { std::hint::unreachable_unchecked() } + }; + + let choices = generate_skip_to_choices(track, &ctx).await; + Ok(ctx.autocomplete(choices).await?) + } +} + +/// Jumps to a new track in the queue, skipping all track in-between. +#[derive(CommandModel, CreateCommand)] +#[command(name = "to")] +pub struct To { + /// Which track? [track title / position in queue] + #[command(min_value = 1, autocomplete = true)] + track: i64, +} + +impl BotSlashCommand for To { + #[allow(clippy::significant_drop_tightening)] + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let mut data_w = data.write().await; + let queue = require::queue_not_empty_mut(&mut data_w)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + + let queue_len = queue.len(); + if queue_len == 1 { + bad!("No where else to jump to", ctx); + } + + let input = self.track; + validate_input_position(input, queue_len)?; + + #[allow(clippy::cast_possible_truncation)] + let position = input.unsigned_abs() as usize; + if position == queue.position().get() { + bad!("Cannot jump to the current track", ctx); + } + + queue.downgrade_repeat_mode(); + queue.acquire_advance_lock(); + + let index = position - 1; + let track = queue[index].data(); + let txt = format!("โ†”๏ธ Jumped to `{}` (`#{}`)", track.info.title, position); + player.context.play_now(track).await?; + + *queue.index_mut() = index; + out!(txt, ctx); + } +} diff --git a/lyra/src/component/playback/play_pause.rs b/lyra/src/component/playback/play_pause.rs new file mode 100644 index 0000000..c920f37 --- /dev/null +++ b/lyra/src/component/playback/play_pause.rs @@ -0,0 +1,37 @@ +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::{ + command::{check, macros::out, model::BotSlashCommand, require, SlashCtx}, + error::CommandResult, +}; + +/// Toggles the playback of the current track. +#[derive(CreateCommand, CommandModel)] +#[command(name = "play-pause", dm_permission = false)] +pub struct PlayPause; + +impl BotSlashCommand for PlayPause { + async fn run(self, ctx: SlashCtx) -> CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let data_r = data.read().await; + let queue = require::queue_not_empty(&data_r)?; + check::current_track_is_users(&require::current_track(queue)?, in_voice_with_user)?; + let pause = !data_r.paused(); + let message = if pause { + "โ–ถ๏ธ Paused" + } else { + "โธ๏ธ Resumed" + }; + drop(data_r); + + player + .set_pause_with(pause, &mut data.write().await) + .await?; + + out!(message, ctx); + } +} diff --git a/lyra/src/component/playback/restart.rs b/lyra/src/component/playback/restart.rs new file mode 100644 index 0000000..c69e52c --- /dev/null +++ b/lyra/src/component/playback/restart.rs @@ -0,0 +1,30 @@ +use std::time::Duration; + +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::command::{check, macros::out, model::BotSlashCommand, require}; + +/// Restarts the current track; Equivalent to seeking to 0:00. +#[derive(CreateCommand, CommandModel)] +#[command(name = "restart", dm_permission = false)] +pub struct Restart; + +impl BotSlashCommand for Restart { + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let data_r = data.read().await; + let queue = require::queue_not_empty(&data_r)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + + drop(data_r); + player + .seek_to_with(Duration::ZERO, &mut data.write().await) + .await?; + out!("โ—€๏ธ Restarted", ctx); + } +} diff --git a/lyra/src/component/playback/seek.rs b/lyra/src/component/playback/seek.rs new file mode 100644 index 0000000..62cc0b4 --- /dev/null +++ b/lyra/src/component/playback/seek.rs @@ -0,0 +1,17 @@ +use lyra_proc::BotCommandGroup; +use twilight_interactions::command::{CommandModel, CreateCommand}; + +mod backward; +mod forward; +mod to; + +#[derive(CommandModel, CreateCommand, BotCommandGroup)] +#[command(name = "seek", desc = ".", dm_permission = false)] +pub enum Seek { + #[command(name = "to")] + To(to::To), + #[command(name = "forward")] + Forward(forward::Forward), + #[command(name = "backward")] + Backward(backward::Backward), +} diff --git a/lyra/src/component/playback/seek/backward.rs b/lyra/src/component/playback/seek/backward.rs new file mode 100644 index 0000000..44f20c4 --- /dev/null +++ b/lyra/src/component/playback/seek/backward.rs @@ -0,0 +1,55 @@ +use std::time::Duration; + +use lyra_ext::pretty::duration_display::DurationDisplay; +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::command::{ + check, + macros::{bad, out}, + model::BotSlashCommand, + require, +}; + +/// Seeks the current track backward to a new position some time earlier. +#[derive(CreateCommand, CommandModel)] +#[command(name = "backward")] +pub struct Backward { + /// Seek by how many seconds? (If not given, 5 seconds) + #[command(min_value = 0)] + seconds: Option, +} + +impl BotSlashCommand for Backward { + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let data_r = data.read().await; + let queue = require::queue_not_empty(&data_r)?; + check::current_track_is_users(&require::current_track(queue)?, in_voice_with_user)?; + + let secs = self.seconds.unwrap_or(5.); + if secs == 0. { + bad!("Seconds can not be zero", ctx); + } + + let old_timestamp = data_r.timestamp(); + drop(data_r); + + let timestamp = old_timestamp.saturating_sub(Duration::from_secs_f64(secs)); + player + .seek_to_with(timestamp, &mut data.write().await) + .await?; + + out!( + format!( + "โช ~~`{}`~~ โžœ **`{}`**", + old_timestamp.pretty_display(), + timestamp.pretty_display(), + ), + ctx + ); + } +} diff --git a/lyra/src/component/playback/seek/forward.rs b/lyra/src/component/playback/seek/forward.rs new file mode 100644 index 0000000..87382b4 --- /dev/null +++ b/lyra/src/component/playback/seek/forward.rs @@ -0,0 +1,68 @@ +use std::time::Duration; + +use lyra_ext::pretty::duration_display::DurationDisplay; +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::command::{ + check, + macros::{bad, out}, + model::BotSlashCommand, + require, +}; + +/// Seeks the current track forward to a new position some time later. +#[derive(CreateCommand, CommandModel)] +#[command(name = "forward")] +pub struct Forward { + /// Seek by how many seconds? (If not given, 10 seconds) + #[command(min_value = 0)] + seconds: Option, +} + +impl BotSlashCommand for Forward { + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let data_r = data.read().await; + let queue = require::queue_not_empty(&data_r)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + + let secs = self.seconds.unwrap_or(10.); + if secs == 0. { + bad!("Seconds must not be zero", ctx); + } + + let old_timestamp = data_r.timestamp(); + let current_track_length = u128::from(current_track.track.data().info.length); + drop(data_r); + + let timestamp = old_timestamp + Duration::from_secs_f64(secs); + + if timestamp.as_millis() > current_track_length { + let remaining = timestamp.as_millis() - current_track_length; + bad!( + format!( + "Cannot seek past the end of the track. Maximum forward seek is {} seconds.", + remaining.div_ceil(1_000), + ), + ctx + ); + } + player + .seek_to_with(timestamp, &mut data.write().await) + .await?; + + out!( + format!( + "โฉ ~~`{}`~~ โžœ **`{}`**", + old_timestamp.pretty_display(), + timestamp.pretty_display(), + ), + ctx + ); + } +} diff --git a/lyra/src/component/playback/seek/to.rs b/lyra/src/component/playback/seek/to.rs new file mode 100644 index 0000000..7193f32 --- /dev/null +++ b/lyra/src/component/playback/seek/to.rs @@ -0,0 +1,96 @@ +use std::time::Duration; + +use lyra_ext::pretty::duration_display::{DurationDisplay, FromPrettyStr}; +use twilight_interactions::command::{CommandModel, CreateCommand}; + +use crate::{ + command::{ + check, + macros::{bad, out}, + model::BotSlashCommand, + require, + }, + component::playback::Restart, + core::model::InteractionClient, +}; + +/// Seeks the current track to a new position. +#[derive(CreateCommand, CommandModel)] +#[command(name = "to")] +pub struct To { + /// Seek to where? [Must be a timestamp like 1m23s or 4:56, or as the total seconds like 78s] + #[command(min_length = 1)] + timestamp: String, +} + +impl BotSlashCommand for To { + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let data_r = data.read().await; + let queue = require::queue_not_empty(&data_r)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + + let timestamp_unchecked = self.timestamp; + let timestamp = if let Ok(secs) = timestamp_unchecked.parse::() { + if secs < 0. { + bad!("Timestamp as total seconds must be positive", ctx); + } + + Duration::from_secs_f64(secs) + } else if let Ok(duration) = Duration::from_pretty_str(×tamp_unchecked) { + duration + } else { + bad!( + format!( + "Invalid timestamp: `{}`; Timestamp must either be in the format like 1m23s or 4:56, or as the total seconds like 78s", + timestamp_unchecked, + ), + ctx + ); + }; + + let current_track_length = u128::from(current_track.track.data().info.length); + + if timestamp.is_zero() { + let restart = InteractionClient::mention_command::(); + + bad!( + format!( + "Timestamp must not be 0:00. To restart the track, use {} instead.", + restart + ), + ctx + ); + } + if timestamp.as_millis() > current_track_length { + bad!( + format!( + "Invalid timestamp: `{}`; Timestamp must be within the track length of `{}`", + timestamp.pretty_display(), + current_track_length.pretty_display(), + ), + ctx + ); + } + + let old_position = data_r.timestamp(); + drop(data_r); + + player + .seek_to_with(timestamp, &mut data.write().await) + .await?; + out!( + format!( + "๐Ÿ•น๏ธ ~~`{}`~~ โžœ **`{}`**", + old_position.pretty_display(), + timestamp.pretty_display(), + ), + ctx + ); + } +} diff --git a/lyra/src/component/playback/skip.rs b/lyra/src/component/playback/skip.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lyra/src/component/playback/skip.rs @@ -0,0 +1 @@ + diff --git a/lyra/src/component/queue.rs b/lyra/src/component/queue.rs index efc27c0..f6bdf6b 100644 --- a/lyra/src/component/queue.rs +++ b/lyra/src/component/queue.rs @@ -8,11 +8,16 @@ mod repeat; mod shuffle; pub use clear::Clear; +#[allow(clippy::module_name_repetitions)] pub use fair_queue::FairQueue; -use lyra_ext::pretty::{ - duration_display::PrettyDurationDisplay, join::PrettyJoiner, truncate::PrettyTruncator, +use lyra_ext::{ + num::usize_to_i64_truncating, + pretty::{duration_display::DurationDisplay, join::PrettyJoiner, truncate::PrettyTruncator}, }; -pub use play::{AddToQueue, Autocomplete as PlayAutocomplete, File as PlayFile, Play}; + +#[allow(clippy::module_name_repetitions)] +pub use play::AddToQueue; +pub use play::{Autocomplete as PlayAutocomplete, File as PlayFile, Play}; pub use r#move::{Autocomplete as MoveAutocomplete, Move}; pub use remove::{Autocomplete as RemoveAutocomplete, Remove}; pub use remove_range::{Autocomplete as RemoveRangeAutocomplete, RemoveRange}; @@ -29,7 +34,7 @@ use crate::{ command::{ macros::{note_fol, out}, model::{GuildCtx, RespondViaMessage}, - require::Player, + require::PlayerInterface, }, core::{ model::{CacheAware, InteractionClient}, @@ -44,12 +49,12 @@ use crate::{ fn generate_position_choice( position: NonZeroUsize, track: &QueueItem, - ctx: &impl CacheAware, + cx: &impl CacheAware, ) -> CommandOptionChoice { - let track_info = &track.track().info; + let track_info = &track.data().info; let track_length = Duration::from_millis(track_info.length); - let requester = ctx.cache().user(track.requester()).map_or_else( - || "Unknown User".into(), + let requester = cx.cache().user(track.requester()).map_or_else( + || String::from("Unknown User"), |u| { u.global_name .clone() @@ -68,16 +73,16 @@ fn generate_position_choice( track_info.corrected_title().pretty_truncate(53) ), name_localizations: None, - value: CommandOptionChoiceValue::Integer(position.get() as i64), + value: CommandOptionChoiceValue::Integer(usize_to_i64_truncating(position.get())), } } -fn generate_position_choices<'a>( +pub fn generate_position_choices<'a>( position: NonZeroUsize, queue_len: NonZeroUsize, queue_iter: impl Iterator + Clone, excluded: &HashSet, - ctx: &impl CacheAware, + cx: &impl CacheAware, ) -> Vec { impl_generate_position_choices( queue_iter @@ -85,7 +90,7 @@ fn generate_position_choices<'a>( .skip_while(|(p, _)| *p < position) .take(queue_len.get()), excluded, - ctx, + cx, ) } @@ -94,7 +99,7 @@ fn generate_position_choices_reversed<'a>( queue_len: NonZeroUsize, queue_iter: impl Clone + DoubleEndedIterator, excluded: &HashSet, - ctx: &impl CacheAware, + cx: &impl CacheAware, ) -> Vec { impl_generate_position_choices( queue_iter @@ -103,48 +108,48 @@ fn generate_position_choices_reversed<'a>( .skip_while(|(p, _)| *p > position) .take(queue_len.get()), excluded, - ctx, + cx, ) } fn impl_generate_position_choices<'a>( queue_iter: impl Iterator + Clone, excluded: &HashSet, - ctx: &impl CacheAware, + cx: &impl CacheAware, ) -> Vec { queue_iter .filter(|(p, _)| !excluded.contains(p)) .take(COMMAND_CHOICES_LIMIT) - .map(|(p, t)| generate_position_choice(p, t, ctx)) + .map(|(p, t)| generate_position_choice(p, t, cx)) .collect() } -fn generate_position_choices_from_input<'a>( +pub fn generate_position_choices_from_input<'a>( input: i64, queue_len: NonZeroUsize, queue_iter: impl Clone + DoubleEndedIterator, excluded: &HashSet, - ctx: &impl CacheAware, + cx: &impl CacheAware, ) -> Vec { normalize_queue_position(input, queue_len) .filter(|p| !excluded.contains(p)) .map_or_else(Vec::new, |position| { if input.is_positive() { - return generate_position_choices(position, queue_len, queue_iter, excluded, ctx); + return generate_position_choices(position, queue_len, queue_iter, excluded, cx); } - generate_position_choices_reversed(position, queue_len, queue_iter, excluded, ctx) + generate_position_choices_reversed(position, queue_len, queue_iter, excluded, cx) }) } -fn generate_position_choices_from_fuzzy_match<'a>( +pub fn generate_position_choices_from_fuzzy_match<'a>( focused: &str, queue_iter: impl Iterator, excluded: &HashSet, - ctx: &impl CacheAware, + cx: &impl CacheAware, ) -> Vec { let queue_iter = queue_iter .filter_map(|(p, t)| { - let track_info = &t.track().info; + let track_info = &t.data().info; let author = track_info.corrected_author(); let title = track_info.corrected_title(); let requester = t.requester(); @@ -156,32 +161,33 @@ fn generate_position_choices_from_fuzzy_match<'a>( }) .sorted_by_key(|(_, _, s)| -s) .map(|(p, t, _)| (p, t)); - impl_generate_position_choices(queue_iter, excluded, ctx) + impl_generate_position_choices(queue_iter, excluded, cx) } fn normalize_queue_position(position: i64, queue_len: NonZeroUsize) -> Option { - (1..=queue_len.get()) - .contains(&(position.unsigned_abs() as usize)) - .then(|| { - NonZeroUsize::new( - position - .is_positive() - .then(|| position.unsigned_abs() as usize) - .unwrap_or_else(|| queue_len.get() - position.unsigned_abs() as usize + 1), - ) - })? + #[allow(clippy::cast_possible_truncation)] + let position_usize = position.unsigned_abs() as usize; + + (1..=queue_len.get()).contains(&position_usize).then(|| { + NonZeroUsize::new( + position + .is_positive() + .then_some(position_usize) + .unwrap_or_else(|| queue_len.get() - position_usize + 1), + ) + })? } -fn validate_input_positions( - inputs: &[i64], +pub const fn validate_input_position( + input: i64, queue_len: usize, ) -> Result<(), PositionOutOfRangeError> { - if let Some(&position) = inputs.iter().find(|&i| !(1..=queue_len as i64).contains(i)) { + if 1 > input || input > usize_to_i64_truncating(queue_len) { return Err(if queue_len == 1 { - PositionOutOfRangeError::OnlyTrack(position) + PositionOutOfRangeError::OnlyTrack(input) } else { PositionOutOfRangeError::OutOfRange { - position, + position: input, queue_len, } }); @@ -190,19 +196,33 @@ fn validate_input_positions( Ok(()) } +fn validate_input_positions( + inputs: &[i64], + queue_len: usize, +) -> Result<(), PositionOutOfRangeError> { + inputs + .iter() + .try_for_each(|&input| validate_input_position(input, queue_len))?; + + Ok(()) +} + async fn remove_range( start: i64, end: i64, ctx: &mut GuildCtx, - player: &Player, + player: &PlayerInterface, ) -> Result<(), RemoveTracksError> { + #[allow(clippy::cast_possible_truncation)] + let (start_usize, end_usize) = (start.unsigned_abs() as usize, end.unsigned_abs() as usize); + let data = player.data(); let mut data_w = data.write().await; let queue = data_w.queue_mut(); - let range = (start - 1) as usize..=(end - 1) as usize; + let range = (start_usize - 1)..end_usize; let queue_len = queue.len(); - let positions_len = (end - start) as usize + 1; + let positions_len = (end_usize - start_usize) + 1; let queue_cleared = positions_len > 1 && positions_len == queue_len; let removed = if queue_cleared { queue.drain_all().collect::>() @@ -210,8 +230,8 @@ async fn remove_range( queue.drain(range).collect() }; - let positions = (start..=end) - .filter_map(|p| NonZeroUsize::new(p as usize)) + let positions = (start_usize..=end_usize) + .filter_map(NonZeroUsize::new) .collect(); drop(data_w); @@ -221,7 +241,7 @@ async fn remove_range( async fn remove( positions: Box<[NonZeroUsize]>, ctx: &mut GuildCtx, - player: &Player, + player: &PlayerInterface, ) -> Result<(), RemoveTracksError> { let data = player.data(); let mut data_w = data.write().await; @@ -240,12 +260,13 @@ async fn remove( impl_remove(positions, removed, queue_cleared, ctx, player).await } +#[allow(clippy::significant_drop_tightening)] async fn impl_remove( positions: Box<[NonZeroUsize]>, removed: Vec, queue_cleared: bool, ctx: &mut GuildCtx, - player: &Player, + player: &PlayerInterface, ) -> Result<(), RemoveTracksError> { let data = player.data(); let mut data_w = data.write().await; @@ -256,8 +277,8 @@ async fn impl_remove( 0 => String::new(), 1..=ADD_TRACKS_WRAP_LIMIT => removed .into_iter() - .map(|t| format!("`{}`", t.into_track().info.corrected_title())) - .collect::>() + .map(|t| format!("`{}`", t.into_data().info.corrected_title())) + .collect::>() .pretty_join_with_and(), _ => format!("`{removed_len} tracks`"), }; @@ -274,17 +295,15 @@ async fn impl_remove( *queue.index_mut() -= positions[..before_current].len(); if positions.binary_search(¤t).is_ok() { - queue.adjust_repeat_mode(); - let next = queue.current().map(|t| t.track().clone()); - - queue - .with_advance_lock_and_stopped(&player.context, |p| async move { - if let Some(ref next) = next { - p.play(next).await?; - } - Ok(()) - }) - .await?; + queue.downgrade_repeat_mode(); + let next = queue.current().map(QueueItem::data); + + if let Some(next) = next { + queue.acquire_advance_lock(); + player.context.play_now(next).await?; + } else { + player.acquire_advance_lock_and_stop_with(queue).await?; + } } out!(format!("{} Removed {}", minus, removed_text), ?ctx); diff --git a/lyra/src/component/queue/clear.rs b/lyra/src/component/queue/clear.rs index 5ae4952..8895500 100644 --- a/lyra/src/component/queue/clear.rs +++ b/lyra/src/component/queue/clear.rs @@ -10,7 +10,8 @@ use crate::{ require, }, error::CommandResult, - lavalink::{Event, LavalinkAware}, + lavalink::Event, + LavalinkAware, }; /// Clears the queue @@ -19,31 +20,31 @@ use crate::{ pub struct Clear; impl BotSlashCommand for Clear { + #[allow(clippy::significant_drop_tightening)] async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; let in_voice = require::in_voice(&ctx)?.and_unsuppressed()?; let connection = ctx.lavalink().connection_from(&in_voice); - let in_voice_with_user = check::in_voice_with_user(in_voice)?; - let player = require::player(&ctx)?.and_queue_not_empty().await?; + let in_voice_with_user = check::user_in(in_voice)?; + let player = require::player(&ctx)?; let data = player.data(); { let data_r = data.read().await; - let queue = data_r.queue(); + let queue = require::queue_not_empty(&data_r)?; let positions = (1..=queue.len()).filter_map(NonZeroUsize::new); - check::all_users_track(positions, in_voice_with_user, queue, &ctx)?; + check::all_users_track(queue, positions, in_voice_with_user)?; - queue.stop_with_advance_lock(&player.context).await?; + player.acquire_advance_lock_and_stop_with(queue).await?; connection.dispatch(Event::QueueClear); drop(connection); } { let mut data_w = data.write().await; - let queue = data_w.queue_mut(); - queue.clear(); + data_w.queue_mut().clear(); } - out!("๐Ÿ’ฅ Cleared the queue", ctx); + out!("โน๏ธ Cleared the queue", ctx); } } diff --git a/lyra/src/component/queue/fair_queue.rs b/lyra/src/component/queue/fair_queue.rs index b35cb09..e3ef03e 100644 --- a/lyra/src/component/queue/fair_queue.rs +++ b/lyra/src/component/queue/fair_queue.rs @@ -21,10 +21,12 @@ impl BotSlashCommand for FairQueue { let mut ctx = require::guild(ctx)?; check::user_is_dj(&ctx)?; let _ = require::in_voice(&ctx)?.and_with_someone_else()?; - let player = require::player(&ctx)?.and_queue_not_empty().await?; + let data = require::player(&ctx)?.data(); - let data = player.data(); - let indexer_type = data.read().await.queue().indexer_type(); + let data_r = data.read().await; + let queue = require::queue_not_empty(&data_r)?; + let indexer_type = queue.indexer_type(); + drop(data_r); match indexer_type { IndexerType::Fair => { diff --git a/lyra/src/component/queue/move.rs b/lyra/src/component/queue/move.rs index d077327..4acc55e 100644 --- a/lyra/src/component/queue/move.rs +++ b/lyra/src/component/queue/move.rs @@ -13,7 +13,8 @@ use crate::{ component::queue::normalize_queue_position, core::model::CacheAware, error::{command::AutocompleteResult, CommandResult}, - lavalink::{CorrectTrackInfo, PlayerAware}, + lavalink::CorrectTrackInfo, + LavalinkAndGuildIdAware, }; enum MoveAutocompleteOptionType { @@ -28,11 +29,12 @@ struct MoveAutocompleteOptions { kind: MoveAutocompleteOptionType, } -async fn generate_move_autocomplete_choices( +#[allow(clippy::significant_drop_tightening)] +async fn generate_move_choices( options: &MoveAutocompleteOptions, - ctx: &(impl PlayerAware + CacheAware + Sync), + cx: &(impl LavalinkAndGuildIdAware + CacheAware + Sync), ) -> Vec { - let Ok(player) = require::player(ctx) else { + let Ok(player) = require::player(cx) else { return Vec::new(); }; let data = player.data(); @@ -41,10 +43,7 @@ async fn generate_move_autocomplete_choices( return Vec::new(); }; - let queue_iter = queue - .iter() - .enumerate() - .filter_map(|(i, t)| NonZeroUsize::new(i + 1).map(|i| (i, t))); + let queue_iter = queue.iter_positions_and_items(); let excluded = match options.kind { MoveAutocompleteOptionType::TrackFocused | MoveAutocompleteOptionType::PositionFocused => { @@ -67,9 +66,9 @@ async fn generate_move_autocomplete_choices( }; match options.focused.parse::() { - Ok(input) => super::generate_position_choices_from_input( - input, queue_len, queue_iter, &excluded, ctx, - ), + Ok(input) => { + super::generate_position_choices_from_input(input, queue_len, queue_iter, &excluded, cx) + } Err(e) if matches!(e.kind(), std::num::IntErrorKind::Empty) => match options.kind { MoveAutocompleteOptionType::TrackFocused | MoveAutocompleteOptionType::TrackFocusedPositionCompleted(_) => { @@ -78,13 +77,13 @@ async fn generate_move_autocomplete_choices( queue_len, queue_iter, &excluded, - ctx, + cx, ) } MoveAutocompleteOptionType::PositionFocused | MoveAutocompleteOptionType::PositionFocusedTrackCompleted(_) => { super::generate_position_choices_reversed( - queue_len, queue_len, queue_iter, &excluded, ctx, + queue_len, queue_len, queue_iter, &excluded, cx, ) } }, @@ -92,7 +91,7 @@ async fn generate_move_autocomplete_choices( &options.focused, queue_iter, &excluded, - ctx, + cx, ), } } @@ -131,7 +130,7 @@ impl BotAutocomplete for Autocomplete { focused: focused.into_boxed_str(), kind, }; - let choices = generate_move_autocomplete_choices(&options, &ctx).await; + let choices = generate_move_choices(&options, &ctx).await; Ok(ctx.autocomplete(choices).await?) } } @@ -149,15 +148,15 @@ pub struct Move { } impl BotSlashCommand for Move { + #[allow(clippy::significant_drop_tightening)] async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; - let in_voice_with_user = - check::in_voice_with_user(require::in_voice(&ctx)?.and_unsuppressed()?)?; - let player = require::player(&ctx)?.and_queue_not_empty().await?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; let data = player.data(); let mut data_w = data.write().await; - let queue = data_w.queue_mut(); + let queue = require::queue_not_empty_mut(&mut data_w)?; let queue_len = queue.len(); if queue_len == 1 { @@ -167,7 +166,8 @@ impl BotSlashCommand for Move { ); } - super::validate_input_positions(&[self.track, self.position], queue_len)?; + super::validate_input_position(self.track, queue_len)?; + super::validate_input_position(self.position, queue_len)?; if self.track == self.position { bad!( @@ -176,17 +176,24 @@ impl BotSlashCommand for Move { ); } + #[allow(clippy::cast_possible_truncation)] + let (track_usize, position_usize) = ( + self.track.unsigned_abs() as usize, + self.position.unsigned_abs() as usize, + ); + // SAFETY: `self.track as usize` is in range [1, +inf), so it is non-zero - let position = unsafe { NonZeroUsize::new_unchecked(self.position as usize) }; - check::users_track(position, in_voice_with_user, queue, &ctx)?; + let position = unsafe { NonZeroUsize::new_unchecked(position_usize) }; + let track = &queue[position]; + check::track_is_users(track, position, in_voice_with_user)?; // SAFETY: `self.track as usize` is in range [1, +inf), so it is non-zero - let track_position = unsafe { NonZeroUsize::new_unchecked(self.track as usize) }; + let track_position = unsafe { NonZeroUsize::new_unchecked(track_usize) }; let queue_position = queue.position(); // SAFETY: `track_position.get() - 1` has been validated to be in-bounds, so this unwrap is safe let track = unsafe { queue.remove(track_position.get() - 1).unwrap_unchecked() }; - let track_title = track.track().info.corrected_title(); + let track_title = track.data().info.corrected_title(); let message = format!("โคด๏ธ Moved `{track_title}` to position **`{position}`**"); let insert_position = position.get() - 1; diff --git a/lyra/src/component/queue/play.rs b/lyra/src/component/queue/play.rs index 97232a1..62e1fd6 100644 --- a/lyra/src/component/queue/play.rs +++ b/lyra/src/component/queue/play.rs @@ -12,9 +12,7 @@ use lavalink_rs::{ use linkify::{LinkFinder, LinkKind}; use lyra_ext::{ as_grapheme::AsGrapheme, - pretty::{ - duration_display::PrettyDurationDisplay, join::PrettyJoiner, truncate::PrettyTruncator, - }, + pretty::{duration_display::DurationDisplay, join::PrettyJoiner, truncate::PrettyTruncator}, }; use twilight_interactions::command::{ AutocompleteValue, CommandModel, CommandOption, CreateCommand, CreateOption, @@ -43,9 +41,9 @@ use crate::{ }, gateway::GuildIdAware, lavalink::{ - CorrectPlaylistInfo, CorrectTrackInfo, LavalinkAware, UnwrappedPlayerData, - UnwrappedPlayerInfoUri, + CorrectPlaylistInfo, CorrectTrackInfo, UnwrappedPlayerData, UnwrappedPlayerInfoUri, }, + LavalinkAware, }; struct LoadTrackContext { @@ -54,10 +52,10 @@ struct LoadTrackContext { } impl LoadTrackContext { - fn new_via(ctx: &(impl GuildIdAware + LavalinkAware)) -> Self { + fn new_via(cx: &(impl GuildIdAware + LavalinkAware)) -> Self { Self { - guild_id: ctx.guild_id(), - lavalink: ctx.lavalink().clone_inner(), + guild_id: cx.guild_id(), + lavalink: cx.lavalink().clone_inner(), } } } @@ -315,7 +313,7 @@ impl BotAutocomplete for Autocomplete { choices } - TrackLoadType::Error => Err(LoadFailedError(query))?, + TrackLoadType::Error => return Err(LoadFailedError(query).into()), TrackLoadType::Empty => Vec::new(), }; @@ -388,10 +386,11 @@ async fn play( let total_tracks = Vec::from(results); // SAFETY: at least one tracks must be loaded, so this unwrap is safe - let first_track = unsafe { total_tracks.first().cloned().unwrap_unchecked() }; + let first_track = unsafe { total_tracks.first().unwrap_unchecked() }; let player = util::auto_new_player(ctx).await?; + player.play(first_track).await?; player .data_unwrapped() .write() @@ -399,8 +398,6 @@ async fn play( .queue_mut() .enqueue(total_tracks, ctx.author_id()); - player.play(&first_track).await?; - out_or_fol!(format!("{} Added {}", plus, enqueued_text), ctx); } Err(e) => match e { @@ -421,7 +418,7 @@ async fn play( ); } }, - LoadTrackProcessManyError::Lavalink(e) => Err(e)?, + LoadTrackProcessManyError::Lavalink(e) => Err(e.into()), }, } } diff --git a/lyra/src/component/queue/remove.rs b/lyra/src/component/queue/remove.rs index 17cc6ca..f836fbe 100644 --- a/lyra/src/component/queue/remove.rs +++ b/lyra/src/component/queue/remove.rs @@ -15,15 +15,16 @@ use crate::{ }, core::model::CacheAware, error::{command::AutocompleteResult, CommandResult}, - lavalink::PlayerAware, + LavalinkAndGuildIdAware, }; +#[allow(clippy::significant_drop_tightening)] async fn generate_remove_choices( focused: &str, finished: Vec, - ctx: &(impl CacheAware + PlayerAware + Sync), + cx: &(impl CacheAware + LavalinkAndGuildIdAware + Sync), ) -> Vec { - let Ok(player) = require::player(ctx) else { + let Ok(player) = require::player(cx) else { return Vec::new(); }; let data = player.data(); @@ -37,24 +38,17 @@ async fn generate_remove_choices( .filter_map(|i| super::normalize_queue_position(i, queue_len)) .collect::>(); - let queue_iter = queue - .iter() - .enumerate() - .filter_map(|(i, t)| NonZeroUsize::new(i + 1).map(|i| (i, t))); + let queue_iter = queue.iter_positions_and_items(); match focused.parse::() { - Ok(input) => super::generate_position_choices_from_input( - input, queue_len, queue_iter, &excluded, ctx, - ), - Err(e) if matches!(e.kind(), IntErrorKind::Empty) => super::generate_position_choices( - queue.position(), - queue_len, - queue_iter, - &excluded, - ctx, - ), + Ok(input) => { + super::generate_position_choices_from_input(input, queue_len, queue_iter, &excluded, cx) + } + Err(e) if matches!(e.kind(), IntErrorKind::Empty) => { + super::generate_position_choices(queue.position(), queue_len, queue_iter, &excluded, cx) + } Err(_) => { - super::generate_position_choices_from_fuzzy_match(focused, queue_iter, &excluded, ctx) + super::generate_position_choices_from_fuzzy_match(focused, queue_iter, &excluded, cx) } } } @@ -128,14 +122,12 @@ pub struct Remove { impl BotSlashCommand for Remove { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; - let in_voice_with_user = - check::in_voice_with_user(require::in_voice(&ctx)?.and_unsuppressed()?)?; - let player = require::player(&ctx)?.and_queue_not_empty().await?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; let data = player.data(); let data_r = data.read().await; - let queue = data_r.queue(); - let queue_len = queue.len(); + let queue = require::queue_not_empty(&data_r)?; let inputs = [ Some(self.track), @@ -149,14 +141,15 @@ impl BotSlashCommand for Remove { .unique() .collect::>(); - super::validate_input_positions(&inputs, queue_len)?; + super::validate_input_positions(&inputs, queue.len())?; + #[allow(clippy::cast_possible_truncation)] let mut positions = inputs .iter() - .filter_map(|p| NonZeroUsize::new(*p as usize)) + .filter_map(|&p| NonZeroUsize::new(p.unsigned_abs() as usize)) .collect::>(); - check::all_users_track(positions.iter().copied(), in_voice_with_user, queue, &ctx)?; + check::all_users_track(queue, positions.iter().copied(), in_voice_with_user)?; positions.sort_unstable(); diff --git a/lyra/src/component/queue/remove_range.rs b/lyra/src/component/queue/remove_range.rs index d5fcf36..cd44917 100644 --- a/lyra/src/component/queue/remove_range.rs +++ b/lyra/src/component/queue/remove_range.rs @@ -3,6 +3,7 @@ use std::{ num::{IntErrorKind, NonZeroUsize}, }; +use lyra_ext::num::usize_to_i64_truncating; use twilight_interactions::command::{AutocompleteValue, CommandModel, CreateCommand}; use twilight_model::application::command::CommandOptionChoice; @@ -16,7 +17,7 @@ use crate::{ component::queue::Remove, core::model::{CacheAware, InteractionClient}, error::{command::AutocompleteResult, CommandResult}, - lavalink::PlayerAware, + LavalinkAndGuildIdAware, }; enum RemoveRangeAutocompleteOptionsType { @@ -31,11 +32,12 @@ struct RemoveRangeAutocompleteOptions { kind: RemoveRangeAutocompleteOptionsType, } +#[allow(clippy::significant_drop_tightening)] async fn generate_remove_range_autocomplete_choices( options: &RemoveRangeAutocompleteOptions, - ctx: &(impl CacheAware + PlayerAware + Sync), + cx: &(impl CacheAware + LavalinkAndGuildIdAware + Sync), ) -> Vec { - let Ok(player) = require::player(ctx) else { + let Ok(player) = require::player(cx) else { return Vec::new(); }; let data = player.data(); @@ -44,10 +46,7 @@ async fn generate_remove_range_autocomplete_choices( return Vec::new(); }; - let queue_iter = queue - .iter() - .enumerate() - .filter_map(|(i, t)| NonZeroUsize::new(i + 1).map(|i| (i, t))); + let queue_iter = queue.iter_positions_and_items(); let excluded = match options.kind { RemoveRangeAutocompleteOptionsType::StartFocused @@ -71,9 +70,9 @@ async fn generate_remove_range_autocomplete_choices( }; match options.focused.parse::() { - Ok(input) => super::generate_position_choices_from_input( - input, queue_len, queue_iter, &excluded, ctx, - ), + Ok(input) => { + super::generate_position_choices_from_input(input, queue_len, queue_iter, &excluded, cx) + } Err(e) if matches!(e.kind(), IntErrorKind::Empty) => match options.kind { RemoveRangeAutocompleteOptionsType::StartFocused | RemoveRangeAutocompleteOptionsType::StartFocusedEndCompleted(_) => { @@ -82,13 +81,13 @@ async fn generate_remove_range_autocomplete_choices( queue_len, queue_iter, &excluded, - ctx, + cx, ) } RemoveRangeAutocompleteOptionsType::EndFocused | RemoveRangeAutocompleteOptionsType::EndFocusedStartCompleted(_) => { super::generate_position_choices_reversed( - queue_len, queue_len, queue_iter, &excluded, ctx, + queue_len, queue_len, queue_iter, &excluded, cx, ) } }, @@ -96,7 +95,7 @@ async fn generate_remove_range_autocomplete_choices( &options.focused, queue_iter, &excluded, - ctx, + cx, ), } } @@ -155,15 +154,14 @@ pub struct RemoveRange { impl BotSlashCommand for RemoveRange { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; - let in_voice_with_user = - check::in_voice_with_user(require::in_voice(&ctx)?.and_unsuppressed()?)?; - let player = require::player(&ctx)?.and_queue_not_empty().await?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; let data = player.data(); let data_r = data.read().await; - let queue = data_r.queue(); - let queue_len = queue.len(); + let queue = require::queue_not_empty(&data_r)?; + let queue_len = queue.len(); if queue_len == 1 { let remove = InteractionClient::mention_command::(); @@ -173,10 +171,11 @@ impl BotSlashCommand for RemoveRange { ); } - super::validate_input_positions(&[self.start, self.end], queue_len)?; + super::validate_input_position(self.start, queue_len)?; + super::validate_input_position(self.end, queue_len)?; if self.end <= self.start { - let message = if self.end == queue_len as i64 { + let message = if self.end == usize_to_i64_truncating(queue_len) { format!( "Invalid starting position: `{}`; Starting position must be from `1` to `{}`.", self.start, @@ -194,8 +193,10 @@ impl BotSlashCommand for RemoveRange { bad!(message, ctx); } - let positions = (self.start..=self.end).filter_map(|p| NonZeroUsize::new(p as usize)); - check::all_users_track(positions, in_voice_with_user, queue, &ctx)?; + #[allow(clippy::cast_possible_truncation)] + let positions = + (self.start..=self.end).filter_map(|p| NonZeroUsize::new(p.unsigned_abs() as usize)); + check::all_users_track(queue, positions, in_voice_with_user)?; drop(data_r); Ok(super::remove_range(self.start, self.end, &mut ctx, &player).await?) diff --git a/lyra/src/component/queue/repeat.rs b/lyra/src/component/queue/repeat.rs index ca15af8..0d0d851 100644 --- a/lyra/src/component/queue/repeat.rs +++ b/lyra/src/component/queue/repeat.rs @@ -2,7 +2,7 @@ use twilight_interactions::command::{CommandModel, CommandOption, CreateCommand, use crate::{ command::{ - check, + check::{self, ResolveWithPoll, StartPoll}, macros::out_or_upd, model::BotSlashCommand, poll::Topic, @@ -11,7 +11,8 @@ use crate::{ }, error::CommandResult, gateway::GuildIdAware, - lavalink::{self, DelegateMethods, LavalinkAware}, + lavalink::{DelegateMethods, Event, RepeatMode as LavalinkRepeatMode}, + LavalinkAware, }; #[derive(CommandOption, CreateOption)] @@ -24,7 +25,7 @@ enum RepeatMode { Track, } -impl From for lavalink::RepeatMode { +impl From for LavalinkRepeatMode { fn from(value: RepeatMode) -> Self { match value { RepeatMode::Off => Self::Off, @@ -51,30 +52,32 @@ impl BotSlashCommand for Repeat { mode.into() } else { let mode = match ctx.lavalink().get_player_data(guild_id) { - Some(data) => data.write().await.queue().repeat_mode(), - None => lavalink::RepeatMode::Off, + Some(data) => data.read().await.queue().repeat_mode(), + None => LavalinkRepeatMode::Off, }; mode.next() } }; let in_voice = require::in_voice(&ctx)?; - let in_voice_cacheless = PartialInVoice::from(&in_voice); - let player = require::player(&ctx)?.and_queue_not_empty().await?; - check::in_voice_with_user(in_voice)? - .only_else_poll(Topic::Repeat(mode))? - .start(&mut ctx) + let partial_in_voice = PartialInVoice::from(&in_voice); + let player = require::player(&ctx)?; + let data = player.data(); + + let data_r = data.read().await; + require::queue_not_empty(&data_r)?; + drop(data_r); + + check::user_in(in_voice)? + .only() + .or_else_try_resolve_with(Topic::Repeat(mode))? + .and_then_start(&mut ctx) .await?; ctx.lavalink() - .connection_from(&in_voice_cacheless) - .dispatch(lavalink::Event::QueueRepeat); - player - .data() - .write() - .await - .queue_mut() - .set_repeat_mode(mode); + .connection_from(&partial_in_voice) + .dispatch(Event::QueueRepeat); + data.write().await.queue_mut().set_repeat_mode(mode); let txt = &format!("{} {}", mode.emoji(), mode); out_or_upd!(txt, ctx); diff --git a/lyra/src/component/queue/shuffle.rs b/lyra/src/component/queue/shuffle.rs index 4854c95..67b1d8a 100644 --- a/lyra/src/component/queue/shuffle.rs +++ b/lyra/src/component/queue/shuffle.rs @@ -19,12 +19,14 @@ pub struct Shuffle; impl BotSlashCommand for Shuffle { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; - check::in_voice_with_user(require::in_voice(&ctx)?)?.only()?; - let player = require::player(&ctx)?.and_queue_not_empty().await?; - + check::user_in(require::in_voice(&ctx)?)?.only()?; + let player = require::player(&ctx)?; let data = player.data(); + let data_r = data.read().await; - let indexer_type = data_r.queue().indexer_type(); + let queue = require::queue_not_empty(&data_r)?; + let indexer_type = queue.indexer_type(); + drop(data_r); match indexer_type { IndexerType::Shuffled => { diff --git a/lyra/src/component/tuning.rs b/lyra/src/component/tuning.rs index 86b0877..a182295 100644 --- a/lyra/src/component/tuning.rs +++ b/lyra/src/component/tuning.rs @@ -15,18 +15,18 @@ use crate::{ command::{ check, model::{CtxKind, GuildCtx}, - require::{self, InVoice, Player}, + require::{self, InVoice, PlayerInterface}, }, core::model::{BotStateAware, HttpAware}, - error::CommandError, gateway::{voice, GuildIdAware}, - lavalink::{DelegateMethods, LavalinkAware}, + lavalink::DelegateMethods, + CommandError, LavalinkAware, }; #[inline] fn check_user_is_dj_and_require_unsuppressed_player( ctx: &GuildCtx, -) -> Result<(InVoice, Player), CommandError> { +) -> Result<(InVoice, PlayerInterface), CommandError> { check::user_is_dj(ctx)?; let in_voice = require::in_voice(ctx)?.and_unsuppressed()?; let player = require::player(ctx)?; @@ -45,7 +45,7 @@ fn unmuting_checks(ctx: &GuildCtx) -> Result, -) -> Result<(InVoice, Player), CommandError> { +) -> Result<(InVoice, PlayerInterface), CommandError> { check::user_is_dj(ctx)?; let in_voice = require::in_voice(ctx)?; let player = require::player(ctx)?; @@ -61,7 +61,7 @@ trait UpdateFilter { async fn update_filter(&self, update: impl ApplyFilter + Send + Sync) -> LavalinkResult<()>; } -impl UpdateFilter for Player { +impl UpdateFilter for PlayerInterface { async fn update_filter(&self, update: impl ApplyFilter + Send + Sync) -> LavalinkResult<()> { let old_filter = self.info().await?.filters.unwrap_or_default(); diff --git a/lyra/src/component/tuning/equaliser.rs b/lyra/src/component/tuning/equaliser.rs index 5a0f4a4..c119571 100644 --- a/lyra/src/component/tuning/equaliser.rs +++ b/lyra/src/component/tuning/equaliser.rs @@ -20,6 +20,7 @@ impl SetEqualiser { equaliser.map(|o| o.filter(|o| (o - Self::DEFAULT_GAIN).abs() > ERR_MARGIN)); equaliser.iter().any(Option::is_some).then(|| { Self(core::array::from_fn(|i| Equalizer { + #[allow(clippy::cast_possible_truncation)] band: i as u8, gain: equaliser[i].unwrap_or(Self::DEFAULT_GAIN), })) diff --git a/lyra/src/component/tuning/equaliser/preset.rs b/lyra/src/component/tuning/equaliser/preset.rs index 3d200aa..e3d37ea 100644 --- a/lyra/src/component/tuning/equaliser/preset.rs +++ b/lyra/src/component/tuning/equaliser/preset.rs @@ -14,6 +14,7 @@ impl From for SetEqualiser { let gains = value.gains(); Self(core::array::from_fn(|i| { lavalink_rs::model::player::Equalizer { + #[allow(clippy::cast_possible_truncation)] band: i as u8, gain: gains[i], } diff --git a/lyra/src/component/tuning/filter/pitch.rs b/lyra/src/component/tuning/filter/pitch.rs index 2c55955..0c5a9df 100644 --- a/lyra/src/component/tuning/filter/pitch.rs +++ b/lyra/src/component/tuning/filter/pitch.rs @@ -11,7 +11,7 @@ use lavalink_rs::{ use lyra_proc::BotCommandGroup; use twilight_interactions::command::{CommandModel, CreateCommand}; -use crate::{command::require::Player, lavalink::Pitch as PitchModel}; +use crate::{command::require::PlayerInterface, lavalink::Pitch as PitchModel}; enum Tier { Default, @@ -40,7 +40,7 @@ impl PitchModel { } async fn shift_pitch( - player: &Player, + player: &PlayerInterface, half_tones: NonZeroI64, ) -> LavalinkResult<(PitchModel, PitchModel)> { let old_filter = player.info().await?.filters.unwrap_or_default(); diff --git a/lyra/src/component/tuning/filter/pitch/set.rs b/lyra/src/component/tuning/filter/pitch/set.rs index 0beb344..c51cc03 100644 --- a/lyra/src/component/tuning/filter/pitch/set.rs +++ b/lyra/src/component/tuning/filter/pitch/set.rs @@ -75,7 +75,7 @@ impl BotSlashCommand for Set { let (_, player) = check_user_is_dj_and_require_unsuppressed_player(&ctx)?; let Some(update) = SetPitch::new(self.multiplier) else { - bad!("Multiplier must not be 0", ctx); + bad!("Multiplier must not be zero", ctx); }; let multiplier = update.multiplier(); diff --git a/lyra/src/component/tuning/speed.rs b/lyra/src/component/tuning/speed.rs index fdd88da..fa7d7e1 100644 --- a/lyra/src/component/tuning/speed.rs +++ b/lyra/src/component/tuning/speed.rs @@ -1,11 +1,15 @@ -use lavalink_rs::model::player::{Filters, Timescale}; +use lavalink_rs::{ + error::LavalinkResult, + model::player::{Filters, Timescale}, +}; use twilight_interactions::command::{CommandModel, CreateCommand}; use crate::{ command::{ macros::{bad, out}, model::BotSlashCommand, - require, SlashCtx, + require::{self, PlayerInterface}, + SlashCtx, }, component::tuning::{check_user_is_dj_and_require_unsuppressed_player, UpdateFilter}, error::CommandResult, @@ -89,6 +93,14 @@ impl ApplyFilter for SpeedFilter { } } +impl PlayerInterface { + async fn set_speed(&self, update: SpeedFilter) -> LavalinkResult<()> { + self.data().write().await.set_speed(update.multiplier()); + self.update_filter(update).await?; + Ok(()) + } +} + /// Sets the playback speed #[derive(CommandModel, CreateCommand)] #[command(name = "speed", dm_permission = false)] @@ -107,12 +119,12 @@ impl BotSlashCommand for Speed { let Some(update) = SpeedFilter::new(self.multiplier, self.pitch_shift.unwrap_or_default()) else { - bad!("Multiplier must not be 0", ctx); + bad!("Multiplier must not be zero", ctx); }; let multiplier = update.multiplier(); let emoji = update.tier().emoji(); - player.update_filter(update).await?; + player.set_speed(update).await?; out!( format!("{emoji} Set the playback speed to `{multiplier}`ร—."), diff --git a/lyra/src/component/tuning/volume/down.rs b/lyra/src/component/tuning/volume/down.rs index 7331791..2aab979 100644 --- a/lyra/src/component/tuning/volume/down.rs +++ b/lyra/src/component/tuning/volume/down.rs @@ -8,7 +8,7 @@ use crate::{ core::model::{BotStateAware, HttpAware}, error::CommandResult, gateway::GuildIdAware, - lavalink::LavalinkAware, + LavalinkAware, }; /// Decrease the playback volume @@ -29,9 +29,10 @@ impl BotSlashCommand for Down { let data = player.data(); let old_percent = data.read().await.volume(); + #[allow(clippy::cast_possible_truncation)] let maybe_new_percent = old_percent .get() - .checked_sub(self.percent.unwrap_or(10) as u16) + .checked_sub(self.percent.unwrap_or(10).unsigned_abs() as u16) .and_then(NonZeroU16::new); let emoji = super::volume_emoji(maybe_new_percent); diff --git a/lyra/src/component/tuning/volume/set.rs b/lyra/src/component/tuning/volume/set.rs index 94d98b1..a0036a5 100644 --- a/lyra/src/component/tuning/volume/set.rs +++ b/lyra/src/component/tuning/volume/set.rs @@ -23,16 +23,14 @@ impl BotSlashCommand for Set { let (_, player) = check_user_is_dj_and_require_unsuppressed_player(&ctx)?; // SAFETY: `self.percent as u16` is in range [1, 1_000], so it is non-zero - let percent = unsafe { NonZeroU16::new_unchecked(self.percent as u16) }; + #[allow(clippy::cast_possible_truncation)] + let percent = unsafe { NonZeroU16::new_unchecked(self.percent.unsigned_abs() as u16) }; player.context.set_volume(percent.get()).await?; player.data().write().await.set_volume(percent); let emoji = super::volume_emoji(Some(percent)); let warning = super::clipping_warning(percent); - out!( - format!("{emoji} Set playback volume to `{percent}`%{warning}."), - ctx - ); + out!(format!("{emoji} `{percent}`%{warning}"), ctx); } } diff --git a/lyra/src/component/tuning/volume/toggle_mute.rs b/lyra/src/component/tuning/volume/toggle_mute.rs index a214878..0dcfcc8 100644 --- a/lyra/src/component/tuning/volume/toggle_mute.rs +++ b/lyra/src/component/tuning/volume/toggle_mute.rs @@ -6,7 +6,7 @@ use crate::{ core::model::{BotStateAware, HttpAware}, error::CommandResult, gateway::GuildIdAware, - lavalink::LavalinkAware, + LavalinkAware, }; /// Toggles server muting the bot diff --git a/lyra/src/component/tuning/volume/up.rs b/lyra/src/component/tuning/volume/up.rs index 927a9ec..c223672 100644 --- a/lyra/src/component/tuning/volume/up.rs +++ b/lyra/src/component/tuning/volume/up.rs @@ -12,7 +12,7 @@ use crate::{ core::model::{BotStateAware, HttpAware}, error::CommandResult, gateway::GuildIdAware, - lavalink::LavalinkAware, + LavalinkAware, }; /// Increase the playback volume @@ -26,16 +26,18 @@ pub struct Up { impl BotSlashCommand for Up { async fn run(self, ctx: SlashCtx) -> CommandResult { + // SAFETY: `1_000` is non-zero + const MAX_PERCENT: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(1_000) }; + let mut ctx = require::guild(ctx)?; let (in_voice, player) = check_user_is_dj_and_require_player(&ctx)?; let lavalink = ctx.lavalink(); let guild_id = ctx.guild_id(); let data = player.data(); - let percent_u16 = self.percent.unwrap_or(10) as u16; + #[allow(clippy::cast_possible_truncation)] + let percent_u16 = self.percent.unwrap_or(10).unsigned_abs() as u16; - // SAFETY: `1_000` is non-zero - let max_percent = unsafe { NonZeroU16::new_unchecked(1_000) }; let (old_percent_str, new_percent) = if lavalink.connection_from(&in_voice).mute { lavalink.connection_mut_from(&in_voice).mute = false; ctx.http() @@ -51,20 +53,20 @@ impl BotSlashCommand for Up { } else { let old_percent = data.read().await.volume(); - if old_percent >= max_percent { + if old_percent >= MAX_PERCENT { note!("Already at max playback volume.", ctx); } ( format!("`{old_percent}%`"), - old_percent.saturating_add(percent_u16).min(max_percent), + old_percent.saturating_add(percent_u16).min(MAX_PERCENT), ) }; let emoji = super::volume_emoji(Some(new_percent)); let warning = super::clipping_warning(new_percent); - let maxed_note = (new_percent == max_percent) + let maxed_note = (new_percent == MAX_PERCENT) .then_some(" (`Max`)") .unwrap_or_default(); diff --git a/lyra/src/core/const.rs b/lyra/src/core/const.rs index efbbbd8..8c24a54 100644 --- a/lyra/src/core/const.rs +++ b/lyra/src/core/const.rs @@ -77,9 +77,9 @@ pub mod connection { pub const INACTIVITY_TIMEOUT_SECS: u16 = 600; pub const INACTIVITY_TIMEOUT_POLL_N: u8 = 10; - pub fn connection_changed_timeout() -> &'static Duration { - static CONNECTION_CHANGED_TIMEOUT: OnceLock = OnceLock::new(); - CONNECTION_CHANGED_TIMEOUT.get_or_init(|| Duration::from_millis(500)) + pub fn changed_timeout() -> &'static Duration { + static CHANGED_TIMEOUT: OnceLock = OnceLock::new(); + CHANGED_TIMEOUT.get_or_init(|| Duration::from_millis(250)) } pub fn get_lavalink_connection_info_timeout() -> &'static Duration { @@ -112,6 +112,11 @@ pub mod misc { static DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT: OnceLock = OnceLock::new(); DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT.get_or_init(|| Duration::from_secs(60)) } + + pub fn queue_advance_locked_timeout() -> &'static Duration { + static QUEUE_ADVANCE_LOCKED_TIMEOUT: OnceLock = OnceLock::new(); + QUEUE_ADVANCE_LOCKED_TIMEOUT.get_or_init(|| Duration::from_millis(250)) + } } pub mod text { diff --git a/lyra/src/core/model.rs b/lyra/src/core/model.rs index 67f95a4..e23b170 100644 --- a/lyra/src/core/model.rs +++ b/lyra/src/core/model.rs @@ -21,10 +21,7 @@ use twilight_model::{ }; use twilight_standby::Standby; -use crate::{ - error::core::DeserializeBodyFromHttpError, - lavalink::{self, Lavalink}, -}; +use crate::{error::core::DeserializeBodyFromHttpError, lavalink::Lavalink, LavalinkAware}; pub use self::interaction::{ Client as InteractionClient, Interface as InteractionInterface, MessageResponse, @@ -188,7 +185,7 @@ impl BotState { } } -impl lavalink::LavalinkAware for BotState { +impl LavalinkAware for BotState { fn lavalink(&self) -> &Lavalink { &self.lavalink } diff --git a/lyra/src/core/model/interaction.rs b/lyra/src/core/model/interaction.rs index e1f775c..9031d08 100644 --- a/lyra/src/core/model/interaction.rs +++ b/lyra/src/core/model/interaction.rs @@ -1,3 +1,5 @@ +use std::fmt::{Display, Write}; + use twilight_http::{request::application::interaction::UpdateResponse, Response}; use twilight_model::{ application::{command::CommandOptionChoice, interaction::Interaction}, @@ -7,7 +9,7 @@ use twilight_model::{ }, http::interaction::{InteractionResponse, InteractionResponseData, InteractionResponseType}, id::{ - marker::{InteractionMarker, MessageMarker}, + marker::{CommandMarker, InteractionMarker, MessageMarker}, Id, }, }; @@ -21,6 +23,12 @@ use crate::{ error::core::{FollowupResult, RegisterGlobalCommandsError, RespondResult}, }; +pub type MessageResponse = Response; +pub type UnitRespondResult = RespondResult<()>; +pub type MessageRespondResult = RespondResult; +pub type UnitFollowupResult = FollowupResult<()>; +pub type MessageFollowupResult = FollowupResult; + pub struct Client<'a>(twilight_http::client::InteractionClient<'a>); pub struct Interface<'a> { @@ -231,21 +239,22 @@ impl<'a> Client<'a> { pub fn populated_command( ) -> &'static twilight_model::application::command::Command { + let name = T::name(); POPULATED_COMMANDS_MAP .get() .unwrap_or_else(|| panic!("`POPULATED_COMMANDS_MAP` is not yet populated")) - .get(T::name()) - .unwrap_or_else(|| panic!("command not found: {}", T::name())) + .get(name) + .unwrap_or_else(|| panic!("command not found: {name}")) } - pub fn mention_command() -> Box { + pub fn mention_command() -> MentionCommand { let cmd = Self::populated_command::(); - let name = &cmd.name; + let name = cmd.name.clone().into(); let id = cmd .id .unwrap_or_else(|| panic!("`POPULATED_COMMANDS_MAP` is not yet populated")); - format!("").into_boxed_str() + MentionCommand::new(name, id) } #[inline] @@ -266,8 +275,25 @@ impl<'a> Client<'a> { } } -pub type MessageResponse = Response; -pub type UnitRespondResult = RespondResult<()>; -pub type MessageRespondResult = RespondResult; -pub type UnitFollowupResult = FollowupResult<()>; -pub type MessageFollowupResult = FollowupResult; +pub struct MentionCommand { + name: Box, + id: Id, +} + +impl MentionCommand { + pub const fn new(name: Box, id: Id) -> Self { + Self { name, id } + } +} + +impl Display for MentionCommand { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("')?; + + Ok(()) + } +} diff --git a/lyra/src/core/traced.rs b/lyra/src/core/traced.rs index 11542f1..988ec77 100644 --- a/lyra/src/core/traced.rs +++ b/lyra/src/core/traced.rs @@ -1,13 +1,11 @@ -use std::{error::Error, fmt::Debug, future::Future}; +use std::{error::Error, future::Future}; use tokio::task::JoinHandle; use tracing::Instrument; -pub fn tokio_spawn(fut: F) -> JoinHandle<()> -where - E: Error + Debug, - F: Future> + Send + 'static, -{ +pub fn tokio_spawn( + fut: impl Future> + Send + 'static, +) -> JoinHandle<()> { tokio::spawn( async move { if let Err(error) = fut.await { diff --git a/lyra/src/error.rs b/lyra/src/error.rs index eb45205..f093f80 100644 --- a/lyra/src/error.rs +++ b/lyra/src/error.rs @@ -5,7 +5,6 @@ pub mod gateway; pub mod lavalink; pub mod runner; -pub use command::Error as CommandError; pub use command::Result as CommandResult; use thiserror::Error; @@ -15,8 +14,11 @@ use twilight_model::id::{ Id, }; -pub trait EPrint: std::error::Error + std::fmt::Debug { - fn eprint(&self) -> String; +use crate::command::require::PartialInVoice; + +pub trait PrettyErrorDisplay<'a> { + type Displayer: std::fmt::Display; + fn pretty_display(&'a self) -> Self::Displayer; } #[derive(Error, Debug)] @@ -66,14 +68,25 @@ pub struct InVoiceAlready(pub Id); pub struct InVoiceWithoutUser(pub Id); #[derive(Error, Debug)] -#[error("bot is already in voice and someone else also is: {}", .0)] -pub struct InVoiceWithSomeoneElse(pub Id); +#[error("bot is already in voice and someone else also is: {}", .0.channel_id())] +pub struct InVoiceWithSomeoneElse(pub PartialInVoice); + +impl<'a> PrettyErrorDisplay<'a> for InVoiceWithSomeoneElse { + type Displayer = PrettyInVoiceWithSomeoneElseDisplayer<'a>; + + fn pretty_display(&'a self) -> Self::Displayer { + PrettyInVoiceWithSomeoneElseDisplayer(self) + } +} + +pub struct PrettyInVoiceWithSomeoneElseDisplayer<'a>(&'a InVoiceWithSomeoneElse); -impl EPrint for InVoiceWithSomeoneElse { - fn eprint(&self) -> String { - format!( +impl std::fmt::Display for PrettyInVoiceWithSomeoneElseDisplayer<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, "There are someone else in {}; You need to be a ***DJ*** to do that.", - self.0.mention(), + self.0 .0.channel_id().mention(), ) } } @@ -107,8 +120,18 @@ pub struct NotPlaying; #[error("queue is not seekable")] pub struct QueueNotSeekable; -impl EPrint for QueueNotSeekable { - fn eprint(&self) -> String { +impl<'a> PrettyErrorDisplay<'a> for QueueNotSeekable { + type Displayer = PrettyQueueNotSeekableDisplayer; + + fn pretty_display(&'a self) -> Self::Displayer { + PrettyQueueNotSeekableDisplayer + } +} + +pub struct PrettyQueueNotSeekableDisplayer; + +impl std::fmt::Display for PrettyQueueNotSeekableDisplayer { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { todo!() } } @@ -122,14 +145,26 @@ pub struct NotUsersTrack { pub channel_id: Id, } -impl EPrint for NotUsersTrack { - fn eprint(&self) -> String { - format!( +impl<'a> PrettyErrorDisplay<'a> for NotUsersTrack { + type Displayer = PrettyNotUsersTrackDisplayer<'a>; + + fn pretty_display(&'a self) -> Self::Displayer { + PrettyNotUsersTrackDisplayer(self) + } +} + +pub struct PrettyNotUsersTrackDisplayer<'a>(&'a NotUsersTrack); + +impl std::fmt::Display for PrettyNotUsersTrackDisplayer<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner = self.0; + write!( + f, "`{}` (`#{}`) was requested by {} and you're not the only person in {}; You'll need to be a ***DJ*** to do that.", - self.title, - self.position, - self.requester.mention(), - self.channel_id.mention(), + inner.title, + inner.position, + inner.requester.mention(), + inner.channel_id.mention(), ) } } @@ -164,7 +199,7 @@ pub struct PrettifiedTimestampParse; #[derive(Error, Debug)] #[error("error running the bot starter: {}", .0)] -pub enum RunError { +pub enum Run { ColorEyre(#[from] color_eyre::Report), Dotenvy(#[from] dotenvy::Error), StartError(#[from] runner::StartError), diff --git a/lyra/src/error/command.rs b/lyra/src/error/command.rs index 8f9640c..9cd1afc 100644 --- a/lyra/src/error/command.rs +++ b/lyra/src/error/command.rs @@ -1,6 +1,7 @@ pub mod check; pub mod declare; pub mod poll; +pub mod require; pub mod util; #[derive(thiserror::Error, Debug)] @@ -27,10 +28,10 @@ pub enum Error { NotInVoice(#[from] super::NotInVoice), InVoiceWithoutUser(#[from] super::InVoiceWithoutUser), QueueEmpty(#[from] super::QueueEmpty), - CheckNotSuppressed(#[from] check::NotSuppressedError), + RequireUnsuppressed(#[from] require::UnsuppressedError), CheckUsersTrack(#[from] check::UsersTrackError), UserNotDj(#[from] super::UserNotDj), - InVoiceWithSomeoneElse(#[from] check::InVoiceWithSomeoneElseError), + RequireInVoiceWithSomeoneElse(#[from] require::InVoiceWithSomeoneElseError), PositionOutOfRange(#[from] super::PositionOutOfRange), CheckRun(#[from] check::RunError), Respond(#[from] RespondError), @@ -38,9 +39,6 @@ pub enum Error { PromptForConfirmation(#[from] util::PromptForConfirmationError), Join(#[from] super::component::connection::join::ResidualError), Leave(#[from] super::component::connection::leave::ResidualError), - WithAdvanceLockAndStopped( - #[from] super::component::queue::remove::WithAdvanceLockAndStoppedError, - ), Play(#[from] super::component::queue::play::Error), DeserializeBodyFromHttp(#[from] super::core::DeserializeBodyFromHttpError), RemoveTracks(#[from] super::component::queue::RemoveTracksError), @@ -51,109 +49,109 @@ pub enum Error { CheckUserOnlyIn(#[from] check::UserOnlyInError), Cache(#[from] super::Cache), HandlePoll(#[from] check::HandlePollError), + NotPlaying(#[from] super::NotPlaying), + Paused(#[from] super::Paused), } pub enum FlattenedError<'a> { - UserNotAccessManager(&'a super::UserNotAccessManager), - Sqlx(&'a sqlx::Error), - TaskJoin(&'a tokio::task::JoinError), - EmbedValidation(&'a twilight_validate::embed::EmbedValidationError), - NotInVoice(&'a super::NotInVoice), InVoiceWithoutUser(&'a super::InVoiceWithoutUser), - QueueEmpty(&'a super::QueueEmpty), - PositionOutOfRange(&'a super::PositionOutOfRange), - Cache(&'a super::Cache), Suppressed(&'a super::Suppressed), NotUsersTrack(&'a super::NotUsersTrack), - UserNotDj(&'a super::UserNotDj), InVoiceWithoutSomeoneElse(&'a super::InVoiceWithoutSomeoneElse), - NotPlaying(&'a super::NotPlaying), - Paused(&'a super::Paused), - Stopped(&'a super::Stopped), InVoiceWithSomeoneElse(&'a super::InVoiceWithSomeoneElse), QueueNotSeekable(&'a super::QueueNotSeekable), AnotherPollOngoing(&'a check::AnotherPollOngoingError), - TwilightHttp(&'a twilight_http::Error), - DeserializeBody(&'a twilight_http::response::DeserializeBodyError), - EventSend(&'a tokio::sync::broadcast::error::SendError), - EventRecv(&'a tokio::sync::broadcast::error::RecvError), - ImageSourceUrl(&'a twilight_util::builder::embed::image_source::ImageSourceUrlError), - MessageValidation(&'a twilight_validate::message::MessageValidationError), + PositionOutOfRange(&'a super::PositionOutOfRange), PollLoss(&'a check::PollLossError), PollVoided(&'a check::PollVoidedError), - StandbyCanceled(&'a twilight_standby::future::Canceled), - ConfirmationTimedOut(&'a super::ConfirmationTimedOut), - GatewaySend(&'a twilight_gateway::error::ChannelError), AutoJoinSuppressed(&'a util::AutoJoinSuppressedError), AutoJoinAttemptFailed(&'a super::AutoJoinAttemptFailed), - Lavalink(&'a lavalink_rs::error::LavalinkError), - NoPlayer(&'a super::lavalink::NoPlayerError), - NotInGuild(&'a super::NotInGuild), + UserNotAccessManager, + Sqlx, + TaskJoin, + EmbedValidation, + NotInVoice, + QueueEmpty, + Cache, + UserNotDj, + NotPlaying, + Paused, + Stopped, + TwilightHttp, + DeserializeBody, + EventSend, + EventRecv, + ImageSourceUrl, + MessageValidation, + StandbyCanceled, + ConfirmationTimedOut, + GatewaySend, + Lavalink, + NoPlayer, + NotInGuild, } pub use FlattenedError as Fe; impl<'a> Fe<'a> { - const fn from_core_followup_error(error: &'a super::core::FollowupError) -> Fe<'a> { + const fn from_core_followup_error(error: &'a super::core::FollowupError) -> Self { match error { - super::core::FollowupError::TwilightHttp(e) => Self::TwilightHttp(e), - super::core::FollowupError::MessageValidation(e) => Self::MessageValidation(e), + super::core::FollowupError::TwilightHttp(_) => Self::TwilightHttp, + super::core::FollowupError::MessageValidation(_) => Self::MessageValidation, } } - const fn from_check_not_suppressed_error(error: &'a check::NotSuppressedError) -> Fe<'a> { + const fn from_require_unsuppressed_error(error: &'a require::UnsuppressedError) -> Self { match error { - check::NotSuppressedError::Cache(e) => Self::Cache(e), - check::NotSuppressedError::Suppressed(e) => Self::Suppressed(e), + require::UnsuppressedError::Cache(_) => Self::Cache, + require::UnsuppressedError::Suppressed(e) => Self::Suppressed(e), } } const fn from_deserialize_body_from_http_error( error: &'a super::core::DeserializeBodyFromHttpError, - ) -> Fe<'a> { + ) -> Self { match error { - super::core::DeserializeBodyFromHttpError::TwilightHttp(e) => Self::TwilightHttp(e), - super::core::DeserializeBodyFromHttpError::DeserializeBody(e) => { - Self::DeserializeBody(e) - } + super::core::DeserializeBodyFromHttpError::TwilightHttp(_) => Self::TwilightHttp, + super::core::DeserializeBodyFromHttpError::DeserializeBody(_) => Self::DeserializeBody, } } - const fn from_users_track_error(error: &'a check::UsersTrackError) -> Fe<'a> { + const fn from_users_track_error(error: &'a check::UsersTrackError) -> Self { match error { - check::UsersTrackError::Cache(e) => Self::Cache(e), + check::UsersTrackError::Cache(_) => Self::Cache, check::UsersTrackError::NotUsersTrack(e) => Self::NotUsersTrack(e), } } - const fn from_in_voice_with_someone_else_error( - error: &'a check::InVoiceWithSomeoneElseError, + const fn from_require_in_voice_with_someone_else_error( + error: &'a require::InVoiceWithSomeoneElseError, ) -> Self { match error { - check::InVoiceWithSomeoneElseError::Cache(e) => Self::Cache(e), - check::InVoiceWithSomeoneElseError::InVoiceWithoutSomeoneElse(e) => { + require::InVoiceWithSomeoneElseError::Cache(_) => Self::Cache, + require::InVoiceWithSomeoneElseError::InVoiceWithoutSomeoneElse(e) => { Self::InVoiceWithoutSomeoneElse(e) } } } - const fn from_run(error: &'a check::RunError) -> Fe<'a> { + const fn from_run(error: &'a check::RunError) -> Self { match error { - check::RunError::NotInVoice(e) => Self::NotInVoice(e), - check::RunError::QueueEmpty(e) => Self::QueueEmpty(e), - check::RunError::NotPlaying(e) => Self::NotPlaying(e), + check::RunError::NotInVoice(_) => Self::NotInVoice, + check::RunError::QueueEmpty(_) => Self::QueueEmpty, + check::RunError::NotPlaying(_) => Self::NotPlaying, + check::RunError::Cache(_) => Self::Cache, + check::RunError::Paused(_) => Self::Paused, + check::RunError::Stopped(_) => Self::Stopped, check::RunError::InVoiceWithoutUser(e) => Self::InVoiceWithoutUser(e), - check::RunError::Cache(e) => Self::Cache(e), - check::RunError::Paused(e) => Self::Paused(e), - check::RunError::Stopped(e) => Self::Stopped(e), - check::RunError::NotSuppressed(e) => Self::from_check_not_suppressed_error(e), + check::RunError::NotSuppressed(e) => Self::from_require_unsuppressed_error(e), check::RunError::HandleInVoiceWithSomeoneElse(e) => { Self::from_handle_in_voice_with_someone_else_error(e) } } } - const fn from_vote_resolvable(error: &'a check::PollResolvableError) -> Fe<'a> { + const fn from_vote_resolvable(error: &'a check::PollResolvableError) -> Self { match error { check::PollResolvableError::InVoiceWithSomeoneElse(e) => { Self::InVoiceWithSomeoneElse(e) @@ -163,26 +161,26 @@ impl<'a> Fe<'a> { } } - const fn from_update_embed(error: &'a poll::UpdateEmbedError) -> Fe<'a> { + const fn from_update_embed(error: &'a poll::UpdateEmbedError) -> Self { match error { - poll::UpdateEmbedError::Http(e) => Self::TwilightHttp(e), - poll::UpdateEmbedError::EmbedValidation(e) => Self::EmbedValidation(e), - poll::UpdateEmbedError::MessageValidation(e) => Self::MessageValidation(e), + poll::UpdateEmbedError::Http(_) => Self::TwilightHttp, + poll::UpdateEmbedError::EmbedValidation(_) => Self::EmbedValidation, + poll::UpdateEmbedError::MessageValidation(_) => Self::MessageValidation, poll::UpdateEmbedError::Followup(e) => Self::from_core_followup_error(e), } } - const fn from_generate_embed(error: &'a poll::GenerateEmbedError) -> Fe<'a> { + const fn from_generate_embed(error: &'a poll::GenerateEmbedError) -> Self { match error { - poll::GenerateEmbedError::ImageSourceUrl(e) => Self::ImageSourceUrl(e), - poll::GenerateEmbedError::EmbedValidation(e) => Self::EmbedValidation(e), + poll::GenerateEmbedError::ImageSourceUrl(_) => Self::ImageSourceUrl, + poll::GenerateEmbedError::EmbedValidation(_) => Self::EmbedValidation, } } - const fn from_wait_for_votes(error: &'a poll::WaitForVotesError) -> Fe<'a> { + const fn from_wait_for_votes(error: &'a poll::WaitForVotesError) -> Self { match error { - poll::WaitForVotesError::TwilightHttp(e) => Self::TwilightHttp(e), - poll::WaitForVotesError::EventRecv(e) => Self::EventRecv(e), + poll::WaitForVotesError::TwilightHttp(_) => Self::TwilightHttp, + poll::WaitForVotesError::EventRecv(_) => Self::EventRecv, poll::WaitForVotesError::DeserializeBodyFromHttp(e) => { Self::from_deserialize_body_from_http_error(e) } @@ -190,23 +188,23 @@ impl<'a> Fe<'a> { } } - const fn from_start_poll(error: &'a poll::StartPollError) -> Fe<'a> { + const fn from_start_poll(error: &'a poll::StartPollError) -> Self { match error { - poll::StartPollError::Cache(e) => Self::Cache(e), - poll::StartPollError::DeserializeBody(e) => Self::DeserializeBody(e), + poll::StartPollError::Cache(_) => Self::Cache, + poll::StartPollError::DeserializeBody(_) => Self::DeserializeBody, poll::StartPollError::Respond(e) => Self::from_respond(e), poll::StartPollError::GenerateEmbed(e) => Self::from_generate_embed(e), poll::StartPollError::WaitForVotes(e) => Self::from_wait_for_votes(e), } } - const fn from_handle_poll(error: &'a check::HandlePollError) -> Fe<'a> { + const fn from_handle_poll(error: &'a check::HandlePollError) -> Self { match error { + check::HandlePollError::EventRecv(_) => Self::EventRecv, + check::HandlePollError::EventSend(_) => Self::EventSend, check::HandlePollError::AnotherPollOngoing(e) => Self::AnotherPollOngoing(e), - check::HandlePollError::EventSend(e) => Self::EventSend(e), check::HandlePollError::PollLoss(e) => Self::PollLoss(e), check::HandlePollError::PollVoided(e) => Self::PollVoided(e), - check::HandlePollError::EventRecv(e) => Self::EventRecv(e), check::HandlePollError::StartPoll(e) => Self::from_start_poll(e), check::HandlePollError::DeserializeBodyFromHttp(e) => { Self::from_deserialize_body_from_http_error(e) @@ -216,7 +214,7 @@ impl<'a> Fe<'a> { const fn from_handle_in_voice_with_someone_else_error( error: &'a check::HandleInVoiceWithSomeoneElseError, - ) -> Fe<'a> { + ) -> Self { match error { check::HandleInVoiceWithSomeoneElseError::PollResolvable(e) => { Self::from_vote_resolvable(e) @@ -225,16 +223,16 @@ impl<'a> Fe<'a> { } } - const fn from_respond(error: &'a RespondError) -> Fe<'a> { + const fn from_respond(error: &'a RespondError) -> Self { match error { - RespondError::TwilightHttp(e) => Self::TwilightHttp(e), + RespondError::TwilightHttp(_) => Self::TwilightHttp, RespondError::DeserializeBodyFromHttp(e) => { Self::from_deserialize_body_from_http_error(e) } } } - const fn from_followup(error: &'a FollowupError) -> Fe<'a> { + const fn from_followup(error: &'a FollowupError) -> Self { match error { FollowupError::DeserializeBodyFromHttp(e) => { Self::from_deserialize_body_from_http_error(e) @@ -243,33 +241,31 @@ impl<'a> Fe<'a> { } } - const fn from_prompt_for_confirmation(error: &'a util::PromptForConfirmationError) -> Fe<'a> { + const fn from_prompt_for_confirmation(error: &'a util::PromptForConfirmationError) -> Self { match error { - util::PromptForConfirmationError::StandbyCanceled(e) => Self::StandbyCanceled(e), - util::PromptForConfirmationError::ConfirmationTimedout(e) => { - Self::ConfirmationTimedOut(e) - } + util::PromptForConfirmationError::StandbyCanceled(_) => Self::StandbyCanceled, + util::PromptForConfirmationError::ConfirmationTimedout(_) => Self::ConfirmationTimedOut, util::PromptForConfirmationError::Respond(e) => Self::from_respond(e), } } - const fn from_access_calculator_build(error: &'a check::AccessCalculatorBuildError) -> Fe<'a> { + const fn from_access_calculator_build(error: &'a check::AccessCalculatorBuildError) -> Self { match error { - check::AccessCalculatorBuildError::Sqlx(e) => Self::Sqlx(e), - check::AccessCalculatorBuildError::TaskJoin(e) => Self::TaskJoin(e), + check::AccessCalculatorBuildError::Sqlx(_) => Self::Sqlx, + check::AccessCalculatorBuildError::TaskJoin(_) => Self::TaskJoin, } } - const fn from_check_user_only_in(error: &'a check::UserOnlyInError) -> Fe<'a> { + const fn from_check_user_only_in(error: &'a check::UserOnlyInError) -> Self { match error { - check::UserOnlyInError::Cache(e) => Self::Cache(e), + check::UserOnlyInError::Cache(_) => Self::Cache, check::UserOnlyInError::InVoiceWithSomeoneElse(e) => Self::InVoiceWithSomeoneElse(e), } } const fn from_check_user_allowed_residual( error: &'a super::component::connection::join::ResidualUserAllowedError, - ) -> Fe<'a> { + ) -> Self { match error { super::component::connection::join::ResidualUserAllowedError::AccessCalculatorBuild( e, @@ -279,19 +275,17 @@ impl<'a> Fe<'a> { const fn from_impl_connect_to_residual( error: &'a super::component::connection::join::ResidualImplConnectToError, - ) -> Fe<'a> { + ) -> Self { match error { - super::component::connection::join::ResidualImplConnectToError::Cache(e) => { - Self::Cache(e) - } - super::component::connection::join::ResidualImplConnectToError::GatewaySend(e) => { - Self::GatewaySend(e) + super::component::connection::join::ResidualImplConnectToError::Cache(_) => Self::Cache, + super::component::connection::join::ResidualImplConnectToError::GatewaySend(_) => { + Self::GatewaySend } - super::component::connection::join::ResidualImplConnectToError::TwilightHttp(e) => { - Self::TwilightHttp(e) + super::component::connection::join::ResidualImplConnectToError::TwilightHttp(_) => { + Self::TwilightHttp } - super::component::connection::join::ResidualImplConnectToError::Lavalink(e) => { - Self::Lavalink(e) + super::component::connection::join::ResidualImplConnectToError::Lavalink(_) => { + Self::Lavalink } super::component::connection::join::ResidualImplConnectToError::CheckUserAllowed(e) => { Self::from_check_user_allowed_residual(e) @@ -301,7 +295,7 @@ impl<'a> Fe<'a> { const fn from_connect_to_residual( error: &'a super::component::connection::join::ResidualConnectToError, - ) -> Fe<'a> { + ) -> Self { match error { super::component::connection::join::ResidualConnectToError::CheckUserOnlyIn(e) => { Self::from_check_user_only_in(e) @@ -314,17 +308,17 @@ impl<'a> Fe<'a> { const fn from_get_users_voice_channel_residual( error: &'a super::component::connection::join::ResidualGetUsersVoiceChannelError, - ) -> Fe<'a> { + ) -> Self { match error { - super::component::connection::join::ResidualGetUsersVoiceChannelError::Cache(e) => { - Self::Cache(e) + super::component::connection::join::ResidualGetUsersVoiceChannelError::Cache(_) => { + Self::Cache } } } const fn from_impl_join_residual( error: &'a super::component::connection::join::ResidualImplJoinError, - ) -> Fe<'a> { + ) -> Self { match error { super::component::connection::join::ResidualImplJoinError::GetUsersVoiceChannel(e) => { Self::from_get_users_voice_channel_residual(e) @@ -337,11 +331,11 @@ impl<'a> Fe<'a> { const fn from_handle_response( error: &'a super::component::connection::join::HandleResponseError, - ) -> Fe<'a> { + ) -> Self { match error { - super::component::connection::join::HandleResponseError::Cache(e) => Self::Cache(e), - super::component::connection::join::HandleResponseError::DeserializeBody(e) => { - Self::DeserializeBody(e) + super::component::connection::join::HandleResponseError::Cache(_) => Self::Cache, + super::component::connection::join::HandleResponseError::DeserializeBody(_) => { + Self::DeserializeBody } super::component::connection::join::HandleResponseError::Respond(e) => { Self::from_respond(e) @@ -354,7 +348,7 @@ impl<'a> Fe<'a> { const fn from_join_residual( error: &'a super::component::connection::join::ResidualError, - ) -> Fe<'a> { + ) -> Self { match error { super::component::connection::join::ResidualError::ImplJoin(e) => { Self::from_impl_join_residual(e) @@ -367,27 +361,25 @@ impl<'a> Fe<'a> { const fn from_pre_disconnect_cleanup( error: &'a super::component::connection::leave::PreDisconnectCleanupError, - ) -> Fe<'a> { + ) -> Self { match error { - super::component::connection::leave::PreDisconnectCleanupError::EventSend(e) => { - Self::EventSend(e) + super::component::connection::leave::PreDisconnectCleanupError::EventSend(_) => { + Self::EventSend } - super::component::connection::leave::PreDisconnectCleanupError::Lavalink(e) => { - Self::Lavalink(e) + super::component::connection::leave::PreDisconnectCleanupError::Lavalink(_) => { + Self::Lavalink } } } const fn from_leave_residual( error: &'a super::component::connection::leave::ResidualError, - ) -> Fe<'a> { + ) -> Self { match error { + super::component::connection::leave::ResidualError::GatewaySend(_) => Self::GatewaySend, super::component::connection::leave::ResidualError::InVoiceWithoutUser(e) => { Self::InVoiceWithoutUser(e) } - super::component::connection::leave::ResidualError::GatewaySend(e) => { - Self::GatewaySend(e) - } super::component::connection::leave::ResidualError::CheckUserOnlyIn(e) => { Self::from_check_user_only_in(e) } @@ -397,21 +389,11 @@ impl<'a> Fe<'a> { } } - const fn from_with_advance_lock_and_stopped( - error: &'a super::component::queue::remove::WithAdvanceLockAndStoppedError, - ) -> Fe<'a> { - match error { - super::component::queue::remove::WithAdvanceLockAndStoppedError::Lavalink(e) => { - Self::Lavalink(e) - } - } - } - const fn from_handle_suppressed_auto_join( error: &'a util::HandleSuppressedAutoJoinError, - ) -> Fe<'a> { + ) -> Self { match error { - util::HandleSuppressedAutoJoinError::DeserializeBody(e) => Self::DeserializeBody(e), + util::HandleSuppressedAutoJoinError::DeserializeBody(_) => Self::DeserializeBody, util::HandleSuppressedAutoJoinError::FollowUp(e) => Self::from_followup(e), util::HandleSuppressedAutoJoinError::AutoJoinSuppressed(e) => { Self::AutoJoinSuppressed(e) @@ -423,7 +405,7 @@ impl<'a> Fe<'a> { error: &'a util::ResidualGetUsersVoiceChannelError, ) -> Self { match error { - util::ResidualGetUsersVoiceChannelError::Cache(e) => Self::Cache(e), + util::ResidualGetUsersVoiceChannelError::Cache(_) => Self::Cache, } } @@ -437,10 +419,10 @@ impl<'a> Fe<'a> { const fn from_impl_connect_to_residual_2(error: &'a util::ResidualImplConnectToError) -> Self { match error { - util::ResidualImplConnectToError::Lavalink(e) => Self::Lavalink(e), - util::ResidualImplConnectToError::Cache(e) => Self::Cache(e), - util::ResidualImplConnectToError::GatewaySend(e) => Self::GatewaySend(e), - util::ResidualImplConnectToError::TwilightHttp(e) => Self::TwilightHttp(e), + util::ResidualImplConnectToError::Lavalink(_) => Self::Lavalink, + util::ResidualImplConnectToError::Cache(_) => Self::Cache, + util::ResidualImplConnectToError::GatewaySend(_) => Self::GatewaySend, + util::ResidualImplConnectToError::TwilightHttp(_) => Self::TwilightHttp, util::ResidualImplConnectToError::CheckUserAllowed(e) => { Self::from_check_user_allowed_residual_2(e) } @@ -466,7 +448,7 @@ impl<'a> Fe<'a> { } } - const fn from_auto_join_attempt(error: &'a util::AutoJoinAttemptError) -> Fe<'a> { + const fn from_auto_join_attempt(error: &'a util::AutoJoinAttemptError) -> Self { match error { util::AutoJoinAttemptError::Failed(e) => Self::AutoJoinAttemptFailed(e), util::AutoJoinAttemptError::ImplAutoJoin(e) => Self::from_impl_auto_join_residual(e), @@ -476,13 +458,13 @@ impl<'a> Fe<'a> { const fn from_auto_join_or_check_in_voice_with_user( error: &'a util::AutoJoinOrCheckInVoiceWithUserError, - ) -> Fe<'a> { + ) -> Self { match error { util::AutoJoinOrCheckInVoiceWithUserError::InVoiceWithoutUser(e) => { Self::InVoiceWithoutUser(e) } - util::AutoJoinOrCheckInVoiceWithUserError::CheckNotSuppressed(e) => { - Self::from_check_not_suppressed_error(e) + util::AutoJoinOrCheckInVoiceWithUserError::RequireUnsuppressed(e) => { + Self::from_require_unsuppressed_error(e) } util::AutoJoinOrCheckInVoiceWithUserError::HandleSuppressedAutoJoin(e) => { Self::from_handle_suppressed_auto_join(e) @@ -493,11 +475,11 @@ impl<'a> Fe<'a> { } } - const fn from_play(error: &'a super::component::queue::play::Error) -> Fe<'a> { + const fn from_play(error: &'a super::component::queue::play::Error) -> Self { match error { - super::component::queue::play::Error::Lavalink(e) => Self::Lavalink(e), - super::component::queue::play::Error::CheckNotSuppressed(e) => { - Self::from_check_not_suppressed_error(e) + super::component::queue::play::Error::Lavalink(_) => Self::Lavalink, + super::component::queue::play::Error::RequireUnsuppressed(e) => { + Self::from_require_unsuppressed_error(e) } super::component::queue::play::Error::Respond(e) => Self::from_respond(e), super::component::queue::play::Error::Followup(e) => Self::from_followup(e), @@ -507,11 +489,9 @@ impl<'a> Fe<'a> { } } - const fn from_remove_tracks(error: &'a super::component::queue::RemoveTracksError) -> Fe<'a> { + const fn from_remove_tracks(error: &'a super::component::queue::RemoveTracksError) -> Self { match error { - super::component::queue::RemoveTracksError::TryWithAdvanceLock(e) => { - Self::from_with_advance_lock_and_stopped(e) - } + super::component::queue::RemoveTracksError::Lavalink(_) => Self::Lavalink, super::component::queue::RemoveTracksError::Respond(e) => Self::from_respond(e), super::component::queue::RemoveTracksError::Followup(e) => Self::from_followup(e), super::component::queue::RemoveTracksError::DeserializeBodyFromHttp(e) => { @@ -524,30 +504,33 @@ impl<'a> Fe<'a> { impl Error { pub const fn flatten_as(&self) -> Fe<'_> { match self { - Self::UserNotAccessManager(e) => Fe::UserNotAccessManager(e), - Self::Sqlx(e) => Fe::Sqlx(e), - Self::TaskJoin(e) => Fe::TaskJoin(e), - Self::EmbedValidation(e) => Fe::EmbedValidation(e), - Self::NotInVoice(e) => Fe::NotInVoice(e), - Self::InVoiceWithoutUser(e) => Fe::InVoiceWithoutUser(e), - Self::QueueEmpty(e) => Fe::QueueEmpty(e), + Self::UserNotAccessManager(_) => Fe::UserNotAccessManager, + Self::Sqlx(_) => Fe::Sqlx, + Self::TaskJoin(_) => Fe::TaskJoin, + Self::EmbedValidation(_) => Fe::EmbedValidation, + Self::NotInVoice(_) => Fe::NotInVoice, + Self::QueueEmpty(_) => Fe::QueueEmpty, + Self::UserNotDj(_) => Fe::UserNotDj, + Self::TwilightHttp(_) => Fe::TwilightHttp, + Self::Lavalink(_) => Fe::Lavalink, + Self::NoPlayer(_) => Fe::NoPlayer, + Self::NotInGuild(_) => Fe::NotInGuild, + Self::Cache(_) => Fe::Cache, + Self::NotPlaying(_) => Fe::NotPlaying, + Self::Paused(_) => Fe::Paused, + Self::RequireUnsuppressed(e) => Fe::from_require_unsuppressed_error(e), Self::PositionOutOfRange(e) => Fe::PositionOutOfRange(e), - Self::UserNotDj(e) => Fe::UserNotDj(e), - Self::TwilightHttp(e) => Fe::TwilightHttp(e), - Self::Lavalink(e) => Fe::Lavalink(e), - Self::NoPlayer(e) => Fe::NoPlayer(e), - Self::NotInGuild(e) => Fe::NotInGuild(e), - Self::Cache(e) => Fe::Cache(e), - Self::CheckNotSuppressed(e) => Fe::from_check_not_suppressed_error(e), + Self::InVoiceWithoutUser(e) => Fe::InVoiceWithoutUser(e), Self::CheckUsersTrack(e) => Fe::from_users_track_error(e), - Self::InVoiceWithSomeoneElse(e) => Fe::from_in_voice_with_someone_else_error(e), + Self::RequireInVoiceWithSomeoneElse(e) => { + Fe::from_require_in_voice_with_someone_else_error(e) + } Self::CheckRun(e) => Fe::from_run(e), Self::Respond(e) => Fe::from_respond(e), Self::Followup(e) => Fe::from_followup(e), Self::PromptForConfirmation(e) => Fe::from_prompt_for_confirmation(e), Self::Join(e) => Fe::from_join_residual(e), Self::Leave(e) => Fe::from_leave_residual(e), - Self::WithAdvanceLockAndStopped(e) => Fe::from_with_advance_lock_and_stopped(e), Self::Play(e) => Fe::from_play(e), Self::DeserializeBodyFromHttp(e) => Fe::from_deserialize_body_from_http_error(e), Self::RemoveTracks(e) => Fe::from_remove_tracks(e), diff --git a/lyra/src/error/command/check.rs b/lyra/src/error/command/check.rs index d558e29..e0e973d 100644 --- a/lyra/src/error/command/check.rs +++ b/lyra/src/error/command/check.rs @@ -2,9 +2,13 @@ use thiserror::Error; use crate::error::{ self, Cache as CacheError, InVoiceWithoutUser as InVoiceWithoutUserError, NotInVoice, - NotUsersTrack as NotUsersTrackError, QueueNotSeekable as QueueNotSeekableError, + NotUsersTrack as NotUsersTrackError, PrettyErrorDisplay, PrettyInVoiceWithSomeoneElseDisplayer, + PrettyNotUsersTrackDisplayer, PrettyQueueNotSeekableDisplayer, + QueueNotSeekable as QueueNotSeekableError, }; +use super::require::UnsuppressedError; + #[derive(Error, Debug)] #[error(transparent)] pub enum AccessCalculatorBuildError { @@ -19,13 +23,6 @@ pub enum UserAllowedError { UserNotAllowed(#[from] error::UserNotAllowed), } -#[derive(Error, Debug)] -#[error(transparent)] -pub enum InVoiceWithSomeoneElseError { - Cache(#[from] CacheError), - InVoiceWithoutSomeoneElse(#[from] error::InVoiceWithoutSomeoneElse), -} - #[derive(Error, Debug)] #[error(transparent)] pub enum UserOnlyInError { @@ -48,13 +45,6 @@ pub enum InVoiceWithUserOnlyError { UserOnlyIn(#[from] UserOnlyInError), } -#[derive(Error, Debug)] -#[error(transparent)] -pub enum NotSuppressedError { - Cache(#[from] CacheError), - Suppressed(#[from] error::Suppressed), -} - #[derive(Error, Debug)] #[error(transparent)] pub enum CurrentlyPlayingUsersTrackError { @@ -93,12 +83,38 @@ impl From for PollResolvableError { } } -impl crate::error::EPrint for PollResolvableError { - fn eprint(&self) -> String { +impl<'a> PrettyErrorDisplay<'a> for PollResolvableError { + type Displayer = PrettyPollResolvableErrorDisplayer<'a>; + + fn pretty_display(&'a self) -> Self::Displayer { match self { - Self::InVoiceWithSomeoneElse(e) => e.eprint(), - Self::QueueNotSeekable(e) => e.eprint(), - Self::NotUsersTrack(e) => e.eprint(), + Self::InVoiceWithSomeoneElse(e) => { + PrettyPollResolvableErrorDisplayer::InVoiceWithSomeoneElse( + PrettyInVoiceWithSomeoneElseDisplayer(e), + ) + } + Self::QueueNotSeekable(_) => PrettyPollResolvableErrorDisplayer::QueueNotSeekable( + PrettyQueueNotSeekableDisplayer, + ), + Self::NotUsersTrack(e) => { + PrettyPollResolvableErrorDisplayer::NotUsersTrack(PrettyNotUsersTrackDisplayer(e)) + } + } + } +} + +pub enum PrettyPollResolvableErrorDisplayer<'a> { + InVoiceWithSomeoneElse(PrettyInVoiceWithSomeoneElseDisplayer<'a>), + NotUsersTrack(PrettyNotUsersTrackDisplayer<'a>), + QueueNotSeekable(PrettyQueueNotSeekableDisplayer), +} + +impl<'a> std::fmt::Display for PrettyPollResolvableErrorDisplayer<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + PrettyPollResolvableErrorDisplayer::InVoiceWithSomeoneElse(e) => e.fmt(f), + PrettyPollResolvableErrorDisplayer::NotUsersTrack(e) => e.fmt(f), + PrettyPollResolvableErrorDisplayer::QueueNotSeekable(e) => e.fmt(f), } } } @@ -122,19 +138,36 @@ pub enum AlternateVoteResponse { #[error("poll was voided")] pub struct PollVoidedError(pub crate::command::poll::VoidingEvent); -impl crate::error::EPrint for PollVoidedError { - fn eprint(&self) -> String { +impl<'a> PrettyErrorDisplay<'a> for PollVoidedError { + type Displayer = PrettyVoidedErrorDisplayer; + + fn pretty_display(&'a self) -> Self::Displayer { match self.0 { crate::command::poll::VoidingEvent::QueueClear => { - String::from("the queue had been cleared") + PrettyVoidedErrorDisplayer::QueueClear } crate::command::poll::VoidingEvent::QueueRepeat => { - String::from("the queue had been set to repeat in another manner") + PrettyVoidedErrorDisplayer::QueueRepeat } } } } +pub enum PrettyVoidedErrorDisplayer { + QueueClear, + QueueRepeat, +} + +impl std::fmt::Display for PrettyVoidedErrorDisplayer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let data = match self { + Self::QueueClear => "the queue had been cleared", + Self::QueueRepeat => "the queue had been set to repeat in another manner", + }; + f.write_str(data) + } +} + #[derive(Error, Debug)] #[error(transparent)] pub enum HandlePollError { @@ -181,7 +214,7 @@ pub enum HandleInVoiceWithSomeoneElseError { pub enum RunError { NotInVoice(#[from] NotInVoice), QueueEmpty(#[from] error::QueueEmpty), - NotSuppressed(#[from] NotSuppressedError), + NotSuppressed(#[from] UnsuppressedError), NotPlaying(#[from] error::NotPlaying), InVoiceWithoutUser(#[from] InVoiceWithoutUserError), HandleInVoiceWithSomeoneElse(#[from] HandleInVoiceWithSomeoneElseError), diff --git a/lyra/src/error/command/declare.rs b/lyra/src/error/command/declare.rs index 35703c1..8030364 100644 --- a/lyra/src/error/command/declare.rs +++ b/lyra/src/error/command/declare.rs @@ -14,13 +14,13 @@ pub enum CommandExecuteError { UnknownCommand(PartialCommandData), } -pub enum FlattenedUntilUserNotAllowedCommandExecuteError<'a> { - Sqlx(&'a sqlx::Error), - TaskJoin(&'a tokio::task::JoinError), - UserNotAllowed(&'a crate::error::UserNotAllowed), - InteractionParse(&'a twilight_interactions::error::ParseError), - UnknownCommand(&'a PartialCommandData), - Command(&'a super::Error), +pub enum FlattenedUntilUserNotAllowedCommandExecuteError { + Sqlx, + TaskJoin, + UserNotAllowed, + InteractionParse, + UnknownCommand, + Command, } pub use FlattenedUntilUserNotAllowedCommandExecuteError as Fuunacee; @@ -30,14 +30,14 @@ impl CommandExecuteError { match self { Self::CheckUserAllowed(e) => match e { super::check::UserAllowedError::AccessCalculatorBuild(e) => match e { - super::check::AccessCalculatorBuildError::Sqlx(e) => Fuunacee::Sqlx(e), - super::check::AccessCalculatorBuildError::TaskJoin(e) => Fuunacee::TaskJoin(e), + super::check::AccessCalculatorBuildError::Sqlx(_) => Fuunacee::Sqlx, + super::check::AccessCalculatorBuildError::TaskJoin(_) => Fuunacee::TaskJoin, }, - super::check::UserAllowedError::UserNotAllowed(e) => Fuunacee::UserNotAllowed(e), + super::check::UserAllowedError::UserNotAllowed(_) => Fuunacee::UserNotAllowed, }, - Self::InteractionParse(e) => Fuunacee::InteractionParse(e), - Self::UnknownCommand(c) => Fuunacee::UnknownCommand(c), - Self::Command(e) => Fuunacee::Command(e), + Self::InteractionParse(_) => Fuunacee::InteractionParse, + Self::UnknownCommand(_) => Fuunacee::UnknownCommand, + Self::Command(_) => Fuunacee::Command, } } } diff --git a/lyra/src/error/command/require.rs b/lyra/src/error/command/require.rs new file mode 100644 index 0000000..2456cbd --- /dev/null +++ b/lyra/src/error/command/require.rs @@ -0,0 +1,15 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +#[error(transparent)] +pub enum UnsuppressedError { + Cache(#[from] crate::error::Cache), + Suppressed(#[from] crate::error::Suppressed), +} + +#[derive(Error, Debug)] +#[error(transparent)] +pub enum InVoiceWithSomeoneElseError { + Cache(#[from] crate::error::Cache), + InVoiceWithoutSomeoneElse(#[from] crate::error::InVoiceWithoutSomeoneElse), +} diff --git a/lyra/src/error/command/util.rs b/lyra/src/error/command/util.rs index cc7a8e9..aad2728 100644 --- a/lyra/src/error/command/util.rs +++ b/lyra/src/error/command/util.rs @@ -160,7 +160,7 @@ impl crate::error::component::connection::join::AutoJoinError { #[error(transparent)] pub enum AutoJoinOrCheckInVoiceWithUserError { InVoiceWithoutUser(#[from] crate::error::InVoiceWithoutUser), - CheckNotSuppressed(#[from] super::check::NotSuppressedError), + RequireUnsuppressed(#[from] super::require::UnsuppressedError), AutoJoinAttempt(#[from] AutoJoinAttemptError), HandleSuppressedAutoJoin(#[from] HandleSuppressedAutoJoinError), } diff --git a/lyra/src/error/component.rs b/lyra/src/error/component.rs index a3ca6ed..6556d26 100644 --- a/lyra/src/error/component.rs +++ b/lyra/src/error/component.rs @@ -1,2 +1,3 @@ pub mod connection; +pub mod playback; pub mod queue; diff --git a/lyra/src/error/component/playback.rs b/lyra/src/error/component/playback.rs new file mode 100644 index 0000000..a0a3ea1 --- /dev/null +++ b/lyra/src/error/component/playback.rs @@ -0,0 +1,8 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +#[error("handling `VoiceStateUpdate` failed: {:?}", .0)] +pub enum HandleVoiceStateUpdateError { + Lavalink(#[from] lavalink_rs::error::LavalinkError), + TwilightHttp(#[from] twilight_http::Error), +} diff --git a/lyra/src/error/component/queue.rs b/lyra/src/error/component/queue.rs index f524a07..12d5898 100644 --- a/lyra/src/error/component/queue.rs +++ b/lyra/src/error/component/queue.rs @@ -19,7 +19,7 @@ pub mod play { #[derive(thiserror::Error, Debug)] #[error("playing failed: {:?}", .0)] pub enum Error { - CheckNotSuppressed(#[from] crate::error::command::check::NotSuppressedError), + RequireUnsuppressed(#[from] crate::error::command::require::UnsuppressedError), Respond(#[from] crate::error::command::RespondError), Followup(#[from] crate::error::command::FollowupError), AutoJoinOrCheckInVoiceWithUser( @@ -29,22 +29,12 @@ pub mod play { } } -pub mod remove { - use thiserror::Error; - - #[derive(Error, Debug)] - #[error(transparent)] - pub enum WithAdvanceLockAndStoppedError { - Lavalink(#[from] lavalink_rs::error::LavalinkError), - } -} - use thiserror::Error; #[derive(Error, Debug)] pub enum RemoveTracksError { #[error(transparent)] - TryWithAdvanceLock(#[from] remove::WithAdvanceLockAndStoppedError), + Lavalink(#[from] lavalink_rs::error::LavalinkError), #[error(transparent)] Respond(#[from] crate::error::command::RespondError), #[error(transparent)] diff --git a/lyra/src/error/gateway.rs b/lyra/src/error/gateway.rs index 242c455..33f43f2 100644 --- a/lyra/src/error/gateway.rs +++ b/lyra/src/error/gateway.rs @@ -17,7 +17,11 @@ pub enum ProcessError { #[error(transparent)] Cache(#[from] super::Cache), #[error(transparent)] - HandleVoiceStateUpdate(#[from] super::component::connection::HandleVoiceStateUpdateError), + ConnectionHandleVoiceStateUpdate( + #[from] super::component::connection::HandleVoiceStateUpdateError, + ), + #[error(transparent)] + PlaybackHandleVoiceStateUpdate(#[from] super::component::playback::HandleVoiceStateUpdateError), #[error(transparent)] Respond(#[from] super::command::RespondError), #[error("error executing command `/{}`: {:?}", .name, .source)] diff --git a/lyra/src/gateway/guild.rs b/lyra/src/gateway/guild.rs index c646bbc..6828df1 100644 --- a/lyra/src/gateway/guild.rs +++ b/lyra/src/gateway/guild.rs @@ -1,3 +1,4 @@ +use lyra_ext::num::u64_to_i64_truncating; use twilight_gateway::ShardId; use twilight_model::gateway::payload::incoming::{GuildCreate, GuildDelete}; @@ -30,7 +31,7 @@ impl CreateContext<'_> { NOT EXISTS ( SELECT 1 FROM guild_configs WHERE id = $1 );", - self.inner.id.get() as i64 + u64_to_i64_truncating(self.inner.id.get()) ) .execute(self.bot.db()) .await?; diff --git a/lyra/src/gateway/interaction.rs b/lyra/src/gateway/interaction.rs index 2b2a48c..88d919e 100644 --- a/lyra/src/gateway/interaction.rs +++ b/lyra/src/gateway/interaction.rs @@ -24,7 +24,7 @@ use crate::{ BotState, InteractionClient, InteractionInterface, OwnedBotState, UnitFollowupResult, UnitRespondResult, }, - r#const::exit_code::{DUBIOUS, FORBIDDEN, WARNING}, + r#const::exit_code::{DUBIOUS, PROHIBITED, WARNING}, }, error::{ command::{ @@ -36,10 +36,11 @@ use crate::{ Error as CommandError, Fe, RespondError, }, gateway::{ProcessError, ProcessResult}, - AutoJoinAttemptFailed as AutoJoinAttemptFailedError, EPrint, - PositionOutOfRange as PositionOutOfRangeError, Suppressed as SuppressedError, + AutoJoinAttemptFailed as AutoJoinAttemptFailedError, + PositionOutOfRange as PositionOutOfRangeError, PrettyErrorDisplay, + Suppressed as SuppressedError, }, - lavalink::LavalinkAware, + LavalinkAware, }; pub(super) struct Context { @@ -140,10 +141,10 @@ impl Context { }; match source.flatten_until_user_not_allowed_as() { - Fuunacee::UserNotAllowed(_) => { + Fuunacee::UserNotAllowed => { nope!("You are not allowed to use commands in this context.", i); } - Fuunacee::Command(_) => { + Fuunacee::Command => { let CommandExecuteError::Command(error) = source else { // SAFETY: `source.flatten_until_user_not_allowed_as()` is `Fuunacee::Command(_)`, // so the unflattened source error must be `CommandExecuteError::Command(_)` @@ -216,21 +217,21 @@ async fn match_error( i: InteractionInterface<'_>, ) -> Result<(), ProcessError> { match error.flatten_as() { - Fe::Cache(_) => { + Fe::Cache => { tracing::warn!("cache error: {:#?}", error); crit!("Something isn't working at the moment, try again later.", i); } - Fe::UserNotDj(_) => { + Fe::UserNotDj => { nope!("You need to be a ***DJ*** to do that.", i); } - Fe::UserNotAccessManager(_) => { + Fe::UserNotAccessManager => { nope!("You need to be an ***Access Manager*** to do that.", i); } // Fe::UserNotPlaylistManager(_) => { // nope!("You need to be a ***Playlist Manager*** to do that.", i); // } - Fe::NotInVoice(_) => { + Fe::NotInVoice => { let join = InteractionClient::mention_command::(); let play = InteractionClient::mention_command::(); caut!( @@ -251,7 +252,7 @@ async fn match_error( ); } Fe::InVoiceWithSomeoneElse(e) => { - nope!(e.eprint(), i); + nope!(e.pretty_display(), i); } Fe::InVoiceWithoutSomeoneElse(e) => { bad!(format!("Not enough people are in {}.", e.0.mention()), i); @@ -259,31 +260,38 @@ async fn match_error( Fe::Suppressed(e) => Ok(match_suppressed(e, i).await?), Fe::AutoJoinSuppressed(e) => Ok(match_autojoin_suppressed(e, i).await?), Fe::AutoJoinAttemptFailed(e) => Ok(match_autojoin_attempt_failed(e, i).await?), - Fe::Stopped(_) => todo!(), - Fe::NotPlaying(_) => todo!(), - Fe::Paused(_) => todo!(), + Fe::Stopped => todo!(), + Fe::NotPlaying => { + bad!("Currently not playing anything.", i); + } + Fe::Paused => { + bad!("Currently paused.", i); + } Fe::QueueNotSeekable(e) => { - nope!(e.eprint(), i); + nope!(e.pretty_display(), i); } - Fe::QueueEmpty(_) => { + Fe::QueueEmpty => { bad!("The queue is currently empty.", i); } Fe::PositionOutOfRange(e) => Ok(match_position_out_of_range(e, i).await?), Fe::NotUsersTrack(e) => { - nope!(e.eprint(), i); + nope!(e.pretty_display(), i); } Fe::AnotherPollOngoing(e) => Ok(match_another_poll_ongoing(e, i).await?), Fe::PollLoss(e) => Ok(match_poll_loss(e, i).await?), Fe::PollVoided(e) => { out_upd!( - format!("{WARNING} This poll has been voided as: {}.", e.eprint()), + format!( + "{WARNING} This poll has been voided as: {}.", + e.pretty_display() + ), i ); } - Fe::ConfirmationTimedOut(_) => { + Fe::ConfirmationTimedOut => { sus_fol!("Confirmation timed out.", i); } - Fe::NoPlayer(_) => { + Fe::NoPlayer => { let play = InteractionClient::mention_command::(); caut!(format!("Not yet played anything. Use {} first.", play), i); } @@ -427,5 +435,8 @@ async fn match_poll_loss(error: &PollLossError, i: InteractionInterface<'_>) -> PollLossErrorKind::SupersededLossViaDj => "The poll was superseded to lose by a DJ: ", }; - out_upd!(format!("{FORBIDDEN} {source_txt}{}", source.eprint()), i); + out_upd!( + format!("{PROHIBITED} {source_txt}{}", source.pretty_display()), + i + ); } diff --git a/lyra/src/gateway/model.rs b/lyra/src/gateway/model.rs index af64cd7..b7e443b 100644 --- a/lyra/src/gateway/model.rs +++ b/lyra/src/gateway/model.rs @@ -29,16 +29,16 @@ pub struct LastCachedStates { impl LastCachedStates { pub fn new(cache: &InMemoryCache, event: &Event) -> Self { let voice_state = match event { - Event::VoiceStateUpdate(event) => cache - .voice_state( - event.user_id, - // SAFETY: this bot cannot join DM voice calls, - // meaning all voice states will be from a guild voice channel, - // so `e.guild_id` is present - unsafe { event.guild_id.unwrap_unchecked() }, - ) - .as_deref() - .cloned(), + Event::VoiceStateUpdate(event) => { + // SAFETY: this bot cannot join DM voice calls, + // meaning all voice states will be from a guild voice channel, + // so `e.guild_id` is present + let guild_id = unsafe { event.guild_id.unwrap_unchecked() }; + cache + .voice_state(event.user_id, guild_id) + .as_deref() + .cloned() + } _ => None, }; diff --git a/lyra/src/gateway/shard.rs b/lyra/src/gateway/shard.rs index f608cdb..0d35c7b 100644 --- a/lyra/src/gateway/shard.rs +++ b/lyra/src/gateway/shard.rs @@ -1,3 +1,4 @@ +use lyra_ext::num::u64_to_i64_truncating; use tokio::task::JoinSet; use twilight_gateway::ShardId; use twilight_model::gateway::payload::incoming::Ready; @@ -41,7 +42,7 @@ impl Process for ReadyContext<'_> { self.inner.guilds.iter().for_each(|g| { let db = self.bot.db().clone(); - let guild_id = g.id.get() as i64; + let guild_id = u64_to_i64_truncating(g.id.get()); set.spawn(async move { sqlx::query!( "INSERT INTO guild_configs diff --git a/lyra/src/gateway/voice.rs b/lyra/src/gateway/voice.rs index b9f00a5..99c8ef8 100644 --- a/lyra/src/gateway/voice.rs +++ b/lyra/src/gateway/voice.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use futures::TryFutureExt; use twilight_model::gateway::payload::incoming::VoiceStateUpdate; use twilight_cache_inmemory::{model::CachedVoiceState, InMemoryCache}; @@ -8,12 +9,13 @@ use twilight_http::Client; use twilight_model::id::marker::GuildMarker; use twilight_model::id::Id; -use crate::component::{connection, tuning}; -use crate::core::model::{BotState, BotStateAware, CacheAware, HttpAware, OwnedBotStateAware}; -use crate::error::gateway::ProcessResult; use crate::{ + component::{connection, playback, tuning}, + core::model::{BotState, BotStateAware, CacheAware, HttpAware, OwnedBotStateAware}, + error::gateway::{ProcessError, ProcessResult}, gateway::{GuildIdAware, SenderAware}, - lavalink::{Lavalink, LavalinkAware}, + lavalink::Lavalink, + LavalinkAndGuildIdAware, LavalinkAware, }; use super::{LastCachedStates, Process}; @@ -92,11 +94,21 @@ impl GuildIdAware for Context { } } +impl LavalinkAndGuildIdAware for Context {} + impl Process for Context { async fn process(self) -> ProcessResult { - connection::handle_voice_state_update(&self).await?; - tuning::handle_voice_state_update(&self).await?; - + let connection_changed = match self.get_connection() { + Some(connection) => connection.changed().await, + None => false, + }; + + tokio::try_join![ + connection::handle_voice_state_update(&self, connection_changed) + .map_err(ProcessError::from), + playback::handle_voice_state_update(&self, connection_changed).map_err(Into::into), + tuning::handle_voice_state_update(&self).map_err(Into::into), + ]?; Ok(()) } } diff --git a/lyra/src/lavalink.rs b/lyra/src/lavalink.rs index 4a1c06a..dbf0ed8 100644 --- a/lyra/src/lavalink.rs +++ b/lyra/src/lavalink.rs @@ -6,10 +6,10 @@ mod track; pub use self::{ model::{ - wait_for_with, ClientAware as LavalinkAware, Connection, CorrectPlaylistInfo, - CorrectTrackInfo, DelegateMethods, Event, EventRecvResult, IndexerType, Lavalink, Pitch, - PlayerAware, PlayerDataRwLockArc, Queue, QueueItem, RepeatMode, UnwrappedPlayerData, - UnwrappedPlayerInfoUri, + wait_for_with, ClientAndGuildIdAware, ClientAware, Connection, CorrectPlaylistInfo, + CorrectTrackInfo, DelegateMethods, Event, EventRecvResult, IndexerType, Lavalink, + OwnedPlayerData, Pitch, PlayerDataRead, PlayerDataWrite, Queue, QueueItem, RepeatMode, + UnwrappedPlayerData, UnwrappedPlayerInfoUri, }, process::handlers, }; diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index 428b0c9..0babf09 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -4,7 +4,7 @@ mod pitch; mod queue; mod queue_indexer; -use std::{num::NonZeroU16, sync::Arc}; +use std::{num::NonZeroU16, sync::Arc, time::Duration}; use lavalink_rs::{ client::LavalinkClient, @@ -12,11 +12,9 @@ use lavalink_rs::{ model::{player::ConnectionInfo, track::TrackInfo}, player_context::PlayerContext, }; -use tokio::sync::RwLock; -use twilight_model::id::{ - marker::{GuildMarker, MessageMarker}, - Id, -}; +use lyra_ext::time::track_timestamp::TrackTimestamp; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use twilight_model::id::{marker::GuildMarker, Id}; use crate::{ command::require::{InVoice, PartialInVoice}, @@ -34,33 +32,44 @@ pub use self::{ queue_indexer::IndexerType, }; -pub type PlayerDataRwLockArc = Arc>; +type PlayerData = RwLock; +pub type OwnedPlayerData = Arc; +pub type PlayerDataRead<'a> = RwLockReadGuard<'a, RawPlayerData>; +pub type PlayerDataWrite<'a> = RwLockWriteGuard<'a, RawPlayerData>; pub trait ClientAware { fn lavalink(&self) -> &Lavalink; } -pub trait PlayerAware: ClientAware + GuildIdAware { +pub trait ClientAndGuildIdAware: ClientAware + GuildIdAware { fn get_player(&self) -> Option { self.lavalink().get_player_context(self.guild_id()) } + + fn get_connection(&self) -> Option { + self.lavalink().get_connection(self.guild_id()) + } + + fn get_connection_mut(&self) -> Option { + self.lavalink().get_connection_mut(self.guild_id()) + } } -pub struct PlayerData { +pub struct RawPlayerData { queue: Queue, volume: NonZeroU16, pitch: Pitch, - now_playing_message_id: Option>, + track_timestamp: TrackTimestamp, } -impl PlayerData { - pub const fn new() -> Self { +impl RawPlayerData { + pub fn new() -> Self { Self { // SAFETY: `100` is non-zero volume: unsafe { NonZeroU16::new_unchecked(100) }, pitch: Pitch::new(), queue: Queue::new(), - now_playing_message_id: None, + track_timestamp: TrackTimestamp::new(), } } @@ -68,6 +77,12 @@ impl PlayerData { &self.queue } + #[inline] + pub fn reset_track_timestamp(&mut self) { + self.track_timestamp.reset(); + } + + #[inline] pub fn queue_mut(&mut self) -> &mut Queue { &mut self.queue } @@ -76,13 +91,39 @@ impl PlayerData { self.volume } + #[inline] pub fn set_volume(&mut self, volume: NonZeroU16) { self.volume = volume; } + #[inline] pub fn pitch_mut(&mut self) -> &mut Pitch { &mut self.pitch } + + pub const fn paused(&self) -> bool { + self.track_timestamp.paused() + } + + #[inline] + pub fn timestamp(&self) -> Duration { + self.track_timestamp.get() + } + + #[inline] + pub fn set_pause(&mut self, state: bool) { + self.track_timestamp.set_pause(state); + } + + #[inline] + pub fn seek_to(&mut self, timestamp: Duration) { + self.track_timestamp.seek_to(timestamp); + } + + #[inline] + pub fn set_speed(&mut self, multiplier: f64) { + self.track_timestamp.set_speed(multiplier); + } } pub struct Lavalink { @@ -225,7 +266,7 @@ pub trait DelegateMethods { .await?; tracing::trace!("getting lavalink connection info took {:?}", now.elapsed()); - let data = Arc::new(RwLock::new(PlayerData::new())); + let data = Arc::new(RwLock::new(RawPlayerData::new())); let player = self .create_player_context_with_data(guild_id, info, data) .await?; @@ -237,7 +278,7 @@ pub trait DelegateMethods { fn get_player_data( &self, guild_id: impl Into + Send, - ) -> Option { + ) -> Option { self.get_player_context(guild_id) .map(|c| c.data_unwrapped()) } @@ -260,20 +301,16 @@ impl Lavalink { self.connections.get(&guild_id) } - pub fn connection_from(&self, from: &impl GetConnection) -> ConnectionRef { + pub fn connection_from(&self, cx: &impl GetConnection) -> ConnectionRef { // SAFETY: because the caller has an instance of `InVoice`, // this proves that there is a voice connection currently. - unsafe { self.connections.get(&from.guild_id()).unwrap_unchecked() } + unsafe { self.connections.get(&cx.guild_id()).unwrap_unchecked() } } - pub fn connection_mut_from(&self, from: &impl GetConnection) -> ConnectionRefMut { + pub fn connection_mut_from(&self, cx: &impl GetConnection) -> ConnectionRefMut { // SAFETY: because the caller has an instance of `InVoice`, // this proves that there is a voice connection currently. - unsafe { - self.connections - .get_mut(&from.guild_id()) - .unwrap_unchecked() - } + unsafe { self.connections.get_mut(&cx.guild_id()).unwrap_unchecked() } } pub fn get_connection_mut(&self, guild_id: Id) -> Option { @@ -338,11 +375,11 @@ impl DelegateMethods for LavalinkClient { } pub trait UnwrappedPlayerData { - fn data_unwrapped(&self) -> PlayerDataRwLockArc; + fn data_unwrapped(&self) -> OwnedPlayerData; } impl UnwrappedPlayerData for PlayerContext { - fn data_unwrapped(&self) -> PlayerDataRwLockArc { + fn data_unwrapped(&self) -> OwnedPlayerData { // SAFETY: Player data exists of type `Arc>` unsafe { self.data().unwrap_unchecked() } } diff --git a/lyra/src/lavalink/model/connection.rs b/lyra/src/lavalink/model/connection.rs index f95bc43..ab2c6d4 100644 --- a/lyra/src/lavalink/model/connection.rs +++ b/lyra/src/lavalink/model/connection.rs @@ -34,12 +34,10 @@ impl Connection { } pub async fn changed(&self) -> bool { - tokio::time::timeout( - *r#const::connection::connection_changed_timeout(), - self.change.notified(), - ) - .await - .is_ok() + tracing::trace!("waiting for connection change notification"); + let duration = *r#const::connection::changed_timeout(); + let future = self.change.notified(); + tokio::time::timeout(duration, future).await.is_ok() } pub const fn poll(&self) -> Option<&Poll> { @@ -95,7 +93,7 @@ pub enum Event { pub type EventRecvResult = Result; -#[allow(clippy::needless_pass_by_ref_mut)] +#[allow(clippy::needless_pass_by_ref_mut)] // false positive pub async fn wait_for_with( rx: &mut broadcast::Receiver, predicate: impl Fn(&Event) -> bool + Send + Sync, diff --git a/lyra/src/lavalink/model/pitch.rs b/lyra/src/lavalink/model/pitch.rs index cb1a38d..223855e 100644 --- a/lyra/src/lavalink/model/pitch.rs +++ b/lyra/src/lavalink/model/pitch.rs @@ -5,7 +5,7 @@ const TWELFTH_ROOT_OF_TWO: f64 = 1.059_463_094_359_295_3; #[derive(Clone)] pub struct Pitch { multiplier: f64, - half_tone_shifts: i32, + half_tone_shifts: i64, } impl Pitch { @@ -30,7 +30,9 @@ impl Pitch { #[inline] pub fn get(&self) -> f64 { - self.multiplier * TWELFTH_ROOT_OF_TWO.powi(self.half_tone_shifts) + #[allow(clippy::cast_possible_truncation)] + let half_ton_shifts_i32 = self.half_tone_shifts as i32; + self.multiplier * TWELFTH_ROOT_OF_TWO.powi(half_ton_shifts_i32) } #[inline] @@ -42,7 +44,7 @@ impl Pitch { } pub fn shift(&mut self, half_tones: NonZeroI64) { - self.half_tone_shifts += half_tones.get() as i32; + self.half_tone_shifts += half_tones.get(); } #[inline] diff --git a/lyra/src/lavalink/model/queue.rs b/lyra/src/lavalink/model/queue.rs index c9e728c..bfc4e41 100644 --- a/lyra/src/lavalink/model/queue.rs +++ b/lyra/src/lavalink/model/queue.rs @@ -1,17 +1,11 @@ -use std::{ - collections::VecDeque, - num::NonZeroUsize, - sync::atomic::{AtomicBool, Ordering}, -}; - -use futures::Future; -use lavalink_rs::{model::track::TrackData, player_context::PlayerContext}; +use std::{collections::VecDeque, num::NonZeroUsize}; + +use lavalink_rs::model::track::TrackData; use rayon::iter::{IntoParallelIterator, ParallelExtend, ParallelIterator}; +use tokio::sync::Notify; use twilight_model::id::{marker::UserMarker, Id}; -use crate::error::component::queue::remove::WithAdvanceLockAndStoppedError; - -use super::queue_indexer::{IndexerType, QueueIndexer}; +use super::queue_indexer::{Indexer, IndexerType}; #[derive(Hash, Copy, Clone)] pub enum RepeatMode { @@ -31,7 +25,7 @@ impl RepeatMode { pub const fn emoji(&self) -> &str { match self { - Self::Off => "**` ๐Ÿกฒ `**", + Self::Off => "โžก๏ธ", Self::All => "๐Ÿ”", Self::Track => "๐Ÿ”‚", } @@ -66,11 +60,11 @@ impl Item { self.requester } - pub const fn track(&self) -> &TrackData { + pub const fn data(&self) -> &TrackData { &self.track } - pub fn into_track(self) -> TrackData { + pub fn into_data(self) -> TrackData { self.track } } @@ -78,55 +72,45 @@ impl Item { pub struct Queue { inner: VecDeque, index: usize, - indexer: QueueIndexer, + indexer: Indexer, repeat_mode: RepeatMode, - advance_lock: AtomicBool, - current_track_started: u64, + advance_lock: Notify, } impl Queue { pub(super) const fn new() -> Self { Self { inner: VecDeque::new(), - indexer: QueueIndexer::Standard, + indexer: Indexer::Standard, index: 0, repeat_mode: RepeatMode::Off, - advance_lock: AtomicBool::new(false), - current_track_started: 0, + advance_lock: Notify::const_new(), } } - pub fn position(&self) -> NonZeroUsize { - let d = usize::from(self.current().is_some() || self.index == 0); + fn position_from(&self, current: Option<&Item>) -> NonZeroUsize { + let d = usize::from(current.is_some() || self.index == 0); // SAFETY: `self.index + d` is non-zero unsafe { NonZeroUsize::new_unchecked(self.index + d) } } - pub const fn index(&self) -> &usize { - &self.index - } - - pub fn index_mut(&mut self) -> &mut usize { - &mut self.index - } - - pub fn advance_locked(&self) -> bool { - self.advance_lock.load(Ordering::SeqCst) + pub fn position(&self) -> NonZeroUsize { + self.position_from(self.current()) } - pub fn advance_lock(&self) { - self.advance_lock.store(true, Ordering::SeqCst); + pub const fn index(&self) -> usize { + self.index } - pub fn advance_unlock(&self) { - self.advance_lock.store(false, Ordering::Relaxed); + pub fn index_mut(&mut self) -> &mut usize { + &mut self.index } pub fn current_index(&self) -> Option { match self.indexer { - QueueIndexer::Standard => Some(self.index), - QueueIndexer::Fair(ref indexer) => indexer.current(self.index), - QueueIndexer::Shuffled(ref indexer) => indexer.current(self.index), + Indexer::Standard => Some(self.index), + Indexer::Fair(ref indexer) => indexer.current(self.index), + Indexer::Shuffled(ref indexer) => indexer.current(self.index), } } @@ -134,16 +118,17 @@ impl Queue { self.inner.get(self.current_index()?) } - pub fn current_and_index(&self) -> Option<(&Item, usize)> { - self.current_index() - .and_then(|i| Some((self.inner.get(i)?, i))) + pub fn current_and_position(&self) -> (Option<&Item>, NonZeroUsize) { + let current = self.current(); + let position = self.position_from(current); + (current, position) } pub fn enqueue(&mut self, tracks: Vec, requester: Id) { match self.indexer { - QueueIndexer::Fair(ref mut indexer) => indexer.enqueue(tracks.len(), requester), - QueueIndexer::Shuffled(ref mut indexer) => indexer.enqueue(tracks.len(), self.index), - QueueIndexer::Standard => {} + Indexer::Fair(ref mut indexer) => indexer.enqueue(tracks.len(), requester), + Indexer::Shuffled(ref mut indexer) => indexer.enqueue(tracks.len(), self.index), + Indexer::Standard => {} } let queues = tracks.into_par_iter().map(|t| Item::new(t, requester)); self.inner.par_extend(queues); @@ -190,7 +175,7 @@ impl Queue { self.repeat_mode = mode; } - pub fn adjust_repeat_mode(&mut self) { + pub fn downgrade_repeat_mode(&mut self) { if let RepeatMode::All | RepeatMode::Track = self.repeat_mode { self.repeat_mode = if self.len() > 1 { RepeatMode::All @@ -207,16 +192,16 @@ impl Queue { pub fn set_indexer_type(&mut self, kind: IndexerType) { match (self.indexer.kind(), kind) { (IndexerType::Fair | IndexerType::Shuffled, IndexerType::Standard) => { - self.indexer = QueueIndexer::Standard; + self.indexer = Indexer::Standard; } (IndexerType::Standard | IndexerType::Shuffled, IndexerType::Fair) => { - self.indexer = QueueIndexer::Fair(super::queue_indexer::FairIndexer::new( + self.indexer = Indexer::Fair(super::queue_indexer::FairIndexer::new( self.inner.iter(), self.index, )); } (IndexerType::Standard | IndexerType::Fair, IndexerType::Shuffled) => { - self.indexer = QueueIndexer::Shuffled(super::queue_indexer::ShuffledIndexer::new( + self.indexer = Indexer::Shuffled(super::queue_indexer::ShuffledIndexer::new( self.len(), self.index, )); @@ -237,30 +222,24 @@ impl Queue { } } - pub async fn stop_with_advance_lock( - &self, - player: &PlayerContext, - ) -> Result<(), WithAdvanceLockAndStoppedError> { - self.with_advance_lock_and_stopped(player, |_| async { Ok(()) }) - .await + pub fn acquire_advance_lock(&self) { + tracing::trace!("acquired queue advance lock"); + self.advance_lock.notify_one(); } - pub async fn with_advance_lock_and_stopped<'a, 'b, F>( - &self, - player: &'a PlayerContext, - f: impl FnOnce(&'b PlayerContext) -> F + Send, - ) -> Result<(), WithAdvanceLockAndStoppedError> - where - F: Future> + Send, - 'a: 'b, - { - self.advance_lock(); - - player.stop_now().await?; - f(player).await?; - Ok(()) + pub async fn not_advance_locked(&self) -> bool { + let future = self.advance_lock.notified(); + let duration = *crate::core::r#const::misc::queue_advance_locked_timeout(); + tokio::time::timeout(duration, future).await.is_err() } + pub fn iter_positions_and_items( + &self, + ) -> impl DoubleEndedIterator + Clone { + self.iter() + .enumerate() + .filter_map(|(i, t)| NonZeroUsize::new(i + 1).map(|i| (i, t))) + } #[inline] pub fn is_empty(&self) -> bool { self.inner.is_empty() @@ -294,3 +273,11 @@ impl std::ops::Index for Queue { &self.inner[index] } } + +impl std::ops::Index for Queue { + type Output = Item; + + fn index(&self, index: NonZeroUsize) -> &Self::Output { + &self.inner[index.get() - 1] + } +} diff --git a/lyra/src/lavalink/model/queue_indexer.rs b/lyra/src/lavalink/model/queue_indexer.rs index 58e90a1..1585897 100644 --- a/lyra/src/lavalink/model/queue_indexer.rs +++ b/lyra/src/lavalink/model/queue_indexer.rs @@ -11,13 +11,13 @@ pub enum IndexerType { Shuffled, } -pub(super) enum QueueIndexer { +pub(super) enum Indexer { Standard, Fair(FairIndexer), Shuffled(ShuffledIndexer), } -impl QueueIndexer { +impl Indexer { pub(super) const fn kind(&self) -> IndexerType { match self { Self::Standard => IndexerType::Standard, diff --git a/lyra/src/lavalink/track.rs b/lyra/src/lavalink/track.rs index 6e1a3a6..52a22ae 100644 --- a/lyra/src/lavalink/track.rs +++ b/lyra/src/lavalink/track.rs @@ -9,21 +9,32 @@ use crate::{ lavalink::{model::CorrectTrackInfo, UnwrappedPlayerData}, }; -// FIXME: don't debug `LavalinkClient` until `lavalink_rs` stops stack overflowing - #[tracing::instrument(err, skip_all, name = "track_start")] -async fn impl_start(event: &TrackStart) -> ProcessResult { +async fn impl_start(lavalink: LavalinkClient, _: String, event: &TrackStart) -> ProcessResult { + let guild_id = event.guild_id; tracing::debug!( "guild {} started {:?}", event.guild_id.0, event.track.info.checked_title() ); + let Some(player) = lavalink.get_player_context(guild_id) else { + tracing::warn!(?guild_id, "track started without player"); + + return Ok(()); + }; + player + .data_unwrapped() + .write() + .await + .reset_track_timestamp(); + Ok(()) } +#[allow(clippy::significant_drop_tightening)] #[tracing::instrument(err, skip_all, name = "track_end")] -async fn impl_end(lavalink: LavalinkClient, event: &TrackEnd) -> ProcessResult { +async fn impl_end(lavalink: LavalinkClient, _: String, event: &TrackEnd) -> ProcessResult { let guild_id = event.guild_id; tracing::debug!( "guild {} ended {:?}", @@ -38,51 +49,61 @@ async fn impl_end(lavalink: LavalinkClient, event: &TrackEnd) -> ProcessResult { }; let data = player.data_unwrapped(); let data_r = data.read().await; - let queue = data_r.queue(); // TODO: handle now playing message deleting - if queue.advance_locked() { - queue.advance_unlock(); - } else { + if data_r.queue().not_advance_locked().await { + tracing::trace!(?guild_id, "track ended normally"); + drop(data_r); + let mut data_w = data.write().await; let queue = data_w.queue_mut(); queue.advance(); if let Some(item) = queue.current() { - player.play_now(item.track()).await?; + player.play_now(item.data()).await?; } + } else { + tracing::trace!(?guild_id, "track ended forcefully"); } Ok(()) } #[tracing::instrument(err, skip_all, name = "track_exception")] -async fn impl_exception() -> ProcessResult { +async fn impl_exception(_: LavalinkClient, _: String, event: &TrackException) -> ProcessResult { + tracing::error!(?event, "track exception"); + Ok(()) // TODO: handle track exception } #[tracing::instrument(err, skip_all, name = "track_stuck")] -async fn impl_stuck() -> ProcessResult { +async fn impl_stuck(_: LavalinkClient, _: String, event: &TrackStuck) -> ProcessResult { + tracing::warn!(?event, "track stuck"); + Ok(()) // TODO: handle track stuck } #[hook] -pub(super) async fn start(_: LavalinkClient, _session_id: String, event: &TrackStart) { - let _ = impl_start(event).await; +pub(super) async fn start(lavalink: LavalinkClient, session_id: String, event: &TrackStart) { + let _ = impl_start(lavalink, session_id, event).await; } #[hook] -pub(super) async fn end(lavalink: LavalinkClient, _session_id: String, event: &TrackEnd) { - let _ = impl_end(lavalink, event).await; +pub(super) async fn end(lavalink: LavalinkClient, session_id: String, event: &TrackEnd) { + let _ = impl_end(lavalink, session_id, event).await; } #[hook] -pub(super) async fn exception(_: LavalinkClient, _session_id: String, _: &TrackException) { - let _ = impl_exception().await; +pub(super) async fn exception( + lavalink: LavalinkClient, + session_id: String, + event: &TrackException, +) { + let _ = impl_exception(lavalink, session_id, event).await; } #[hook] -pub(super) async fn stuck(_: LavalinkClient, _session_id: String, _: &TrackStuck) { - let _ = impl_stuck().await; +pub(super) async fn stuck(lavalink: LavalinkClient, session_id: String, event: &TrackStuck) { + let _ = impl_stuck(lavalink, session_id, event).await; } diff --git a/lyra/src/main.rs b/lyra/src/main.rs index a1efa26..71d29c3 100644 --- a/lyra/src/main.rs +++ b/lyra/src/main.rs @@ -7,6 +7,11 @@ mod gateway; mod lavalink; mod runner; +pub use { + error::command::Error as CommandError, + lavalink::{ClientAndGuildIdAware as LavalinkAndGuildIdAware, ClientAware as LavalinkAware}, +}; + #[tokio::main] #[tracing::instrument] async fn main() { diff --git a/lyra/src/runner.rs b/lyra/src/runner.rs index 7e6c933..5e51320 100644 --- a/lyra/src/runner.rs +++ b/lyra/src/runner.rs @@ -29,7 +29,7 @@ use twilight_model::{ id::{marker::UserMarker, Id}, }; -use crate::core::r#const::metadata::banner; +use crate::{core::r#const::metadata::banner, lavalink::handlers, LavalinkAware}; use super::{ core::{ @@ -38,7 +38,7 @@ use super::{ }, error::runner::{StartError, WaitForSignalError, WaitUntilShutdownError}, gateway, - lavalink::{self, DelegateMethods, LavalinkAware}, + lavalink::DelegateMethods, }; use super::{gateway::LastCachedStates, lavalink::Lavalink}; @@ -67,7 +67,7 @@ fn build_shard_config() -> ShardConfig { UpdatePresencePayload::new( [Activity::from(MinimalActivity { kind: ActivityType::Listening, - name: "/play".into(), + name: String::from("/play"), url: None, })], false, @@ -123,11 +123,11 @@ async fn build_and_split_shards( #[tracing::instrument(skip_all, name = "lavalink")] async fn build_lavalink_client(user_id: Id) -> Lavalink { - let events = lavalink::handlers(); + let events = handlers(); let nodes = Vec::from([lavalink_rs::node::NodeBuilder { - hostname: (*CONFIG.lavalink_host).to_string(), - password: (*CONFIG.lavalink_pwd).to_string(), + hostname: String::from(CONFIG.lavalink_host), + password: String::from(CONFIG.lavalink_pwd), user_id: user_id.into(), ..Default::default() }]); diff --git a/lyra_ext/Cargo.toml b/lyra_ext/Cargo.toml index fba9ba6..208fdf1 100644 --- a/lyra_ext/Cargo.toml +++ b/lyra_ext/Cargo.toml @@ -3,18 +3,18 @@ name = "lyra_ext" version = "0.8.0" edition = "2021" +[lints.rust] +unsafe_op_in_unsafe_fn = "forbid" + [lints.clippy] multiple_unsafe_ops_per_block = "forbid" undocumented_unsafe_blocks = "forbid" enum_glob_use = "forbid" unwrap_used = "forbid" +try_err = "forbid" pedantic = { level = "deny", priority = -1 } nursery = { level = "deny", priority = -1 } -module_name_repetitions = "allow" -cast_possible_truncation = "allow" -cast_sign_loss = "allow" - [dependencies] rstest = "0.21.0" heck = "0.5.0" @@ -23,6 +23,7 @@ unicode-segmentation = "1" bitflags = "2" regex = "1" rayon = "1" +mock_instant = "0.5.1" [dependencies.kmeans_colors] version = "0.6.0" @@ -30,7 +31,7 @@ features = ["palette_color"] default-features = false [dependencies.image] -version = "0.25.1" +version = "0.25.2" features = ["jpeg", "png", "gif", "tiff"] default-features = false diff --git a/lyra_ext/src/image/limit_file_size.rs b/lyra_ext/src/image/limit_file_size.rs index 46f22b9..d0bf179 100644 --- a/lyra_ext/src/image/limit_file_size.rs +++ b/lyra_ext/src/image/limit_file_size.rs @@ -1,37 +1,20 @@ -use image::{imageops::FilterType, DynamicImage, GenericImageView}; - -pub struct LimitImageFileSizeResponse { - image: DynamicImage, - kind: LimitImageFileSizeResponseKind, -} +use std::borrow::Cow; -impl LimitImageFileSizeResponse { - const fn new(image: DynamicImage, kind: LimitImageFileSizeResponseKind) -> Self { - Self { image, kind } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub enum LimitImageFileSizeResponseKind { - Unchanged, - Resized, -} +use image::{imageops::FilterType, DynamicImage, GenericImageView}; pub trait LimitFileSize { - fn limit_file_size(self, limit: u32) -> LimitImageFileSizeResponse; + fn limit_file_size(&self, limit: u32) -> Cow; } +#[allow(clippy::similar_names)] impl LimitFileSize for DynamicImage { - fn limit_file_size(self, limit: u32) -> LimitImageFileSizeResponse { + fn limit_file_size(&self, limit: u32) -> Cow { let (x, y) = self.dimensions(); - let bytes_per_pixel = u32::from(self.color().bytes_per_pixel()); + let bytes_per_pixel = self.color().bytes_per_pixel(); - let max_image_size = x * y * bytes_per_pixel; + let max_image_size = x * y * u32::from(bytes_per_pixel); if max_image_size <= limit { - return LimitImageFileSizeResponse::new( - self, - LimitImageFileSizeResponseKind::Unchanged, - ); + return Cow::Borrowed(self); } let x_to_y = f64::from(x) / f64::from(y); @@ -39,58 +22,60 @@ impl LimitFileSize for DynamicImage { let new_y = (f64::from(limit) / (f64::from(bytes_per_pixel) * x_to_y)).sqrt(); let new_x = new_y * x_to_y; - let image = self.resize(new_x as u32, new_y as u32, FilterType::Lanczos3); - LimitImageFileSizeResponse::new(image, LimitImageFileSizeResponseKind::Resized) + #[allow(clippy::cast_possible_truncation)] + let (new_x_u32, new_y_u32) = ((new_x as i32).unsigned_abs(), (new_y as i32).unsigned_abs()); + + let image = self.resize(new_x_u32, new_y_u32, FilterType::Lanczos3); + Cow::Owned(image) } } #[cfg(test)] mod test { + use std::borrow::Cow; + use const_str::concat as const_str_concat; use rstest::rstest; - use crate::image::limit_file_size::{LimitFileSize, LimitImageFileSizeResponseKind}; + use crate::image::limit_file_size::LimitFileSize; const TEST_RESOURCES_PATH: &str = "src/image/test"; #[rstest] #[case( const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_1.png"), - 2_u32.pow(20), - LimitImageFileSizeResponseKind::Resized - )] - #[case( - const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_2.png"), - 2_u32.pow(20), - LimitImageFileSizeResponseKind::Resized + 10 * 2_u32.pow(20), )] #[case( const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_1.png"), - 10 * 2_u32.pow(20), - LimitImageFileSizeResponseKind::Unchanged + 50 * 2_u32.pow(20), )] #[case( const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_2.png"), - 10 * 2_u32.pow(20), - LimitImageFileSizeResponseKind::Resized + 50 * 2_u32.pow(20), )] + fn limit_file_size_borrowed(#[case] input_path: &str, #[case] input_limit: u32) { + let image = image::open(input_path).unwrap_or_else(|e| panic!("{e:#?}")); + let response = image.limit_file_size(input_limit); + assert!(matches!(response, Cow::Borrowed(_))); + } + + #[rstest] #[case( const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_1.png"), - 50 * 2_u32.pow(20), - LimitImageFileSizeResponseKind::Unchanged + 2_u32.pow(20), )] #[case( const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_2.png"), - 50 * 2_u32.pow(20), - LimitImageFileSizeResponseKind::Unchanged + 2_u32.pow(20), + )] + #[case( + const_str_concat!(TEST_RESOURCES_PATH, "/limit_file_size_2.png"), + 10 * 2_u32.pow(20), )] - fn limit_file_size( - #[case] input_path: &str, - #[case] input_limit: u32, - #[case] expected_response_kind: LimitImageFileSizeResponseKind, - ) { + fn limit_file_size_owned(#[case] input_path: &str, #[case] input_limit: u32) { let image = image::open(input_path).unwrap_or_else(|e| panic!("{e:#?}")); let response = image.limit_file_size(input_limit); - assert_eq!(response.kind, expected_response_kind); + assert!(matches!(response, Cow::Owned(_))); } } diff --git a/lyra_ext/src/iter/chunked_range.rs b/lyra_ext/src/iter/chunked_range.rs index e0ba1bd..3170112 100644 --- a/lyra_ext/src/iter/chunked_range.rs +++ b/lyra_ext/src/iter/chunked_range.rs @@ -22,224 +22,224 @@ mod test { #[rstest] #[case(0, [], [])] #[case(1, [], [])] - fn chunked_range_0( + fn chunked_range_0( #[case] input_start: usize, - #[case] input_chunk_sizes: [usize; N], - #[case] expected: [Vec; N], + #[case] input_chunk_sizes: [usize; 0], + #[case] expected: [[usize; 0]; 0], ) { assert_eq!( chunked_range(input_start, input_chunk_sizes) - .map(Iterator::collect::>) + .map(Vec::from_iter) .collect::>(), expected ); } #[rstest] - #[case(0, [0], [vec![]])] - #[case(1, [0], [vec![]])] - #[case(0, [1], [vec![0]])] - #[case(1, [1], [vec![1]])] - #[case(0, [2], [vec![0, 1]])] - #[case(1, [2], [vec![1, 2]])] - #[case(0, [3], [vec![0, 1, 2]])] - #[case(1, [3], [vec![1, 2, 3]])] - fn chunked_range_1( + #[case(0, [0], [[ ]])] + #[case(1, [0], [[ ]])] + #[case(0, [1], [[0 ]])] + #[case(1, [1], [[1 ]])] + #[case(0, [2], [[0, 1 ]])] + #[case(1, [2], [[1, 2 ]])] + #[case(0, [3], [[0, 1, 2]])] + #[case(1, [3], [[1, 2, 3]])] + fn chunked_range_1( #[case] input_start: usize, - #[case] input_chunk_sizes: [usize; N], - #[case] expected: [Vec; N], + #[case] input_chunk_sizes: [usize; 1], + #[case] expected: [[usize; M]; 1], ) { assert_eq!( chunked_range(input_start, input_chunk_sizes) - .map(Iterator::collect::>) + .map(Vec::from_iter) .collect::>(), expected ); } #[rstest] - #[case(0, [0, 0], [vec![], vec![]])] - #[case(1, [0, 0], [vec![], vec![]])] - #[case(0, [0, 1], [vec![], vec![0]])] - #[case(1, [0, 1], [vec![], vec![1]])] - #[case(0, [0, 2], [vec![], vec![0, 1]])] - #[case(1, [0, 2], [vec![], vec![1, 2]])] - #[case(0, [0, 3], [vec![], vec![0, 1, 2]])] - #[case(1, [0, 3], [vec![], vec![1, 2, 3]])] - #[case(0, [1, 0], [vec![0], vec![]])] - #[case(1, [1, 0], [vec![1], vec![]])] - #[case(0, [1, 1], [vec![0], vec![1]])] - #[case(1, [1, 1], [vec![1], vec![2]])] - #[case(0, [1, 2], [vec![0], vec![1, 2]])] - #[case(1, [1, 2], [vec![1], vec![2, 3]])] - #[case(0, [1, 3], [vec![0], vec![1, 2, 3]])] - #[case(1, [1, 3], [vec![1], vec![2, 3, 4]])] - #[case(0, [2, 0], [vec![0, 1], vec![]])] - #[case(1, [2, 0], [vec![1, 2], vec![]])] - #[case(0, [2, 1], [vec![0, 1], vec![2]])] - #[case(1, [2, 1], [vec![1, 2], vec![3]])] - #[case(0, [2, 2], [vec![0, 1], vec![2, 3]])] - #[case(1, [2, 2], [vec![1, 2], vec![3, 4]])] - #[case(0, [2, 3], [vec![0, 1], vec![2, 3, 4]])] - #[case(1, [2, 3], [vec![1, 2], vec![3, 4, 5]])] - #[case(0, [3, 0], [vec![0, 1, 2], vec![]])] - #[case(1, [3, 0], [vec![1, 2, 3], vec![]])] - #[case(0, [3, 1], [vec![0, 1, 2], vec![3]])] - #[case(1, [3, 1], [vec![1, 2, 3], vec![4]])] - #[case(0, [3, 2], [vec![0, 1, 2], vec![3, 4]])] - #[case(1, [3, 2], [vec![1, 2, 3], vec![4, 5]])] + #[case(0, [0, 0], [vec![ ], vec![ ]])] + #[case(1, [0, 0], [vec![ ], vec![ ]])] + #[case(0, [0, 1], [vec![ ], vec![0 ]])] + #[case(1, [0, 1], [vec![ ], vec![1 ]])] + #[case(0, [0, 2], [vec![ ], vec![0, 1 ]])] + #[case(1, [0, 2], [vec![ ], vec![1, 2 ]])] + #[case(0, [0, 3], [vec![ ], vec![0, 1, 2]])] + #[case(1, [0, 3], [vec![ ], vec![1, 2, 3]])] + #[case(0, [1, 0], [vec![0 ], vec![ ]])] + #[case(1, [1, 0], [vec![1 ], vec![ ]])] + #[case(0, [1, 1], [vec![0 ], vec![1 ]])] + #[case(1, [1, 1], [vec![1 ], vec![2 ]])] + #[case(0, [1, 2], [vec![0 ], vec![1, 2 ]])] + #[case(1, [1, 2], [vec![1 ], vec![2, 3 ]])] + #[case(0, [1, 3], [vec![0 ], vec![1, 2, 3]])] + #[case(1, [1, 3], [vec![1 ], vec![2, 3, 4]])] + #[case(0, [2, 0], [vec![0, 1 ], vec![ ]])] + #[case(1, [2, 0], [vec![1, 2 ], vec![ ]])] + #[case(0, [2, 1], [vec![0, 1 ], vec![2 ]])] + #[case(1, [2, 1], [vec![1, 2 ], vec![3 ]])] + #[case(0, [2, 2], [vec![0, 1 ], vec![2, 3 ]])] + #[case(1, [2, 2], [vec![1, 2 ], vec![3, 4 ]])] + #[case(0, [2, 3], [vec![0, 1 ], vec![2, 3, 4]])] + #[case(1, [2, 3], [vec![1, 2 ], vec![3, 4, 5]])] + #[case(0, [3, 0], [vec![0, 1, 2], vec![ ]])] + #[case(1, [3, 0], [vec![1, 2, 3], vec![ ]])] + #[case(0, [3, 1], [vec![0, 1, 2], vec![3 ]])] + #[case(1, [3, 1], [vec![1, 2, 3], vec![4 ]])] + #[case(0, [3, 2], [vec![0, 1, 2], vec![3, 4 ]])] + #[case(1, [3, 2], [vec![1, 2, 3], vec![4, 5 ]])] #[case(0, [3, 3], [vec![0, 1, 2], vec![3, 4, 5]])] #[case(1, [3, 3], [vec![1, 2, 3], vec![4, 5, 6]])] - fn chunked_range_2( + fn chunked_range_2( #[case] input_start: usize, - #[case] input_chunk_sizes: [usize; N], - #[case] expected: [Vec; N], + #[case] input_chunk_sizes: [usize; 2], + #[case] expected: [Vec; 2], ) { assert_eq!( chunked_range(input_start, input_chunk_sizes) - .map(Iterator::collect::>) + .map(Vec::from_iter) .collect::>(), expected ); } #[rstest] - #[case(0, [0, 0, 0], [vec![], vec![], vec![]])] - #[case(1, [0, 0, 0], [vec![], vec![], vec![]])] - #[case(0, [0, 1, 0], [vec![], vec![0], vec![]])] - #[case(1, [0, 1, 0], [vec![], vec![1], vec![]])] - #[case(0, [0, 2, 0], [vec![], vec![0, 1], vec![]])] - #[case(1, [0, 2, 0], [vec![], vec![1, 2], vec![]])] - #[case(0, [0, 3, 0], [vec![], vec![0, 1, 2], vec![]])] - #[case(1, [0, 3, 0], [vec![], vec![1, 2, 3], vec![]])] - #[case(0, [1, 0, 0], [vec![0], vec![], vec![]])] - #[case(1, [1, 0, 0], [vec![1], vec![], vec![]])] - #[case(0, [1, 1, 0], [vec![0], vec![1], vec![]])] - #[case(1, [1, 1, 0], [vec![1], vec![2], vec![]])] - #[case(0, [1, 2, 0], [vec![0], vec![1, 2], vec![]])] - #[case(1, [1, 2, 0], [vec![1], vec![2, 3], vec![]])] - #[case(0, [1, 3, 0], [vec![0], vec![1, 2, 3], vec![]])] - #[case(1, [1, 3, 0], [vec![1], vec![2, 3, 4], vec![]])] - #[case(0, [2, 0, 0], [vec![0, 1], vec![], vec![]])] - #[case(1, [2, 0, 0], [vec![1, 2], vec![], vec![]])] - #[case(0, [2, 1, 0], [vec![0, 1], vec![2], vec![]])] - #[case(1, [2, 1, 0], [vec![1, 2], vec![3], vec![]])] - #[case(0, [2, 2, 0], [vec![0, 1], vec![2, 3], vec![]])] - #[case(1, [2, 2, 0], [vec![1, 2], vec![3, 4], vec![]])] - #[case(0, [2, 3, 0], [vec![0, 1], vec![2, 3, 4], vec![]])] - #[case(1, [2, 3, 0], [vec![1, 2], vec![3, 4, 5], vec![]])] - #[case(0, [3, 0, 0], [vec![0, 1, 2], vec![], vec![]])] - #[case(1, [3, 0, 0], [vec![1, 2, 3], vec![], vec![]])] - #[case(0, [3, 1, 0], [vec![0, 1, 2], vec![3], vec![]])] - #[case(1, [3, 1, 0], [vec![1, 2, 3], vec![4], vec![]])] - #[case(0, [3, 2, 0], [vec![0, 1, 2], vec![3, 4], vec![]])] - #[case(1, [3, 2, 0], [vec![1, 2, 3], vec![4, 5], vec![]])] - #[case(0, [3, 3, 0], [vec![0, 1, 2], vec![3, 4, 5], vec![]])] - #[case(1, [3, 3, 0], [vec![1, 2, 3], vec![4, 5, 6], vec![]])] - #[case(0, [0, 0, 1], [vec![], vec![], vec![0]])] - #[case(1, [0, 0, 1], [vec![], vec![], vec![1]])] - #[case(0, [0, 1, 1], [vec![], vec![0], vec![1]])] - #[case(1, [0, 1, 1], [vec![], vec![1], vec![2]])] - #[case(0, [0, 2, 1], [vec![], vec![0, 1], vec![2]])] - #[case(1, [0, 2, 1], [vec![], vec![1, 2], vec![3]])] - #[case(0, [0, 3, 1], [vec![], vec![0, 1, 2], vec![3]])] - #[case(1, [0, 3, 1], [vec![], vec![1, 2, 3], vec![4]])] - #[case(0, [1, 0, 1], [vec![0], vec![], vec![1]])] - #[case(1, [1, 0, 1], [vec![1], vec![], vec![2]])] - #[case(0, [1, 1, 1], [vec![0], vec![1], vec![2]])] - #[case(1, [1, 1, 1], [vec![1], vec![2], vec![3]])] - #[case(0, [1, 2, 1], [vec![0], vec![1, 2], vec![3]])] - #[case(1, [1, 2, 1], [vec![1], vec![2, 3], vec![4]])] - #[case(0, [1, 3, 1], [vec![0], vec![1, 2, 3], vec![4]])] - #[case(1, [1, 3, 1], [vec![1], vec![2, 3, 4], vec![5]])] - #[case(0, [2, 0, 1], [vec![0, 1], vec![], vec![2]])] - #[case(1, [2, 0, 1], [vec![1, 2], vec![], vec![3]])] - #[case(0, [2, 1, 1], [vec![0, 1], vec![2], vec![3]])] - #[case(1, [2, 1, 1], [vec![1, 2], vec![3], vec![4]])] - #[case(0, [2, 2, 1], [vec![0, 1], vec![2, 3], vec![4]])] - #[case(1, [2, 2, 1], [vec![1, 2], vec![3, 4], vec![5]])] - #[case(0, [2, 3, 1], [vec![0, 1], vec![2, 3, 4], vec![5]])] - #[case(1, [2, 3, 1], [vec![1, 2], vec![3, 4, 5], vec![6]])] - #[case(0, [3, 0, 1], [vec![0, 1, 2], vec![], vec![3]])] - #[case(1, [3, 0, 1], [vec![1, 2, 3], vec![], vec![4]])] - #[case(0, [3, 1, 1], [vec![0, 1, 2], vec![3], vec![4]])] - #[case(1, [3, 1, 1], [vec![1, 2, 3], vec![4], vec![5]])] - #[case(0, [3, 2, 1], [vec![0, 1, 2], vec![3, 4], vec![5]])] - #[case(1, [3, 2, 1], [vec![1, 2, 3], vec![4, 5], vec![6]])] - #[case(0, [3, 3, 1], [vec![0, 1, 2], vec![3, 4, 5], vec![6]])] - #[case(1, [3, 3, 1], [vec![1, 2, 3], vec![4, 5, 6], vec![7]])] - #[case(0, [0, 0, 2], [vec![], vec![], vec![0, 1]])] - #[case(1, [0, 0, 2], [vec![], vec![], vec![1, 2]])] - #[case(0, [0, 1, 2], [vec![], vec![0], vec![1, 2]])] - #[case(1, [0, 1, 2], [vec![], vec![1], vec![2, 3]])] - #[case(0, [0, 2, 2], [vec![], vec![0, 1], vec![2, 3]])] - #[case(1, [0, 2, 2], [vec![], vec![1, 2], vec![3, 4]])] - #[case(0, [0, 3, 2], [vec![], vec![0, 1, 2], vec![3, 4]])] - #[case(1, [0, 3, 2], [vec![], vec![1, 2, 3], vec![4, 5]])] - #[case(0, [1, 0, 2], [vec![0], vec![], vec![1, 2]])] - #[case(1, [1, 0, 2], [vec![1], vec![], vec![2, 3]])] - #[case(0, [1, 1, 2], [vec![0], vec![1], vec![2, 3]])] - #[case(1, [1, 1, 2], [vec![1], vec![2], vec![3, 4]])] - #[case(0, [1, 2, 2], [vec![0], vec![1, 2], vec![3, 4]])] - #[case(1, [1, 2, 2], [vec![1], vec![2, 3], vec![4, 5]])] - #[case(0, [1, 3, 2], [vec![0], vec![1, 2, 3], vec![4, 5]])] - #[case(1, [1, 3, 2], [vec![1], vec![2, 3, 4], vec![5, 6]])] - #[case(0, [2, 0, 2], [vec![0, 1], vec![], vec![2, 3]])] - #[case(1, [2, 0, 2], [vec![1, 2], vec![], vec![3, 4]])] - #[case(0, [2, 1, 2], [vec![0, 1], vec![2], vec![3, 4]])] - #[case(1, [2, 1, 2], [vec![1, 2], vec![3], vec![4, 5]])] - #[case(0, [2, 2, 2], [vec![0, 1], vec![2, 3], vec![4, 5]])] - #[case(1, [2, 2, 2], [vec![1, 2], vec![3, 4], vec![5, 6]])] - #[case(0, [2, 3, 2], [vec![0, 1], vec![2, 3, 4], vec![5, 6]])] - #[case(1, [2, 3, 2], [vec![1, 2], vec![3, 4, 5], vec![6, 7]])] - #[case(0, [3, 0, 2], [vec![0, 1, 2], vec![], vec![3, 4]])] - #[case(1, [3, 0, 2], [vec![1, 2, 3], vec![], vec![4, 5]])] - #[case(0, [3, 1, 2], [vec![0, 1, 2], vec![3], vec![4, 5]])] - #[case(1, [3, 1, 2], [vec![1, 2, 3], vec![4], vec![5, 6]])] - #[case(0, [3, 2, 2], [vec![0, 1, 2], vec![3, 4], vec![5, 6]])] - #[case(1, [3, 2, 2], [vec![1, 2, 3], vec![4, 5], vec![6, 7]])] - #[case(0, [3, 3, 2], [vec![0, 1, 2], vec![3, 4, 5], vec![6, 7]])] - #[case(1, [3, 3, 2], [vec![1, 2, 3], vec![4, 5, 6], vec![7, 8]])] - #[case(0, [0, 0, 3], [vec![], vec![], vec![0, 1, 2]])] - #[case(1, [0, 0, 3], [vec![], vec![], vec![1, 2, 3]])] - #[case(0, [0, 1, 3], [vec![], vec![0], vec![1, 2, 3]])] - #[case(1, [0, 1, 3], [vec![], vec![1], vec![2, 3, 4]])] - #[case(0, [0, 2, 3], [vec![], vec![0, 1], vec![2, 3, 4]])] - #[case(1, [0, 2, 3], [vec![], vec![1, 2], vec![3, 4, 5]])] - #[case(0, [0, 3, 3], [vec![], vec![0, 1, 2], vec![3, 4, 5]])] - #[case(1, [0, 3, 3], [vec![], vec![1, 2, 3], vec![4, 5, 6]])] - #[case(0, [1, 0, 3], [vec![0], vec![], vec![1, 2, 3]])] - #[case(1, [1, 0, 3], [vec![1], vec![], vec![2, 3, 4]])] - #[case(0, [1, 1, 3], [vec![0], vec![1], vec![2, 3, 4]])] - #[case(1, [1, 1, 3], [vec![1], vec![2], vec![3, 4, 5]])] - #[case(0, [1, 2, 3], [vec![0], vec![1, 2], vec![3, 4, 5]])] - #[case(1, [1, 2, 3], [vec![1], vec![2, 3], vec![4, 5, 6]])] - #[case(0, [1, 3, 3], [vec![0], vec![1, 2, 3], vec![4, 5, 6]])] - #[case(1, [1, 3, 3], [vec![1], vec![2, 3, 4], vec![5, 6, 7]])] - #[case(0, [2, 0, 3], [vec![0, 1], vec![], vec![2, 3, 4]])] - #[case(1, [2, 0, 3], [vec![1, 2], vec![], vec![3, 4, 5]])] - #[case(0, [2, 1, 3], [vec![0, 1], vec![2], vec![3, 4, 5]])] - #[case(1, [2, 1, 3], [vec![1, 2], vec![3], vec![4, 5, 6]])] - #[case(0, [2, 2, 3], [vec![0, 1], vec![2, 3], vec![4, 5, 6]])] - #[case(1, [2, 2, 3], [vec![1, 2], vec![3, 4], vec![5, 6, 7]])] - #[case(0, [2, 3, 3], [vec![0, 1], vec![2, 3, 4], vec![5, 6, 7]])] - #[case(1, [2, 3, 3], [vec![1, 2], vec![3, 4, 5], vec![6, 7, 8]])] - #[case(0, [3, 0, 3], [vec![0, 1, 2], vec![], vec![3, 4, 5]])] - #[case(1, [3, 0, 3], [vec![1, 2, 3], vec![], vec![4, 5, 6]])] - #[case(0, [3, 1, 3], [vec![0, 1, 2], vec![3], vec![4, 5, 6]])] - #[case(1, [3, 1, 3], [vec![1, 2, 3], vec![4], vec![5, 6, 7]])] - #[case(0, [3, 2, 3], [vec![0, 1, 2], vec![3, 4], vec![5, 6, 7]])] - #[case(1, [3, 2, 3], [vec![1, 2, 3], vec![4, 5], vec![6, 7, 8]])] + #[case(0, [0, 0, 0], [vec![ ], vec![ ], vec![ ]])] + #[case(1, [0, 0, 0], [vec![ ], vec![ ], vec![ ]])] + #[case(0, [0, 1, 0], [vec![ ], vec![0 ], vec![ ]])] + #[case(1, [0, 1, 0], [vec![ ], vec![1 ], vec![ ]])] + #[case(0, [0, 2, 0], [vec![ ], vec![0, 1 ], vec![ ]])] + #[case(1, [0, 2, 0], [vec![ ], vec![1, 2 ], vec![ ]])] + #[case(0, [0, 3, 0], [vec![ ], vec![0, 1, 2], vec![ ]])] + #[case(1, [0, 3, 0], [vec![ ], vec![1, 2, 3], vec![ ]])] + #[case(0, [1, 0, 0], [vec![0 ], vec![ ], vec![ ]])] + #[case(1, [1, 0, 0], [vec![1 ], vec![ ], vec![ ]])] + #[case(0, [1, 1, 0], [vec![0 ], vec![1 ], vec![ ]])] + #[case(1, [1, 1, 0], [vec![1 ], vec![2 ], vec![ ]])] + #[case(0, [1, 2, 0], [vec![0 ], vec![1, 2 ], vec![ ]])] + #[case(1, [1, 2, 0], [vec![1 ], vec![2, 3 ], vec![ ]])] + #[case(0, [1, 3, 0], [vec![0 ], vec![1, 2, 3], vec![ ]])] + #[case(1, [1, 3, 0], [vec![1 ], vec![2, 3, 4], vec![ ]])] + #[case(0, [2, 0, 0], [vec![0, 1 ], vec![ ], vec![ ]])] + #[case(1, [2, 0, 0], [vec![1, 2 ], vec![ ], vec![ ]])] + #[case(0, [2, 1, 0], [vec![0, 1 ], vec![2 ], vec![ ]])] + #[case(1, [2, 1, 0], [vec![1, 2 ], vec![3 ], vec![ ]])] + #[case(0, [2, 2, 0], [vec![0, 1 ], vec![2, 3 ], vec![ ]])] + #[case(1, [2, 2, 0], [vec![1, 2 ], vec![3, 4 ], vec![ ]])] + #[case(0, [2, 3, 0], [vec![0, 1 ], vec![2, 3, 4], vec![ ]])] + #[case(1, [2, 3, 0], [vec![1, 2 ], vec![3, 4, 5], vec![ ]])] + #[case(0, [3, 0, 0], [vec![0, 1, 2], vec![ ], vec![ ]])] + #[case(1, [3, 0, 0], [vec![1, 2, 3], vec![ ], vec![ ]])] + #[case(0, [3, 1, 0], [vec![0, 1, 2], vec![3 ], vec![ ]])] + #[case(1, [3, 1, 0], [vec![1, 2, 3], vec![4 ], vec![ ]])] + #[case(0, [3, 2, 0], [vec![0, 1, 2], vec![3, 4 ], vec![ ]])] + #[case(1, [3, 2, 0], [vec![1, 2, 3], vec![4, 5 ], vec![ ]])] + #[case(0, [3, 3, 0], [vec![0, 1, 2], vec![3, 4, 5], vec![ ]])] + #[case(1, [3, 3, 0], [vec![1, 2, 3], vec![4, 5, 6], vec![ ]])] + #[case(0, [0, 0, 1], [vec![ ], vec![ ], vec![0 ]])] + #[case(1, [0, 0, 1], [vec![ ], vec![ ], vec![1 ]])] + #[case(0, [0, 1, 1], [vec![ ], vec![0 ], vec![1 ]])] + #[case(1, [0, 1, 1], [vec![ ], vec![1 ], vec![2 ]])] + #[case(0, [0, 2, 1], [vec![ ], vec![0, 1 ], vec![2 ]])] + #[case(1, [0, 2, 1], [vec![ ], vec![1, 2 ], vec![3 ]])] + #[case(0, [0, 3, 1], [vec![ ], vec![0, 1, 2], vec![3 ]])] + #[case(1, [0, 3, 1], [vec![ ], vec![1, 2, 3], vec![4 ]])] + #[case(0, [1, 0, 1], [vec![0 ], vec![ ], vec![1 ]])] + #[case(1, [1, 0, 1], [vec![1 ], vec![ ], vec![2 ]])] + #[case(0, [1, 1, 1], [vec![0 ], vec![1 ], vec![2 ]])] + #[case(1, [1, 1, 1], [vec![1 ], vec![2 ], vec![3 ]])] + #[case(0, [1, 2, 1], [vec![0 ], vec![1, 2 ], vec![3 ]])] + #[case(1, [1, 2, 1], [vec![1 ], vec![2, 3 ], vec![4 ]])] + #[case(0, [1, 3, 1], [vec![0 ], vec![1, 2, 3], vec![4 ]])] + #[case(1, [1, 3, 1], [vec![1 ], vec![2, 3, 4], vec![5 ]])] + #[case(0, [2, 0, 1], [vec![0, 1 ], vec![ ], vec![2 ]])] + #[case(1, [2, 0, 1], [vec![1, 2 ], vec![ ], vec![3 ]])] + #[case(0, [2, 1, 1], [vec![0, 1 ], vec![2 ], vec![3 ]])] + #[case(1, [2, 1, 1], [vec![1, 2 ], vec![3 ], vec![4 ]])] + #[case(0, [2, 2, 1], [vec![0, 1 ], vec![2, 3 ], vec![4 ]])] + #[case(1, [2, 2, 1], [vec![1, 2 ], vec![3, 4 ], vec![5 ]])] + #[case(0, [2, 3, 1], [vec![0, 1 ], vec![2, 3, 4], vec![5 ]])] + #[case(1, [2, 3, 1], [vec![1, 2 ], vec![3, 4, 5], vec![6 ]])] + #[case(0, [3, 0, 1], [vec![0, 1, 2], vec![ ], vec![3 ]])] + #[case(1, [3, 0, 1], [vec![1, 2, 3], vec![ ], vec![4 ]])] + #[case(0, [3, 1, 1], [vec![0, 1, 2], vec![3 ], vec![4 ]])] + #[case(1, [3, 1, 1], [vec![1, 2, 3], vec![4 ], vec![5 ]])] + #[case(0, [3, 2, 1], [vec![0, 1, 2], vec![3, 4 ], vec![5 ]])] + #[case(1, [3, 2, 1], [vec![1, 2, 3], vec![4, 5 ], vec![6 ]])] + #[case(0, [3, 3, 1], [vec![0, 1, 2], vec![3, 4, 5], vec![6 ]])] + #[case(1, [3, 3, 1], [vec![1, 2, 3], vec![4, 5, 6], vec![7 ]])] + #[case(0, [0, 0, 2], [vec![ ], vec![ ], vec![0, 1 ]])] + #[case(1, [0, 0, 2], [vec![ ], vec![ ], vec![1, 2 ]])] + #[case(0, [0, 1, 2], [vec![ ], vec![0 ], vec![1, 2 ]])] + #[case(1, [0, 1, 2], [vec![ ], vec![1 ], vec![2, 3 ]])] + #[case(0, [0, 2, 2], [vec![ ], vec![0, 1 ], vec![2, 3 ]])] + #[case(1, [0, 2, 2], [vec![ ], vec![1, 2 ], vec![3, 4 ]])] + #[case(0, [0, 3, 2], [vec![ ], vec![0, 1, 2], vec![3, 4 ]])] + #[case(1, [0, 3, 2], [vec![ ], vec![1, 2, 3], vec![4, 5 ]])] + #[case(0, [1, 0, 2], [vec![0 ], vec![ ], vec![1, 2 ]])] + #[case(1, [1, 0, 2], [vec![1 ], vec![ ], vec![2, 3 ]])] + #[case(0, [1, 1, 2], [vec![0 ], vec![1 ], vec![2, 3 ]])] + #[case(1, [1, 1, 2], [vec![1 ], vec![2 ], vec![3, 4 ]])] + #[case(0, [1, 2, 2], [vec![0 ], vec![1, 2 ], vec![3, 4 ]])] + #[case(1, [1, 2, 2], [vec![1 ], vec![2, 3 ], vec![4, 5 ]])] + #[case(0, [1, 3, 2], [vec![0 ], vec![1, 2, 3], vec![4, 5 ]])] + #[case(1, [1, 3, 2], [vec![1 ], vec![2, 3, 4], vec![5, 6 ]])] + #[case(0, [2, 0, 2], [vec![0, 1 ], vec![ ], vec![2, 3 ]])] + #[case(1, [2, 0, 2], [vec![1, 2 ], vec![ ], vec![3, 4 ]])] + #[case(0, [2, 1, 2], [vec![0, 1 ], vec![2 ], vec![3, 4 ]])] + #[case(1, [2, 1, 2], [vec![1, 2 ], vec![3 ], vec![4, 5 ]])] + #[case(0, [2, 2, 2], [vec![0, 1 ], vec![2, 3 ], vec![4, 5 ]])] + #[case(1, [2, 2, 2], [vec![1, 2 ], vec![3, 4 ], vec![5, 6 ]])] + #[case(0, [2, 3, 2], [vec![0, 1 ], vec![2, 3, 4], vec![5, 6 ]])] + #[case(1, [2, 3, 2], [vec![1, 2 ], vec![3, 4, 5], vec![6, 7 ]])] + #[case(0, [3, 0, 2], [vec![0, 1, 2], vec![ ], vec![3, 4 ]])] + #[case(1, [3, 0, 2], [vec![1, 2, 3], vec![ ], vec![4, 5 ]])] + #[case(0, [3, 1, 2], [vec![0, 1, 2], vec![3 ], vec![4, 5 ]])] + #[case(1, [3, 1, 2], [vec![1, 2, 3], vec![4 ], vec![5, 6 ]])] + #[case(0, [3, 2, 2], [vec![0, 1, 2], vec![3, 4 ], vec![5, 6 ]])] + #[case(1, [3, 2, 2], [vec![1, 2, 3], vec![4, 5 ], vec![6, 7 ]])] + #[case(0, [3, 3, 2], [vec![0, 1, 2], vec![3, 4, 5], vec![6, 7 ]])] + #[case(1, [3, 3, 2], [vec![1, 2, 3], vec![4, 5, 6], vec![7, 8 ]])] + #[case(0, [0, 0, 3], [vec![ ], vec![ ], vec![0, 1, 2]])] + #[case(1, [0, 0, 3], [vec![ ], vec![ ], vec![1, 2, 3]])] + #[case(0, [0, 1, 3], [vec![ ], vec![0 ], vec![1, 2, 3]])] + #[case(1, [0, 1, 3], [vec![ ], vec![1 ], vec![2, 3, 4]])] + #[case(0, [0, 2, 3], [vec![ ], vec![0, 1 ], vec![2, 3, 4]])] + #[case(1, [0, 2, 3], [vec![ ], vec![1, 2 ], vec![3, 4, 5]])] + #[case(0, [0, 3, 3], [vec![ ], vec![0, 1, 2], vec![3, 4, 5]])] + #[case(1, [0, 3, 3], [vec![ ], vec![1, 2, 3], vec![4, 5, 6]])] + #[case(0, [1, 0, 3], [vec![0 ], vec![ ], vec![1, 2, 3]])] + #[case(1, [1, 0, 3], [vec![1 ], vec![ ], vec![2, 3, 4]])] + #[case(0, [1, 1, 3], [vec![0 ], vec![1 ], vec![2, 3, 4]])] + #[case(1, [1, 1, 3], [vec![1 ], vec![2 ], vec![3, 4, 5]])] + #[case(0, [1, 2, 3], [vec![0 ], vec![1, 2 ], vec![3, 4, 5]])] + #[case(1, [1, 2, 3], [vec![1 ], vec![2, 3 ], vec![4, 5, 6]])] + #[case(0, [1, 3, 3], [vec![0 ], vec![1, 2, 3], vec![4, 5, 6]])] + #[case(1, [1, 3, 3], [vec![1 ], vec![2, 3, 4], vec![5, 6, 7]])] + #[case(0, [2, 0, 3], [vec![0, 1 ], vec![ ], vec![2, 3, 4]])] + #[case(1, [2, 0, 3], [vec![1, 2 ], vec![ ], vec![3, 4, 5]])] + #[case(0, [2, 1, 3], [vec![0, 1 ], vec![2 ], vec![3, 4, 5]])] + #[case(1, [2, 1, 3], [vec![1, 2 ], vec![3 ], vec![4, 5, 6]])] + #[case(0, [2, 2, 3], [vec![0, 1 ], vec![2, 3 ], vec![4, 5, 6]])] + #[case(1, [2, 2, 3], [vec![1, 2 ], vec![3, 4 ], vec![5, 6, 7]])] + #[case(0, [2, 3, 3], [vec![0, 1 ], vec![2, 3, 4], vec![5, 6, 7]])] + #[case(1, [2, 3, 3], [vec![1, 2 ], vec![3, 4, 5], vec![6, 7, 8]])] + #[case(0, [3, 0, 3], [vec![0, 1, 2], vec![ ], vec![3, 4, 5]])] + #[case(1, [3, 0, 3], [vec![1, 2, 3], vec![ ], vec![4, 5, 6]])] + #[case(0, [3, 1, 3], [vec![0, 1, 2], vec![3 ], vec![4, 5, 6]])] + #[case(1, [3, 1, 3], [vec![1, 2, 3], vec![4 ], vec![5, 6, 7]])] + #[case(0, [3, 2, 3], [vec![0, 1, 2], vec![3, 4 ], vec![5, 6, 7]])] + #[case(1, [3, 2, 3], [vec![1, 2, 3], vec![4, 5 ], vec![6, 7, 8]])] #[case(0, [3, 3, 3], [vec![0, 1, 2], vec![3, 4, 5], vec![6, 7, 8]])] #[case(1, [3, 3, 3], [vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]])] - fn chunked_range_3( + fn chunked_range_3( #[case] input_start: usize, - #[case] input_chunk_sizes: [usize; N], - #[case] expected: [Vec; N], + #[case] input_chunk_sizes: [usize; 3], + #[case] expected: [Vec; 3], ) { assert_eq!( chunked_range(input_start, input_chunk_sizes) - .map(Iterator::collect::>) + .map(Vec::from_iter) .collect::>(), expected ); diff --git a/lyra_ext/src/iter/multi_interleave.rs b/lyra_ext/src/iter/multi_interleave.rs index 60ccc51..fe5f5ab 100644 --- a/lyra_ext/src/iter/multi_interleave.rs +++ b/lyra_ext/src/iter/multi_interleave.rs @@ -60,107 +60,110 @@ mod tests { use super::multi_interleave; #[rstest] - #[case([], vec![])] - fn multi_interleave_0(#[case] input: [Vec; 0], #[case] expected: Vec) { + #[case([], [])] + fn multi_interleave_0(#[case] input: [[u8; 0]; 0], #[case] expected: [u8; 0]) { assert_eq!(multi_interleave(input).collect::>(), expected); } #[rstest] - #[case([vec![]] , vec![])] - #[case([vec![1]] , vec![1])] - #[case([vec![1, 2]] , vec![1, 2])] - #[case([vec![1, 2, 3]], vec![1, 2, 3])] - fn multi_interleave_1(#[case] input: [Vec; 1], #[case] expected: Vec) { + #[case([[ ]], [ ])] + #[case([[1 ]], [1 ])] + #[case([[1, 2]], [1, 2 ])] + #[case([[1, 2, 3]], [1, 2, 3])] + fn multi_interleave_1( + #[case] input: [[u8; N]; 1], + #[case] expected: [u8; M], + ) { assert_eq!(multi_interleave(input).collect::>(), expected); } #[rstest] - #[case([vec![] , vec![]] , vec![])] - #[case([vec![1] , vec![]] , vec![1])] - #[case([vec![1, 2] , vec![]] , vec![1, 2])] - #[case([vec![1, 2, 3], vec![]] , vec![1, 2, 3])] - #[case([vec![] , vec![1]] , vec![1])] - #[case([vec![1] , vec![1]] , vec![1, 1])] - #[case([vec![1, 2] , vec![1]] , vec![1, 1, 2])] - #[case([vec![1, 2, 3], vec![1]] , vec![1, 1, 2, 3])] - #[case([vec![] , vec![1, 2]] , vec![1, 2])] - #[case([vec![1] , vec![1, 2]] , vec![1, 1, 2])] - #[case([vec![1, 2] , vec![1, 2]] , vec![1, 1, 2, 2])] - #[case([vec![1, 2, 3], vec![1, 2]] , vec![1, 1, 2, 2, 3])] - #[case([vec![] , vec![1, 2, 3]], vec![1, 2, 3])] - #[case([vec![1] , vec![1, 2, 3]], vec![1, 1, 2, 3])] - #[case([vec![1, 2] , vec![1, 2, 3]], vec![1, 1, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 2, 2, 3, 3])] - fn multi_interleave_2(#[case] input: [Vec; 2], #[case] expected: Vec) { + #[case([vec![ ], vec![ ]], [ ])] + #[case([vec![1 ], vec![ ]], [1 ])] + #[case([vec![1, 2 ], vec![ ]], [1, 2 ])] + #[case([vec![1, 2, 3], vec![ ]], [1, 2, 3 ])] + #[case([vec![ ], vec![1 ]], [1 ])] + #[case([vec![1 ], vec![1 ]], [1, 1 ])] + #[case([vec![1, 2 ], vec![1 ]], [1, 1, 2 ])] + #[case([vec![1, 2, 3], vec![1 ]], [1, 1, 2, 3 ])] + #[case([vec![ ], vec![1, 2 ]], [1, 2 ])] + #[case([vec![1 ], vec![1, 2 ]], [1, 1, 2 ])] + #[case([vec![1, 2 ], vec![1, 2 ]], [1, 1, 2, 2 ])] + #[case([vec![1, 2, 3], vec![1, 2 ]], [1, 1, 2, 2, 3 ])] + #[case([vec![ ], vec![1, 2, 3]], [1, 2, 3 ])] + #[case([vec![1 ], vec![1, 2, 3]], [1, 1, 2, 3 ])] + #[case([vec![1, 2 ], vec![1, 2, 3]], [1, 1, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![1, 2, 3]], [1, 1, 2, 2, 3, 3])] + fn multi_interleave_2(#[case] input: [Vec; 2], #[case] expected: [u8; M]) { assert_eq!(multi_interleave(input).collect::>(), expected); } #[rstest] - #[case([vec![] , vec![], vec![]], vec![])] - #[case([vec![1] , vec![], vec![]], vec![1])] - #[case([vec![1, 2] , vec![], vec![]], vec![1, 2])] - #[case([vec![1, 2, 3], vec![], vec![]], vec![1, 2, 3])] - #[case([vec![] , vec![1], vec![]], vec![1])] - #[case([vec![1] , vec![1], vec![]], vec![1, 1])] - #[case([vec![1, 2] , vec![1], vec![]], vec![1, 1, 2])] - #[case([vec![1, 2, 3], vec![1], vec![]], vec![1, 1, 2, 3])] - #[case([vec![] , vec![1, 2], vec![]], vec![1, 2])] - #[case([vec![1] , vec![1, 2], vec![]], vec![1, 1, 2])] - #[case([vec![1, 2] , vec![1, 2], vec![]], vec![1, 1, 2, 2])] - #[case([vec![1, 2, 3], vec![1, 2], vec![]], vec![1, 1, 2, 2, 3])] - #[case([vec![] , vec![1, 2, 3], vec![]], vec![1, 2, 3])] - #[case([vec![1] , vec![1, 2, 3], vec![]], vec![1, 1, 2, 3])] - #[case([vec![1, 2] , vec![1, 2, 3], vec![]], vec![1, 1, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![1, 2, 3], vec![]], vec![1, 1, 2, 2, 3, 3])] - #[case([vec![] , vec![], vec![1]], vec![1])] - #[case([vec![1] , vec![], vec![1]], vec![1, 1])] - #[case([vec![1, 2] , vec![], vec![1]], vec![1, 1, 2])] - #[case([vec![1, 2, 3], vec![], vec![1]], vec![1, 1, 2, 3])] - #[case([vec![] , vec![1], vec![1]], vec![1, 1])] - #[case([vec![1] , vec![1], vec![1]], vec![1, 1, 1])] - #[case([vec![1, 2] , vec![1], vec![1]], vec![1, 1, 1, 2])] - #[case([vec![1, 2, 3], vec![1], vec![1]], vec![1, 1, 1, 2, 3])] - #[case([vec![] , vec![1, 2], vec![1]], vec![1, 1, 2])] - #[case([vec![1] , vec![1, 2], vec![1]], vec![1, 1, 1, 2])] - #[case([vec![1, 2] , vec![1, 2], vec![1]], vec![1, 1, 1, 2, 2])] - #[case([vec![1, 2, 3], vec![1, 2], vec![1]], vec![1, 1, 1, 2, 2, 3])] - #[case([vec![] , vec![1, 2, 3], vec![1]], vec![1, 1, 2, 3])] - #[case([vec![1] , vec![1, 2, 3], vec![1]], vec![1, 1, 1, 2, 3])] - #[case([vec![1, 2] , vec![1, 2, 3], vec![1]], vec![1, 1, 1, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1]], vec![1, 1, 1, 2, 2, 3, 3])] - #[case([vec![] , vec![], vec![1, 2]], vec![1, 2])] - #[case([vec![1] , vec![], vec![1, 2]], vec![1, 1, 2])] - #[case([vec![1, 2] , vec![], vec![1, 2]], vec![1, 1, 2, 2])] - #[case([vec![1, 2, 3], vec![], vec![1, 2]], vec![1, 1, 2, 2, 3])] - #[case([vec![] , vec![1], vec![1, 2]], vec![1, 1, 2])] - #[case([vec![1] , vec![1], vec![1, 2]], vec![1, 1, 1, 2])] - #[case([vec![1, 2] , vec![1], vec![1, 2]], vec![1, 1, 1, 2, 2])] - #[case([vec![1, 2, 3], vec![1], vec![1, 2]], vec![1, 1, 1, 2, 2, 3])] - #[case([vec![] , vec![1, 2], vec![1, 2]], vec![1, 1, 2, 2])] - #[case([vec![1] , vec![1, 2], vec![1, 2]], vec![1, 1, 1, 2, 2])] - #[case([vec![1, 2] , vec![1, 2], vec![1, 2]], vec![1, 1, 1, 2, 2, 2])] - #[case([vec![1, 2, 3], vec![1, 2], vec![1, 2]], vec![1, 1, 1, 2, 2, 2, 3])] - #[case([vec![] , vec![1, 2, 3], vec![1, 2]], vec![1, 1, 2, 2, 3])] - #[case([vec![1] , vec![1, 2, 3], vec![1, 2]], vec![1, 1, 1, 2, 2, 3])] - #[case([vec![1, 2] , vec![1, 2, 3], vec![1, 2]], vec![1, 1, 1, 2, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1, 2]], vec![1, 1, 1, 2, 2, 2, 3, 3])] - #[case([vec![] , vec![], vec![1, 2, 3]], vec![1, 2, 3])] - #[case([vec![1] , vec![], vec![1, 2, 3]], vec![1, 1, 2, 3])] - #[case([vec![1, 2] , vec![], vec![1, 2, 3]], vec![1, 1, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![], vec![1, 2, 3]], vec![1, 1, 2, 2, 3, 3])] - #[case([vec![] , vec![1], vec![1, 2, 3]], vec![1, 1, 2, 3])] - #[case([vec![1] , vec![1], vec![1, 2, 3]], vec![1, 1, 1, 2, 3])] - #[case([vec![1, 2] , vec![1], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![1], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 3, 3])] - #[case([vec![] , vec![1, 2], vec![1, 2, 3]], vec![1, 1, 2, 2, 3])] - #[case([vec![1] , vec![1, 2], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 3])] - #[case([vec![1, 2] , vec![1, 2], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 2, 3])] - #[case([vec![1, 2, 3], vec![1, 2], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 2, 3, 3])] - #[case([vec![] , vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 2, 2, 3, 3])] - #[case([vec![1] , vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 3, 3])] - #[case([vec![1, 2] , vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 2, 3, 3])] - #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], vec![1, 1, 1, 2, 2, 2, 3, 3, 3])] - fn multi_interleave_3(#[case] input: [Vec; 3], #[case] expected: Vec) { + #[case([vec![ ], vec![ ], vec![ ]], [ ])] + #[case([vec![1 ], vec![ ], vec![ ]], [1 ])] + #[case([vec![1, 2 ], vec![ ], vec![ ]], [1, 2 ])] + #[case([vec![1, 2, 3], vec![ ], vec![ ]], [1, 2, 3 ])] + #[case([vec![ ], vec![1 ], vec![ ]], [1 ])] + #[case([vec![1 ], vec![1 ], vec![ ]], [1, 1 ])] + #[case([vec![1, 2 ], vec![1 ], vec![ ]], [1, 1, 2 ])] + #[case([vec![1, 2, 3], vec![1 ], vec![ ]], [1, 1, 2, 3 ])] + #[case([vec![ ], vec![1, 2 ], vec![ ]], [1, 2 ])] + #[case([vec![1 ], vec![1, 2 ], vec![ ]], [1, 1, 2 ])] + #[case([vec![1, 2 ], vec![1, 2 ], vec![ ]], [1, 1, 2, 2 ])] + #[case([vec![1, 2, 3], vec![1, 2 ], vec![ ]], [1, 1, 2, 2, 3 ])] + #[case([vec![ ], vec![1, 2, 3], vec![ ]], [1, 2, 3 ])] + #[case([vec![1 ], vec![1, 2, 3], vec![ ]], [1, 1, 2, 3 ])] + #[case([vec![1, 2 ], vec![1, 2, 3], vec![ ]], [1, 1, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![1, 2, 3], vec![ ]], [1, 1, 2, 2, 3, 3 ])] + #[case([vec![ ], vec![ ], vec![1 ]], [1 ])] + #[case([vec![1 ], vec![ ], vec![1 ]], [1, 1 ])] + #[case([vec![1, 2 ], vec![ ], vec![1 ]], [1, 1, 2 ])] + #[case([vec![1, 2, 3], vec![ ], vec![1 ]], [1, 1, 2, 3 ])] + #[case([vec![ ], vec![1 ], vec![1 ]], [1, 1 ])] + #[case([vec![1 ], vec![1 ], vec![1 ]], [1, 1, 1 ])] + #[case([vec![1, 2 ], vec![1 ], vec![1 ]], [1, 1, 1, 2 ])] + #[case([vec![1, 2, 3], vec![1 ], vec![1 ]], [1, 1, 1, 2, 3 ])] + #[case([vec![ ], vec![1, 2 ], vec![1 ]], [1, 1, 2 ])] + #[case([vec![1 ], vec![1, 2 ], vec![1 ]], [1, 1, 1, 2 ])] + #[case([vec![1, 2 ], vec![1, 2 ], vec![1 ]], [1, 1, 1, 2, 2 ])] + #[case([vec![1, 2, 3], vec![1, 2 ], vec![1 ]], [1, 1, 1, 2, 2, 3 ])] + #[case([vec![ ], vec![1, 2, 3], vec![1 ]], [1, 1, 2, 3 ])] + #[case([vec![1 ], vec![1, 2, 3], vec![1 ]], [1, 1, 1, 2, 3 ])] + #[case([vec![1, 2 ], vec![1, 2, 3], vec![1 ]], [1, 1, 1, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1 ]], [1, 1, 1, 2, 2, 3, 3 ])] + #[case([vec![ ], vec![ ], vec![1, 2 ]], [1, 2 ])] + #[case([vec![1 ], vec![ ], vec![1, 2 ]], [1, 1, 2 ])] + #[case([vec![1, 2 ], vec![ ], vec![1, 2 ]], [1, 1, 2, 2 ])] + #[case([vec![1, 2, 3], vec![ ], vec![1, 2 ]], [1, 1, 2, 2, 3 ])] + #[case([vec![ ], vec![1 ], vec![1, 2 ]], [1, 1, 2 ])] + #[case([vec![1 ], vec![1 ], vec![1, 2 ]], [1, 1, 1, 2 ])] + #[case([vec![1, 2 ], vec![1 ], vec![1, 2 ]], [1, 1, 1, 2, 2 ])] + #[case([vec![1, 2, 3], vec![1 ], vec![1, 2 ]], [1, 1, 1, 2, 2, 3 ])] + #[case([vec![ ], vec![1, 2 ], vec![1, 2 ]], [1, 1, 2, 2 ])] + #[case([vec![1 ], vec![1, 2 ], vec![1, 2 ]], [1, 1, 1, 2, 2 ])] + #[case([vec![1, 2 ], vec![1, 2 ], vec![1, 2 ]], [1, 1, 1, 2, 2, 2 ])] + #[case([vec![1, 2, 3], vec![1, 2 ], vec![1, 2 ]], [1, 1, 1, 2, 2, 2, 3 ])] + #[case([vec![ ], vec![1, 2, 3], vec![1, 2 ]], [1, 1, 2, 2, 3 ])] + #[case([vec![1 ], vec![1, 2, 3], vec![1, 2 ]], [1, 1, 1, 2, 2, 3 ])] + #[case([vec![1, 2 ], vec![1, 2, 3], vec![1, 2 ]], [1, 1, 1, 2, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1, 2 ]], [1, 1, 1, 2, 2, 2, 3, 3 ])] + #[case([vec![ ], vec![ ], vec![1, 2, 3]], [1, 2, 3 ])] + #[case([vec![1 ], vec![ ], vec![1, 2, 3]], [1, 1, 2, 3 ])] + #[case([vec![1, 2 ], vec![ ], vec![1, 2, 3]], [1, 1, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![ ], vec![1, 2, 3]], [1, 1, 2, 2, 3, 3 ])] + #[case([vec![ ], vec![1 ], vec![1, 2, 3]], [1, 1, 2, 3 ])] + #[case([vec![1 ], vec![1 ], vec![1, 2, 3]], [1, 1, 1, 2, 3 ])] + #[case([vec![1, 2 ], vec![1 ], vec![1, 2, 3]], [1, 1, 1, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![1 ], vec![1, 2, 3]], [1, 1, 1, 2, 2, 3, 3 ])] + #[case([vec![ ], vec![1, 2 ], vec![1, 2, 3]], [1, 1, 2, 2, 3 ])] + #[case([vec![1 ], vec![1, 2 ], vec![1, 2, 3]], [1, 1, 1, 2, 2, 3 ])] + #[case([vec![1, 2 ], vec![1, 2 ], vec![1, 2, 3]], [1, 1, 1, 2, 2, 2, 3 ])] + #[case([vec![1, 2, 3], vec![1, 2 ], vec![1, 2, 3]], [1, 1, 1, 2, 2, 2, 3, 3 ])] + #[case([vec![ ], vec![1, 2, 3], vec![1, 2, 3]], [1, 1, 2, 2, 3, 3 ])] + #[case([vec![1 ], vec![1, 2, 3], vec![1, 2, 3]], [1, 1, 1, 2, 2, 3, 3 ])] + #[case([vec![1, 2 ], vec![1, 2, 3], vec![1, 2, 3]], [1, 1, 1, 2, 2, 2, 3, 3 ])] + #[case([vec![1, 2, 3], vec![1, 2, 3], vec![1, 2, 3]], [1, 1, 1, 2, 2, 2, 3, 3, 3])] + fn multi_interleave_3(#[case] input: [Vec; 3], #[case] expected: [u8; M]) { assert_eq!(multi_interleave(input).collect::>(), expected); } } diff --git a/lyra_ext/src/lib.rs b/lyra_ext/src/lib.rs index f84827e..b773876 100644 --- a/lyra_ext/src/lib.rs +++ b/lyra_ext/src/lib.rs @@ -3,6 +3,9 @@ pub mod image; pub mod iter; pub mod logical_bind; pub mod nested_transpose; +pub mod num; pub mod pretty; pub mod rgb_hex; pub mod time; + +pub use time::{iso8601::iso8601 as iso8601_time, unix::unix as unix_time}; diff --git a/lyra_ext/src/num.rs b/lyra_ext/src/num.rs new file mode 100644 index 0000000..6de8787 --- /dev/null +++ b/lyra_ext/src/num.rs @@ -0,0 +1,13 @@ +#[inline] +#[must_use] +#[allow(clippy::cast_possible_truncation)] +pub const fn u64_to_i64_truncating(n: u64) -> i64 { + (n as i128) as i64 +} + +#[inline] +#[must_use] +#[allow(clippy::cast_possible_truncation)] +pub const fn usize_to_i64_truncating(n: usize) -> i64 { + (n as i128) as i64 +} diff --git a/lyra_ext/src/pretty/duration_display.rs b/lyra_ext/src/pretty/duration_display.rs index 95c29d1..d6bc9f1 100644 --- a/lyra_ext/src/pretty/duration_display.rs +++ b/lyra_ext/src/pretty/duration_display.rs @@ -22,33 +22,43 @@ fn timestamp_2() -> &'static Regex { }) } -pub struct PrettyDurationRefDisplayer<'a>(&'a Duration); +pub struct PrettyDurationDisplayer(u128); -pub trait PrettyDurationDisplay { - fn pretty_display(&self) -> PrettyDurationRefDisplayer; +pub trait DurationDisplay { + fn pretty_display(&self) -> PrettyDurationDisplayer; } -impl PrettyDurationDisplay for Duration { - fn pretty_display(&self) -> PrettyDurationRefDisplayer { - PrettyDurationRefDisplayer(self) +impl DurationDisplay for Duration { + fn pretty_display(&self) -> PrettyDurationDisplayer { + PrettyDurationDisplayer(self.as_millis()) } } -trait FromPrettyStr +impl DurationDisplay for u128 { + fn pretty_display(&self) -> PrettyDurationDisplayer { + PrettyDurationDisplayer(*self) + } +} + +pub struct FromPrettyStrError; + +pub trait FromPrettyStr where Self: Sized, { - fn from_pretty_str(value: &str) -> Result; + /// # Errors + /// if `value` doesn't match `timestamp` or `timestamp_2` regex + fn from_pretty_str(value: &str) -> Result; } impl FromPrettyStr for Duration { - fn from_pretty_str(value: &str) -> Result { + fn from_pretty_str(value: &str) -> Result { let captures = if let Some(captures) = timestamp().captures(value) { captures } else if let Some(captures) = timestamp_2().captures(value) { captures } else { - return Err(value); + return Err(FromPrettyStrError); }; let ms = captures @@ -75,23 +85,20 @@ impl FromPrettyStr for Duration { } } -fn fmt(millis: u128, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let divrem = |x, y| (x / y, x % y); - - let (s, ms) = divrem(millis, 1000); - let (m, s) = divrem(s, 60); - let (h, m) = divrem(m, 60); - - match (h, m, s) { - (0, 0, 0) => write!(f, "0:00.{ms:03}"), - (0, m, s) => write!(f, "{m}:{s:02}"), - (h, m, s) => write!(f, "{h}:{m:02}:{s:02}"), - } -} - -impl Display for PrettyDurationRefDisplayer<'_> { +impl Display for PrettyDurationDisplayer { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt(self.0.as_millis(), f) + let f: &mut std::fmt::Formatter<'_> = f; + let divrem = |x, y| (x / y, x % y); + + let (s, ms) = divrem(self.0, 1000); + let (m, s) = divrem(s, 60); + let (h, m) = divrem(m, 60); + + match (h, m, s) { + (0, 0, 0) => write!(f, "0:00.{ms:03}"), + (0, m, s) => write!(f, "{m}:{s:02}"), + (h, m, s) => write!(f, "{h}:{m:02}:{s:02}"), + } } } @@ -101,7 +108,7 @@ mod test { use rstest::rstest; - use super::{FromPrettyStr, PrettyDurationDisplay}; + use super::{DurationDisplay, FromPrettyStr}; #[rstest] #[case(Duration::ZERO, "0:00.000")] diff --git a/lyra_ext/src/pretty/flags_display.rs b/lyra_ext/src/pretty/flags_display.rs index d733aa2..8184835 100644 --- a/lyra_ext/src/pretty/flags_display.rs +++ b/lyra_ext/src/pretty/flags_display.rs @@ -28,13 +28,13 @@ where } flag }) - .collect::>() + .collect::>() .pretty_join_with_and(); f.write_str(&s) } } -pub trait PrettyFlagsDisplay: Flags { +pub trait FlagsDisplay: Flags { fn pretty_display(&self) -> PrettyFlagsDisplayer { PrettyFlagsDisplayer { inner: self, @@ -55,7 +55,7 @@ mod test { use bitflags::bitflags; use rstest::rstest; - use super::PrettyFlagsDisplay; + use super::FlagsDisplay; bitflags! { struct TestFlag: u8 { @@ -71,7 +71,7 @@ mod test { } } - impl PrettyFlagsDisplay for TestFlag {} + impl FlagsDisplay for TestFlag {} #[rstest] #[case(TestFlag::empty(), "")] @@ -103,7 +103,7 @@ mod test { } } - impl PrettyFlagsDisplay for TestFlag2 {} + impl FlagsDisplay for TestFlag2 {} #[rstest] #[case(TestFlag2::empty(), "")] diff --git a/lyra_ext/src/pretty/join.rs b/lyra_ext/src/pretty/join.rs index ccc3f81..ba03440 100644 --- a/lyra_ext/src/pretty/join.rs +++ b/lyra_ext/src/pretty/join.rs @@ -1,6 +1,6 @@ use std::borrow::Borrow; -pub trait PrettyJoin { +pub trait Join { type Joined; fn pretty_join(slice: &Self, sep: J, last_sep: J) -> Self::Joined; @@ -13,33 +13,33 @@ pub trait PrettyJoiner { fn and() -> Self::Joiner; fn or() -> Self::Joiner; - fn pretty_join(&self, sep: J, last_sep: J) -> >::Joined + fn pretty_join(&self, sep: J, last_sep: J) -> >::Joined where - Self: PrettyJoin, + Self: Join, { - PrettyJoin::pretty_join(self, sep, last_sep) + Join::pretty_join(self, sep, last_sep) } - fn pretty_join_with(&self, last_sep: Self::Joiner) -> >::Joined + fn pretty_join_with(&self, last_sep: Self::Joiner) -> >::Joined where - Self: PrettyJoin, + Self: Join, { - PrettyJoin::pretty_join(self, Self::sep(), last_sep) + Join::pretty_join(self, Self::sep(), last_sep) } - fn pretty_join_with_and(&self) -> >::Joined + fn pretty_join_with_and(&self) -> >::Joined where - Self: PrettyJoin, + Self: Join, { - PrettyJoin::pretty_join(self, Self::sep(), Self::and()) + Join::pretty_join(self, Self::sep(), Self::and()) } - fn pretty_join_with_or(&self) -> >::Joined + fn pretty_join_with_or(&self) -> >::Joined where - Self: PrettyJoin, + Self: Join, { - PrettyJoin::pretty_join(self, Self::sep(), Self::or()) + Join::pretty_join(self, Self::sep(), Self::or()) } } -impl> PrettyJoin<&str> for [S] { +impl> Join<&str> for [S] { type Joined = String; fn pretty_join(slice: &Self, sep: &str, last_sep: &str) -> Self::Joined { diff --git a/lyra_ext/src/rgb_hex.rs b/lyra_ext/src/rgb_hex.rs index 87db1e9..1077386 100644 --- a/lyra_ext/src/rgb_hex.rs +++ b/lyra_ext/src/rgb_hex.rs @@ -17,46 +17,46 @@ mod test { use rstest::rstest; #[rstest] - #[case([0, 0, 0], 0x00_00_00)] - #[case([255, 0, 0], 0xFF_00_00)] - #[case([0, 255, 0], 0x00_FF_00)] - #[case([255, 255, 0], 0xFF_FF_00)] - #[case([0, 0, 255], 0x00_00_FF)] - #[case([255, 0, 255], 0xFF_00_FF)] - #[case([0, 255, 255], 0x00_FF_FF)] + #[case([0 , 0 , 0 ], 0x00_00_00)] + #[case([255, 0 , 0 ], 0xFF_00_00)] + #[case([0 , 255, 0 ], 0x00_FF_00)] + #[case([255, 255, 0 ], 0xFF_FF_00)] + #[case([0 , 0 , 255], 0x00_00_FF)] + #[case([255, 0 , 255], 0xFF_00_FF)] + #[case([0 , 255, 255], 0x00_FF_FF)] #[case([255, 255, 255], 0xFF_FF_FF)] - #[case([123, 45, 67], 0x7B_2D_43)] - #[case([89, 101, 112], 0x59_65_70)] + #[case([123, 45 , 67 ], 0x7B_2D_43)] + #[case([89 , 101, 112], 0x59_65_70)] fn rgb_to_hex(#[case] input: [u8; 3], #[case] expected: u32) { assert_eq!(super::rgb_to_hex(input), expected); } #[rstest] - #[case(0x00_00_00, [0, 0, 0])] - #[case(0xFF_00_00, [255, 0, 0])] - #[case(0x00_FF_00, [0, 255, 0])] - #[case(0xFF_FF_00, [255, 255, 0])] - #[case(0x00_00_FF, [0, 0, 255])] - #[case(0xFF_00_FF, [255, 0, 255])] - #[case(0x00_FF_FF, [0, 255, 255])] + #[case(0x00_00_00, [0 , 0 , 0 ])] + #[case(0xFF_00_00, [255, 0 , 0 ])] + #[case(0x00_FF_00, [0 , 255, 0 ])] + #[case(0xFF_FF_00, [255, 255, 0 ])] + #[case(0x00_00_FF, [0 , 0 , 255])] + #[case(0xFF_00_FF, [255, 0 , 255])] + #[case(0x00_FF_FF, [0 , 255, 255])] #[case(0xFF_FF_FF, [255, 255, 255])] - #[case(0x7B_2D_43, [123, 45, 67])] - #[case(0x59_65_70, [89, 101, 112])] + #[case(0x7B_2D_43, [123, 45 , 67 ])] + #[case(0x59_65_70, [89 , 101, 112])] fn hex_to_rgb(#[case] input: u32, #[case] expected: [u8; 3]) { assert_eq!(super::hex_to_rgb(input), expected); } #[rstest] - #[case([0, 0, 0])] - #[case([255, 0, 0])] - #[case([0, 255, 0])] - #[case([255, 255, 0])] - #[case([0, 0, 255])] - #[case([255, 0, 255])] - #[case([0, 255, 255])] + #[case([0 , 0 , 0 ])] + #[case([255, 0 , 0 ])] + #[case([0 , 255, 0 ])] + #[case([255, 255, 0 ])] + #[case([0 , 0 , 255])] + #[case([255, 0 , 255])] + #[case([0 , 255, 255])] #[case([255, 255, 255])] - #[case([123, 45, 67])] - #[case([89, 101, 112])] + #[case([123, 45 , 67 ])] + #[case([89 , 101, 112])] fn rgb_to_hex_to_rgb(#[case] input: [u8; 3]) { assert_eq!(super::hex_to_rgb(super::rgb_to_hex(input)), input); } diff --git a/lyra_ext/src/time.rs b/lyra_ext/src/time.rs index ec05d24..4e68a94 100644 --- a/lyra_ext/src/time.rs +++ b/lyra_ext/src/time.rs @@ -1,2 +1,3 @@ -pub mod rfc3339; +pub mod iso8601; +pub mod track_timestamp; pub mod unix; diff --git a/lyra_ext/src/time/iso8601.rs b/lyra_ext/src/time/iso8601.rs new file mode 100644 index 0000000..56e8345 --- /dev/null +++ b/lyra_ext/src/time/iso8601.rs @@ -0,0 +1,10 @@ +use time::{format_description::well_known::Iso8601, OffsetDateTime}; + +/// # Panics +/// This function panics when writing ISO 8601 datetime to string fails +#[must_use] +pub fn iso8601() -> String { + OffsetDateTime::now_utc() + .format(&Iso8601::DEFAULT) + .expect("writing iso8601 datetime to string should never fail") +} diff --git a/lyra_ext/src/time/rfc3339.rs b/lyra_ext/src/time/rfc3339.rs deleted file mode 100644 index 8412eb8..0000000 --- a/lyra_ext/src/time/rfc3339.rs +++ /dev/null @@ -1,10 +0,0 @@ -use time::{format_description::well_known::Rfc3339, OffsetDateTime}; - -/// # Panics -/// This function panics when writing RFC 3339 datetime to string fails -#[must_use] -pub fn rfc3339_time() -> String { - OffsetDateTime::now_utc() - .format(&Rfc3339) - .expect("writing rfc3339 datetime to string should never fail") -} diff --git a/lyra_ext/src/time/track_timestamp.rs b/lyra_ext/src/time/track_timestamp.rs new file mode 100644 index 0000000..085cd9d --- /dev/null +++ b/lyra_ext/src/time/track_timestamp.rs @@ -0,0 +1,460 @@ +#[cfg(test)] +use mock_instant::thread_local::Instant; +#[cfg(not(test))] +use std::time::Instant; + +use std::time::Duration; + +#[derive(Debug, Copy, Clone)] +enum Operation { + Pause(bool), + Speed(f64), + Seek(Duration), +} + +pub struct Data { + most_recent_operation: Instant, + most_recent_position: Instant, + paused: bool, + speed: f64, +} + +impl Data { + #[must_use] + const fn new(started: Instant) -> Self { + Self { + most_recent_operation: started, + most_recent_position: started, + paused: false, + speed: 1.0, + } + } + + fn reset(&mut self, started: Instant) { + self.most_recent_operation = started; + self.most_recent_position = started; + self.paused = false; + self.speed = 1.0; + } +} + +pub struct TrackTimestamp { + started: Instant, + data: Data, + last_operation: Instant, +} + +impl TrackTimestamp { + #[must_use] + pub fn new() -> Self { + let started = Instant::now(); + Self { + started, + data: Data::new(started), + last_operation: started, + } + } + + pub fn reset(&mut self) { + let started = Instant::now(); + self.started = started; + self.data.reset(started); + self.last_operation = started; + } + + #[must_use] + pub fn get(&self) -> Duration { + let data = &self.data; + let most_recent_duration = data + .most_recent_position + .saturating_duration_since(self.started); + if data.paused { + return most_recent_duration; + } + let elapsed = Instant::now().saturating_duration_since(data.most_recent_operation); + most_recent_duration + elapsed.mul_f64(data.speed) + } + + #[must_use] + pub const fn paused(&self) -> bool { + self.data.paused + } + + fn apply(&mut self, op: Operation) { + let now = Instant::now(); + let last_operation = &mut self.last_operation; + let since_prev = now - *last_operation; + *last_operation = now; + + let data = &mut self.data; + let most_recent_position = &mut data.most_recent_position; + let paused = &mut data.paused; + let speed = &mut data.speed; + + match op { + Operation::Pause(p) => { + *paused = p; + if p { + *most_recent_position += since_prev.mul_f64(*speed); + } + } + Operation::Speed(m) => { + if !*paused { + *most_recent_position += since_prev.mul_f64(*speed); + } + *speed = m; + } + Operation::Seek(d) => { + *most_recent_position = self.started + d; + } + } + + data.most_recent_operation += since_prev; + } + + pub fn set_pause(&mut self, state: bool) { + if state == self.data.paused { + return; // this is no-op + } + + self.apply(Operation::Pause(state)); + } + + pub fn set_speed(&mut self, multiplier: f64) { + self.apply(Operation::Speed(multiplier)); + } + + pub fn seek_to(&mut self, timestamp: Duration) { + self.apply(Operation::Seek(timestamp)); + } + + #[inline] + pub fn resume(&mut self) { + self.set_pause(false); + } + + #[inline] + pub fn pause(&mut self) { + self.set_pause(true); + } + + #[inline] + pub fn seek_forward(&mut self, duration: Duration) { + self.seek_to(self.get() + duration.mul_f64(self.data.speed)); + } + + #[inline] + pub fn seek_backward(&mut self, duration: Duration) { + self.seek_to(self.get().saturating_sub(duration.mul_f64(self.data.speed))); + } +} + +impl Default for TrackTimestamp { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod test { + use std::time::Duration; + + use mock_instant::thread_local::MockClock; + use rstest::{fixture, rstest}; + + use super::TrackTimestamp; + + const SECS_0: Duration = Duration::ZERO; + const SEC: Duration = Duration::from_secs(1); + + #[fixture] + fn stamp() -> TrackTimestamp { + TrackTimestamp::new() + } + + #[rstest] + fn then_get(stamp: TrackTimestamp) { + MockClock::advance(SEC); + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + fn pause(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +1 [ignored] + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + fn pause_resume(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + stamp.resume(); + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + fn pause_then_resume(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +1 [ignored] + assert_eq!(stamp.get(), SEC); + + stamp.resume(); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), 2 * SEC); + } + + #[test] + fn seekf() { + MockClock::set_time(SEC); + let mut stamp = TrackTimestamp::new(); + + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.seek_forward(SEC); // +1 + assert_eq!(stamp.get(), 2 * SEC); + } + + #[test] + fn pause_seekf() { + MockClock::set_time(SEC); + let mut stamp = TrackTimestamp::new(); + + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + stamp.seek_forward(SEC); // +1 + assert_eq!(stamp.get(), 2 * SEC); + } + + #[test] + fn pause_then_seekf() { + MockClock::set_time(SEC); + let mut stamp = TrackTimestamp::new(); + + stamp.pause(); + assert_eq!(stamp.get(), SECS_0); + + MockClock::advance(SEC); // +1 [ignored] + assert_eq!(stamp.get(), SECS_0); + + stamp.seek_forward(SEC); // +1 + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + #[case(SEC, SEC)] + #[case(2 * SEC, SECS_0)] + #[case(3 * SEC, SECS_0)] + fn seekb(mut stamp: TrackTimestamp, #[case] input: Duration, #[case] expected: Duration) { + MockClock::advance(2 * SEC); // +2 + assert_eq!(stamp.get(), 2 * SEC); + + stamp.seek_backward(input); // -input, min 0 + assert_eq!(stamp.get(), expected); + } + + #[rstest] + #[case(SEC, SEC)] + #[case(2 * SEC, SECS_0)] + #[case(3 * SEC + SEC, SECS_0)] + fn pause_seekb(mut stamp: TrackTimestamp, #[case] input: Duration, #[case] expected: Duration) { + MockClock::advance(2 * SEC); // +2 + assert_eq!(stamp.get(), 2 * SEC); + + stamp.pause(); + assert_eq!(stamp.get(), 2 * SEC); + + stamp.seek_backward(input); // -input, min 0 + assert_eq!(stamp.get(), expected); + } + + #[rstest] + #[case(SEC, SEC)] + #[case(2 * SEC, SECS_0)] + #[case(3 * SEC + SEC, SECS_0)] + fn pause_then_seekb( + mut stamp: TrackTimestamp, + #[case] input: Duration, + #[case] expected: Duration, + ) { + MockClock::advance(2 * SEC); // +2 + assert_eq!(stamp.get(), 2 * SEC); + + stamp.pause(); + assert_eq!(stamp.get(), 2 * SEC); + + MockClock::advance(SEC); // +1 [ignored] + assert_eq!(stamp.get(), 2 * SEC); + + stamp.seek_backward(input); // -input, min 0 + assert_eq!(stamp.get(), expected); + } + + #[rstest] + fn speed(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +(1 x2) = +2 + assert_eq!(stamp.get(), 3 * SEC); + } + + #[rstest] + fn pause_speed(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + fn pause_then_speed(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // ignored + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + fn pause_speed_resume(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +(1 x2) [ignored] + assert_eq!(stamp.get(), SEC); + + stamp.resume(); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +(1 x2) = +2 + assert_eq!(stamp.get(), 3 * SEC); + } + + #[rstest] + fn speed_seekf(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + stamp.seek_forward(SEC); // +(1 x2) + assert_eq!(stamp.get(), 3 * SEC); + } + + #[rstest] + fn speed_seekf_pause(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + stamp.seek_forward(SEC); // +(1 x2) + assert_eq!(stamp.get(), 3 * SEC); + + stamp.pause(); + assert_eq!(stamp.get(), 3 * SEC); + + MockClock::advance(SEC); + assert_eq!(stamp.get(), 3 * SEC); + } + + #[rstest] + fn speed_seekb(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + stamp.seek_backward(SEC); // -(1 x2), min 0 + assert_eq!(stamp.get(), SECS_0); + } + + #[rstest] + fn speed_seekb_pause(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.set_speed(2.); + assert_eq!(stamp.get(), SEC); + + stamp.seek_backward(SEC); // -(1 x2), min 0 + assert_eq!(stamp.get(), SECS_0); + + stamp.pause(); + assert_eq!(stamp.get(), SECS_0); + + MockClock::advance(SEC); // +(1 x2) [ignored] + assert_eq!(stamp.get(), SECS_0); + } + + #[rstest] + fn pause_pause(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +1 [ignored] + assert_eq!(stamp.get(), SEC); + + stamp.pause(); + assert_eq!(stamp.get(), SEC); + + MockClock::advance(SEC); // +1 [ignored] + assert_eq!(stamp.get(), SEC); + } + + #[rstest] + fn resume(mut stamp: TrackTimestamp) { + MockClock::advance(SEC); // +1 + assert_eq!(stamp.get(), SEC); + + stamp.resume(); + assert_eq!(stamp.get(), SEC); + } +} diff --git a/lyra_ext/src/time/unix.rs b/lyra_ext/src/time/unix.rs index 6aa9f9e..132d2a4 100644 --- a/lyra_ext/src/time/unix.rs +++ b/lyra_ext/src/time/unix.rs @@ -1,8 +1,10 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; +/// # Panics +/// if system clock went backwards #[must_use] -pub fn unix_time() -> Duration { +pub fn unix() -> Duration { SystemTime::now() .duration_since(UNIX_EPOCH) - .unwrap_or_default() + .unwrap_or_else(|_| panic!("system clock went backwards")) } diff --git a/lyra_proc/Cargo.toml b/lyra_proc/Cargo.toml index 7940ec2..765947f 100644 --- a/lyra_proc/Cargo.toml +++ b/lyra_proc/Cargo.toml @@ -12,10 +12,11 @@ proc-macro = true unsafe_code = "forbid" [lints.clippy] -enum_glob_use = "deny" -pedantic = "deny" -nursery = "deny" -unwrap_used = "deny" +enum_glob_use = "forbid" +unwrap_used = "forbid" +try_err = "forbid" +pedantic = { level = "deny", priority = -1 } +nursery = { level = "deny", priority = -1 } [dependencies] syn = "2" diff --git a/lyra_proc/src/command.rs b/lyra_proc/src/command.rs index 4c58e1a..85f1eb4 100644 --- a/lyra_proc/src/command.rs +++ b/lyra_proc/src/command.rs @@ -18,7 +18,7 @@ fn unwrap(type_path: &TypePath, from: impl Into) -> Option<&Path> { } } -fn process( +fn declare_commands( fields: &FieldsUnnamed, v: &Variant, c: (QuoteTokenStream, QuoteTokenStream), @@ -69,7 +69,7 @@ fn process( } } -pub fn impl_lyra_command_group(input: &DeriveInput) -> TokenStream { +pub fn impl_bot_command_group(input: &DeriveInput) -> TokenStream { let name = &input.ident; let data = &input.data; @@ -77,8 +77,8 @@ pub fn impl_lyra_command_group(input: &DeriveInput) -> TokenStream { Data::Enum(data) => { data.variants .iter() - .fold((quote! {}, quote! {}), |c, v| match v.fields { - Fields::Unnamed(ref fields) => process(fields, v, c, name), + .fold((quote!(), quote!()), |c, v| match v.fields { + Fields::Unnamed(ref fields) => declare_commands(fields, v, c, name), _ => panic!("all fields must be unnamed"), }) } @@ -104,3 +104,54 @@ pub fn impl_lyra_command_group(input: &DeriveInput) -> TokenStream { } .into() } + +fn declare_autocompletes( + fields: &FieldsUnnamed, + v: &Variant, + sub_autocomplete_match: &QuoteTokenStream, +) -> QuoteTokenStream { + let sub_cmd = fields + .unnamed + .first() + .expect("variant must have exactly one unnamed field"); + let v_ident = &v.ident; + match sub_cmd.ty { + Type::Path(_) => { + quote! { + #sub_autocomplete_match + Self::#v_ident(sub_cmd) => sub_cmd.execute(ctx).await, + } + } + _ => panic!("the field must be a path"), + } +} + +pub fn impl_bot_autocomplete_group(input: &DeriveInput) -> TokenStream { + let name = &input.ident; + let data = &input.data; + + let sub_autocomplete_matches = match data { + Data::Enum(data) => data.variants.iter().fold(quote!(), |c, v| match v.fields { + Fields::Unnamed(ref fields) => declare_autocompletes(fields, v, &c), + _ => panic!("all fields must be unnamed"), + }), + _ => panic!("this can only be derived from an enum"), + }; + + let bot_autocomplete_path = + syn::parse_str::("crate::command::model::BotAutocomplete").expect("path is valid"); + let autocomplete_ctx_path = + syn::parse_str::("crate::command::model::AutocompleteCtx").expect("path is valid"); + let result_path = + syn::parse_str::("crate::error::command::AutocompleteResult").expect("path is valid"); + quote! { + impl #bot_autocomplete_path for #name { + async fn execute(self, ctx: #autocomplete_ctx_path) -> #result_path { + match self { + #sub_autocomplete_matches + } + } + } + } + .into() +} diff --git a/lyra_proc/src/lib.rs b/lyra_proc/src/lib.rs index cf664c5..0ff7e10 100644 --- a/lyra_proc/src/lib.rs +++ b/lyra_proc/src/lib.rs @@ -11,7 +11,14 @@ use proc_macro::TokenStream; pub fn bot_command_group(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as syn::DeriveInput); - command::impl_lyra_command_group(&input) + command::impl_bot_command_group(&input) +} + +#[proc_macro_derive(BotAutocompleteGroup)] +pub fn bot_autocomplete_group(input: TokenStream) -> TokenStream { + let input = syn::parse_macro_input!(input as syn::DeriveInput); + + command::impl_bot_autocomplete_group(&input) } #[proc_macro] diff --git a/scripts/get-lavalink b/scripts/get-lavalink new file mode 100755 index 0000000..22b1898 --- /dev/null +++ b/scripts/get-lavalink @@ -0,0 +1,12 @@ +#!/usr/bin/env sh + +LAVALINK_DIR="$PWD/lavalink" + +URL="https://github.com/lavalink-devs/Lavalink/releases/download/4.0.6/Lavalink.jar" +FILE="${LAVALINK_DIR}/Lavalink.jar" +if [ -f $FILE ]; then + echo "File $FILE exists." +else + curl -o $FILE -L $URL +fi + From 0a905d29e026ecc9a14ba2d5a72c4684e2742a18 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Mon, 29 Jul 2024 03:06:31 +0700 Subject: [PATCH 03/12] Utilise `LazyLock` & Update Lavalink --- lavalink/application.yml | 4 +- lyra/src/command/declare.rs | 67 +++++++-------- lyra/src/command/util.rs | 4 +- lyra/src/component/connection.rs | 2 +- lyra/src/component/queue.rs | 4 +- lyra/src/component/queue/play.rs | 2 +- lyra/src/core/const.rs | 103 +++++++++--------------- lyra/src/core/model/interaction.rs | 6 +- lyra/src/lavalink/model.rs | 2 +- lyra/src/lavalink/model/connection.rs | 4 +- lyra/src/lavalink/model/queue.rs | 2 +- lyra/src/runner.rs | 4 +- lyra_ext/src/pretty/duration_display.rs | 35 ++++---- scripts/get-lavalink | 2 +- 14 files changed, 99 insertions(+), 142 deletions(-) diff --git a/lavalink/application.yml b/lavalink/application.yml index 699647e..7091a9d 100644 --- a/lavalink/application.yml +++ b/lavalink/application.yml @@ -29,11 +29,11 @@ plugins: lavalink: plugins: - - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.1.1" + - dependency: "com.github.topi314.lavasrc:lavasrc-plugin:4.2.0" snapshot: false - dependency: "com.github.topi314.lavasearch:lavasearch-plugin:1.0.0" snapshot: false - - dependency: "dev.lavalink.youtube:youtube-plugin:1.3.0" + - dependency: "dev.lavalink.youtube:youtube-plugin:1.5.0" snapshot: false server: sources: diff --git a/lyra/src/command/declare.rs b/lyra/src/command/declare.rs index 1f68de8..6881949 100644 --- a/lyra/src/command/declare.rs +++ b/lyra/src/command/declare.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, sync::OnceLock}; +use std::{ + collections::HashMap, + sync::{LazyLock, OnceLock}, +}; use twilight_interactions::command::{CommandModel, CreateCommand}; use twilight_model::application::{ @@ -38,28 +41,21 @@ macro_rules! declare_slash_commands { } } - fn slash_commands_map() -> &'static SlashCommandMap { - static SLASH_COMMANDS_MAP: OnceLock = OnceLock::new(); - SLASH_COMMANDS_MAP.get_or_init(|| { - ::paste::paste! { - SlashCommandMap { - $([<_ $raw_cmd:snake>]: <$raw_cmd>::create_command().into(),)* - } + static SLASH_COMMANDS_MAP: LazyLock = LazyLock::new(|| { + ::paste::paste! { + SlashCommandMap { + $([<_ $raw_cmd:snake>]: <$raw_cmd>::create_command().into(),)* } - }) - } + } + }); type SlashCommands = [Command; count!($($raw_cmd)*)]; - pub fn slash_commands() -> &'static SlashCommands { - static SLASH_COMMANDS: OnceLock = OnceLock::new(); - SLASH_COMMANDS.get_or_init(|| { - let map = slash_commands_map(); - ::paste::paste! { - [$(map.[<_ $raw_cmd:snake>].clone(),)*] - } - }) - } + pub static SLASH_COMMANDS: LazyLock = LazyLock::new(|| { + ::paste::paste! { + [$(SLASH_COMMANDS_MAP.[<_ $raw_cmd:snake>].clone(),)*] + } + }); pub static POPULATED_COMMANDS_MAP: OnceLock, Command>> = OnceLock::new(); @@ -67,7 +63,7 @@ macro_rules! declare_slash_commands { impl CommandInfoAware for $raw_cmd { fn name() -> &'static str { ::paste::paste! { - &slash_commands_map().[<_ $raw_cmd:snake>].name + &SLASH_COMMANDS_MAP.[<_ $raw_cmd:snake>].name } } } @@ -101,34 +97,27 @@ macro_rules! declare_message_commands { } } - fn message_commands_map() -> &'static MessageCommandMap { - static MESSAGE_COMMANDS_MAP: OnceLock = OnceLock::new(); - MESSAGE_COMMANDS_MAP.get_or_init(|| { - ::paste::paste! { - MessageCommandMap { - $([<_ $raw_cmd:snake>]: <$raw_cmd>::create_command().into(),)* - } + static MESSAGE_COMMANDS_MAP: LazyLock = LazyLock::new(|| { + ::paste::paste! { + MessageCommandMap { + $([<_ $raw_cmd:snake>]: <$raw_cmd>::create_command().into(),)* } - }) - } + } + }); type MessageCommands = [Command; count!($($raw_cmd)*)]; - pub fn message_commands() -> &'static MessageCommands { - static MESSAGE_COMMANDS: OnceLock = OnceLock::new(); - MESSAGE_COMMANDS.get_or_init(|| { - let map = message_commands_map(); - ::paste::paste! { - [$(map.[<_ $raw_cmd:snake>].clone(),)*] - } - }) - } + pub static MESSAGE_COMMANDS: LazyLock = LazyLock::new(|| { + ::paste::paste! { + [$(MESSAGE_COMMANDS_MAP.[<_ $raw_cmd:snake>].clone(),)*] + } + }); $( impl CommandInfoAware for $raw_cmd { fn name() -> &'static str { ::paste::paste! { - &message_commands_map().[<_ $raw_cmd:snake>].name + &MESSAGE_COMMANDS_MAP.[<_ $raw_cmd:snake>].name } } } diff --git a/lyra/src/command/util.rs b/lyra/src/command/util.rs index 9fbd2cb..4240c4a 100644 --- a/lyra/src/command/util.rs +++ b/lyra/src/command/util.rs @@ -265,7 +265,7 @@ async fn handle_suppressed_auto_join( ); let requested_to_speak_message = requested_to_speak.model().await?; let wait_for_speaker = tokio::time::timeout( - *r#const::misc::wait_for_bot_events_timeout(), + *r#const::misc::WAIT_FOR_BOT_EVENTS_TIMEOUT, wait_for_speaker, ); @@ -323,7 +323,7 @@ pub async fn prompt_for_confirmation( }); let wait_for_modal_submit = tokio::time::timeout( - *r#const::misc::destructive_command_confirmation_timeout(), + *r#const::misc::DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT, wait_for_modal_submit, ) .await; diff --git a/lyra/src/component/connection.rs b/lyra/src/component/connection.rs index aec14d9..163fa0d 100644 --- a/lyra/src/component/connection.rs +++ b/lyra/src/component/connection.rs @@ -111,7 +111,7 @@ async fn start_inactivity_timeout( ); for _ in 0..const_connection::INACTIVITY_TIMEOUT_POLL_N { - tokio::time::sleep(*const_connection::inactivity_timeout_poll_interval()).await; + tokio::time::sleep(*const_connection::INACTIVITY_TIMEOUT_POLL_INTERVAL).await; if users_in_voice(&ctx, channel_id).is_some_and(|n| n >= 1) { return Ok(()); } diff --git a/lyra/src/component/queue.rs b/lyra/src/component/queue.rs index f6bdf6b..c8f1bea 100644 --- a/lyra/src/component/queue.rs +++ b/lyra/src/component/queue.rs @@ -39,7 +39,7 @@ use crate::{ core::{ model::{CacheAware, InteractionClient}, r#const::{ - discord::COMMAND_CHOICES_LIMIT, misc::ADD_TRACKS_WRAP_LIMIT, text::fuzzy_matcher, + discord::COMMAND_CHOICES_LIMIT, misc::ADD_TRACKS_WRAP_LIMIT, text::FUZZY_MATCHER, }, }, error::{component::queue::RemoveTracksError, PositionOutOfRange as PositionOutOfRangeError}, @@ -156,7 +156,7 @@ pub fn generate_position_choices_from_fuzzy_match<'a>( Some(( p, t, - fuzzy_matcher().fuzzy_match(&format!("{requester} {author} {title}",), focused)?, + FUZZY_MATCHER.fuzzy_match(&format!("{requester} {author} {title}",), focused)?, )) }) .sorted_by_key(|(_, _, s)| -s) diff --git a/lyra/src/component/queue/play.rs b/lyra/src/component/queue/play.rs index 62e1fd6..957c94c 100644 --- a/lyra/src/component/queue/play.rs +++ b/lyra/src/component/queue/play.rs @@ -238,7 +238,7 @@ impl BotAutocomplete for Autocomplete { }) .map(|q| { let source = self.source.unwrap_or_default(); - (!regex::url().is_match(&q)) + (!regex::URL.is_match(&q)) .then(|| format!("{}{}", source.value(), q).into_boxed_str()) .unwrap_or_else(|| q.into_boxed_str()) }) diff --git a/lyra/src/core/const.rs b/lyra/src/core/const.rs index 8c24a54..731f1a1 100644 --- a/lyra/src/core/const.rs +++ b/lyra/src/core/const.rs @@ -1,5 +1,5 @@ pub mod metadata { - use std::sync::OnceLock; + use std::sync::LazyLock; const VERSION: &str = env!("CARGO_PKG_VERSION"); const COPYRIGHT: &str = env!("CARGO_PKG_LICENSE"); @@ -54,73 +54,56 @@ pub mod metadata { "%cargo_opt_level", ]; - pub fn banner() -> &'static str { - static BANNER: OnceLock<&'static str> = OnceLock::new(); - BANNER.get_or_init(|| { - use aho_corasick::AhoCorasick; - - let rdr = include_str!("../../../assets/lyra2-ascii.ans"); - let mut wtr = Vec::new(); - - let ac = AhoCorasick::new(METADATA_PATTERNS).expect("METADATA_PATTERNS is valid"); - ac.try_stream_replace_all(rdr.as_bytes(), &mut wtr, &METADATA_REPLACEMENTS) - .expect("searching is infallible"); - // SAFETY: since `rdr` is utf-8, `wtr` must also be utf-8 - unsafe { String::from_utf8_unchecked(wtr).leak() } - }) - } + pub static BANNER: LazyLock<&'static str> = LazyLock::new(|| { + use aho_corasick::AhoCorasick; + + let rdr = include_str!("../../../assets/lyra2-ascii.ans"); + let mut wtr = Vec::new(); + + let ac = AhoCorasick::new(METADATA_PATTERNS).expect("METADATA_PATTERNS is valid"); + ac.try_stream_replace_all(rdr.as_bytes(), &mut wtr, &METADATA_REPLACEMENTS) + .expect("searching is infallible"); + // SAFETY: since `rdr` is utf-8, `wtr` must also be utf-8 + unsafe { String::from_utf8_unchecked(wtr).leak() } + }); } pub mod connection { - use std::{sync::OnceLock, time::Duration}; + use std::{sync::LazyLock, time::Duration}; pub const INACTIVITY_TIMEOUT_SECS: u16 = 600; pub const INACTIVITY_TIMEOUT_POLL_N: u8 = 10; - pub fn changed_timeout() -> &'static Duration { - static CHANGED_TIMEOUT: OnceLock = OnceLock::new(); - CHANGED_TIMEOUT.get_or_init(|| Duration::from_millis(250)) - } - - pub fn get_lavalink_connection_info_timeout() -> &'static Duration { - static GET_LAVALINK_CONNECTION_INFO_TIMEOUT: OnceLock = OnceLock::new(); - GET_LAVALINK_CONNECTION_INFO_TIMEOUT.get_or_init(|| Duration::from_millis(2_000)) - } - - pub fn inactivity_timeout_poll_interval() -> &'static Duration { - static INACTIVITY_TIMEOUT_POLL_INTERVAL: OnceLock = OnceLock::new(); - INACTIVITY_TIMEOUT_POLL_INTERVAL.get_or_init(|| { - Duration::from_secs( - u64::from(INACTIVITY_TIMEOUT_SECS) / u64::from(INACTIVITY_TIMEOUT_POLL_N), - ) - }) - } + pub static CHANGED_TIMEOUT: LazyLock = LazyLock::new(|| Duration::from_millis(250)); + + pub static GET_LAVALINK_CONNECTION_INFO_TIMEOUT: LazyLock = + LazyLock::new(|| Duration::from_millis(2_000)); + + pub static INACTIVITY_TIMEOUT_POLL_INTERVAL: LazyLock = LazyLock::new(|| { + Duration::from_secs( + u64::from(INACTIVITY_TIMEOUT_SECS) / u64::from(INACTIVITY_TIMEOUT_POLL_N), + ) + }); } pub mod misc { - use std::{sync::OnceLock, time::Duration}; + use std::{sync::LazyLock, time::Duration}; pub const ADD_TRACKS_WRAP_LIMIT: usize = 3; pub const WAIT_FOR_NOT_SUPPRESSED_TIMEOUT_SECS: u8 = 30; - pub fn wait_for_bot_events_timeout() -> &'static Duration { - static WAIT_FOR_BOT_EVENTS_TIMEOUT: OnceLock = OnceLock::new(); - WAIT_FOR_BOT_EVENTS_TIMEOUT.get_or_init(|| Duration::from_millis(1_000)) - } + pub static WAIT_FOR_BOT_EVENTS_TIMEOUT: LazyLock = + LazyLock::new(|| Duration::from_millis(1_000)); - pub fn destructive_command_confirmation_timeout() -> &'static Duration { - static DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT: OnceLock = OnceLock::new(); - DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT.get_or_init(|| Duration::from_secs(60)) - } + pub static DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT: LazyLock = + LazyLock::new(|| Duration::from_secs(60)); - pub fn queue_advance_locked_timeout() -> &'static Duration { - static QUEUE_ADVANCE_LOCKED_TIMEOUT: OnceLock = OnceLock::new(); - QUEUE_ADVANCE_LOCKED_TIMEOUT.get_or_init(|| Duration::from_millis(250)) - } + pub static QUEUE_ADVANCE_LOCKED_TIMEOUT: LazyLock = + LazyLock::new(|| Duration::from_millis(250)); } pub mod text { - use std::sync::OnceLock; + use std::sync::LazyLock; use fuzzy_matcher::skim::SkimMatcherV2; @@ -130,26 +113,20 @@ pub mod text { pub const EMPTY_EMBED_FIELD: &str = "`-Empty-`"; pub const NO_ROWS_AFFECTED_MESSAGE: &str = "๐Ÿ” No changes were made."; - pub fn fuzzy_matcher() -> &'static SkimMatcherV2 { - static FUZZY_MATCHER: OnceLock = OnceLock::new(); - FUZZY_MATCHER.get_or_init(SkimMatcherV2::default) - } + pub static FUZZY_MATCHER: LazyLock = LazyLock::new(SkimMatcherV2::default); } pub mod regex { - use std::sync::OnceLock; + use std::sync::LazyLock; use regex::Regex; - pub fn url() -> &'static Regex { - static URL: OnceLock = OnceLock::new(); - URL.get_or_init(|| { - Regex::new( - r"(https://www\.|http://www\.|https://|http://)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?/[a-zA-Z0-9]{2,}|((https://www\.|http://www\.|https://|http://)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https://www\.|http://www\.|https://|http://)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?" - ) - .expect("regex is valid") - }) - } + pub static URL: LazyLock = LazyLock::new(|| { + Regex::new( + r"(https://www\.|http://www\.|https://|http://)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?/[a-zA-Z0-9]{2,}|((https://www\.|http://www\.|https://|http://)?[a-zA-Z]{2,}(\.[a-zA-Z]{2,})(\.[a-zA-Z]{2,})?)|(https://www\.|http://www\.|https://|http://)?[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}(\.[a-zA-Z0-9]{2,})?" + ) + .expect("regex is valid") + }); } pub mod exit_code { diff --git a/lyra/src/core/model/interaction.rs b/lyra/src/core/model/interaction.rs index 9031d08..6d40d90 100644 --- a/lyra/src/core/model/interaction.rs +++ b/lyra/src/core/model/interaction.rs @@ -17,7 +17,7 @@ use twilight_util::builder::InteractionResponseDataBuilder; use crate::{ command::{ - declare::{message_commands, slash_commands, POPULATED_COMMANDS_MAP}, + declare::{MESSAGE_COMMANDS, POPULATED_COMMANDS_MAP, SLASH_COMMANDS}, model::CommandInfoAware, }, error::core::{FollowupResult, RegisterGlobalCommandsError, RespondResult}, @@ -220,9 +220,7 @@ impl<'a> Client<'a> { pub async fn register_global_commands(&self) -> Result<(), RegisterGlobalCommandsError> { let commands = self .0 - .set_global_commands( - &[slash_commands().as_slice(), message_commands().as_slice()].concat(), - ) + .set_global_commands(&[SLASH_COMMANDS.as_slice(), MESSAGE_COMMANDS.as_slice()].concat()) .await? .models() .await?; diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index 0babf09..2a5ae36 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -261,7 +261,7 @@ pub trait DelegateMethods { let info = self .get_connection_info( guild_id, - *r#const::connection::get_lavalink_connection_info_timeout(), + *r#const::connection::GET_LAVALINK_CONNECTION_INFO_TIMEOUT, ) .await?; tracing::trace!("getting lavalink connection info took {:?}", now.elapsed()); diff --git a/lyra/src/lavalink/model/connection.rs b/lyra/src/lavalink/model/connection.rs index ab2c6d4..590c6d8 100644 --- a/lyra/src/lavalink/model/connection.rs +++ b/lyra/src/lavalink/model/connection.rs @@ -35,7 +35,7 @@ impl Connection { pub async fn changed(&self) -> bool { tracing::trace!("waiting for connection change notification"); - let duration = *r#const::connection::changed_timeout(); + let duration = *r#const::connection::CHANGED_TIMEOUT; let future = self.change.notified(); tokio::time::timeout(duration, future).await.is_ok() } @@ -98,7 +98,7 @@ pub async fn wait_for_with( rx: &mut broadcast::Receiver, predicate: impl Fn(&Event) -> bool + Send + Sync, ) -> EventRecvResult> { - let event = tokio::time::timeout(*r#const::misc::wait_for_bot_events_timeout(), async { + let event = tokio::time::timeout(*r#const::misc::WAIT_FOR_BOT_EVENTS_TIMEOUT, async { loop { let event = rx.recv().await?; if predicate(&event) { diff --git a/lyra/src/lavalink/model/queue.rs b/lyra/src/lavalink/model/queue.rs index bfc4e41..b114368 100644 --- a/lyra/src/lavalink/model/queue.rs +++ b/lyra/src/lavalink/model/queue.rs @@ -229,7 +229,7 @@ impl Queue { pub async fn not_advance_locked(&self) -> bool { let future = self.advance_lock.notified(); - let duration = *crate::core::r#const::misc::queue_advance_locked_timeout(); + let duration = *crate::core::r#const::misc::QUEUE_ADVANCE_LOCKED_TIMEOUT; tokio::time::timeout(duration, future).await.is_err() } diff --git a/lyra/src/runner.rs b/lyra/src/runner.rs index 5e51320..2b577ca 100644 --- a/lyra/src/runner.rs +++ b/lyra/src/runner.rs @@ -29,7 +29,7 @@ use twilight_model::{ id::{marker::UserMarker, Id}, }; -use crate::{core::r#const::metadata::banner, lavalink::handlers, LavalinkAware}; +use crate::{core::r#const::metadata::BANNER, lavalink::handlers, LavalinkAware}; use super::{ core::{ @@ -107,7 +107,7 @@ pub async fn start() -> Result<(), StartError> { tasks.push(tokio::spawn(handle_gateway_events(shard, bot.clone()))); } - println!("{}", banner()); + println!("{}", *BANNER); Ok(wait_until_shutdown(senders, tasks).await?) } diff --git a/lyra_ext/src/pretty/duration_display.rs b/lyra_ext/src/pretty/duration_display.rs index d6bc9f1..06cd45f 100644 --- a/lyra_ext/src/pretty/duration_display.rs +++ b/lyra_ext/src/pretty/duration_display.rs @@ -1,26 +1,19 @@ -use std::{fmt::Display, sync::OnceLock, time::Duration}; +use std::{fmt::Display, sync::LazyLock, time::Duration}; use regex::Regex; -fn timestamp() -> &'static Regex { - static TIMESTAMP: OnceLock = OnceLock::new(); - TIMESTAMP.get_or_init(|| { - Regex::new( - r"^(((?[1-9]\d*):(?[0-5]\d))|(?[0-5]?\d)):(?[0-5]\d)(\.(?\d{3}))?$", - ) - .expect("regex is valid") - }) -} +static TIMESTAMP: LazyLock = LazyLock::new(|| { + Regex::new( + r"^(((?[1-9]\d*):(?[0-5]\d))|(?[0-5]?\d)):(?[0-5]\d)(\.(?\d{3}))?$", + ) + .expect("regex is valid") +}); -fn timestamp_2() -> &'static Regex { - static TIMESTAMP_2: OnceLock = OnceLock::new(); - TIMESTAMP_2.get_or_init(|| { - Regex::new( - r"^((?[1-9]\d*)\s?hr?)?\s*((?[1-9]|[1-5]\d)\s?m(in)?)?\s*((?[1-9]|[1-5]\d)\s?s(ec)?)?\s*((?[1-9]\d{0,2})\s?ms(ec)?)?$" - ) - .expect("regex is valid") - }) -} +static TIMESTAMP_2: LazyLock = LazyLock::new(|| { + Regex::new( + r"^((?[1-9]\d*)\s?hr?)?\s*((?[1-9]|[1-5]\d)\s?m(in)?)?\s*((?[1-9]|[1-5]\d)\s?s(ec)?)?\s*((?[1-9]\d{0,2})\s?ms(ec)?)?$" + ).expect("regex is valid") +}); pub struct PrettyDurationDisplayer(u128); @@ -53,9 +46,9 @@ where impl FromPrettyStr for Duration { fn from_pretty_str(value: &str) -> Result { - let captures = if let Some(captures) = timestamp().captures(value) { + let captures = if let Some(captures) = TIMESTAMP.captures(value) { captures - } else if let Some(captures) = timestamp_2().captures(value) { + } else if let Some(captures) = TIMESTAMP_2.captures(value) { captures } else { return Err(FromPrettyStrError); diff --git a/scripts/get-lavalink b/scripts/get-lavalink index 22b1898..4226d02 100755 --- a/scripts/get-lavalink +++ b/scripts/get-lavalink @@ -2,7 +2,7 @@ LAVALINK_DIR="$PWD/lavalink" -URL="https://github.com/lavalink-devs/Lavalink/releases/download/4.0.6/Lavalink.jar" +URL="https://github.com/lavalink-devs/Lavalink/releases/download/4.0.7/Lavalink.jar" FILE="${LAVALINK_DIR}/Lavalink.jar" if [ -f $FILE ]; then echo "File $FILE exists." From 1e17e3caabd4305be2a6fe85ebf40135f289d6cb Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:35:12 +0700 Subject: [PATCH 04/12] Update dependencies --- Cargo.lock | 655 ++++++++++++++------------------------------ flake.lock | 18 +- lyra/Cargo.toml | 14 +- lyra_ext/Cargo.toml | 2 +- 4 files changed, 224 insertions(+), 465 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b3fb11..f378ae5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,9 +142,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -187,9 +187,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" +checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" [[package]] name = "byteorder" @@ -205,15 +205,15 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "camino" -version = "1.1.7" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" dependencies = [ "serde", ] @@ -243,13 +243,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.99" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] @@ -346,15 +346,15 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -448,7 +448,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -459,7 +459,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -533,7 +533,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -543,7 +543,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -558,17 +558,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "displaydoc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "dotenvy" version = "0.15.7" @@ -589,9 +578,9 @@ dependencies = [ [[package]] name = "either" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] @@ -667,9 +656,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "libz-sys", @@ -684,7 +673,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -769,7 +758,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -872,7 +861,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "libc", "libgit2-sys", "log", @@ -981,9 +970,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1004,15 +993,15 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.3" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1056,7 +1045,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.10", + "rustls 0.23.12", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -1066,9 +1055,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", "futures-channel", @@ -1084,124 +1073,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -1210,14 +1081,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "icu_normalizer", - "icu_properties", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -1245,9 +1114,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -1270,9 +1139,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] @@ -1297,8 +1166,9 @@ dependencies = [ [[package]] name = "lavalink-rs" -version = "0.12.0" -source = "git+https://gitlab.com/vicky5124/lavalink-rs?rev=56fd9191#56fd919119c7ae0ce3172012578ce8afbf79513c" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded9801aae30b05b30dc854b0b9a3e96f73626c4e73a7b13a2ee8c4740edb25" dependencies = [ "arc-swap", "bytes", @@ -1330,23 +1200,23 @@ checksum = "426ef2204d4fa6806fa6a00fdd747f7312645fcd30cf9208a5bdfeec6f3b9a41" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin", ] [[package]] name = "libc" -version = "0.2.155" +version = "0.2.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" [[package]] name = "libgit2-sys" @@ -1379,9 +1249,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.18" +version = "1.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" +checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" dependencies = [ "cc", "libc", @@ -1404,12 +1274,6 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" -[[package]] -name = "litemap" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" - [[package]] name = "lock_api" version = "0.4.12" @@ -1432,7 +1296,7 @@ version = "0.8.0" dependencies = [ "aho-corasick", "anyhow", - "bitflags 2.5.0", + "bitflags 2.6.0", "color-eyre", "const-str", "const_panic", @@ -1476,7 +1340,7 @@ dependencies = [ name = "lyra_ext" version = "0.8.0" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "const-str", "heck", "image", @@ -1498,7 +1362,7 @@ dependencies = [ "itertools", "quote", "serde", - "syn 2.0.66", + "syn 2.0.75", "toml", ] @@ -1535,9 +1399,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", "simd-adler32", @@ -1545,9 +1409,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ "hermit-abi", "libc", @@ -1729,7 +1593,7 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -1756,9 +1620,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall 0.5.3", "smallvec", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -1799,7 +1663,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -1862,9 +1726,12 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" @@ -1901,9 +1768,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1978,18 +1845,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -2045,7 +1912,7 @@ dependencies = [ "cfg-if", "getrandom", "libc", - "spin 0.9.8", + "spin", "untrusted", "windows-sys 0.52.0", ] @@ -2072,9 +1939,9 @@ dependencies = [ [[package]] name = "rstest" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" +checksum = "7b423f0e62bdd61734b67cd21ff50871dfaeb9cc74f869dcd6af974fbcb19936" dependencies = [ "futures", "futures-timer", @@ -2084,9 +1951,9 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" +checksum = "c5e1711e7d14f74b12a58411c542185ef7fb7f2e7f8ee6e2940a883628522b42" dependencies = [ "cfg-if", "glob", @@ -2096,7 +1963,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.66", + "syn 2.0.75", "unicode-ident", ] @@ -2121,7 +1988,7 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -2148,33 +2015,33 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] [[package]] name = "rustls" -version = "0.23.10" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.4", + "rustls-webpki 0.102.6", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", + "rustls-pemfile 2.1.3", "rustls-pki-types", "schannel", "security-framework", @@ -2191,9 +2058,9 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64 0.22.1", "rustls-pki-types", @@ -2201,9 +2068,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" @@ -2217,9 +2084,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -2265,11 +2132,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2278,9 +2145,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", @@ -2297,9 +2164,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.204" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] @@ -2316,22 +2183,23 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.204" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -2355,14 +2223,14 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -2392,9 +2260,9 @@ dependencies = [ [[package]] name = "sha1_smol" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" @@ -2416,6 +2284,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2475,12 +2349,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -2575,7 +2443,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -2598,7 +2466,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.66", + "syn 2.0.75", "tempfile", "tokio", "url", @@ -2612,7 +2480,7 @@ checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "bytes", "crc", @@ -2654,7 +2522,7 @@ checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.6.0", "byteorder", "crc", "dotenvy", @@ -2707,12 +2575,6 @@ dependencies = [ "url", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - [[package]] name = "stringprep" version = "0.1.5" @@ -2732,9 +2594,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2749,26 +2611,15 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "sysinfo" version = "0.30.13" @@ -2785,14 +2636,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2812,7 +2664,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -2869,21 +2721,11 @@ dependencies = [ "time-core", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2896,9 +2738,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" dependencies = [ "backtrace", "bytes", @@ -2919,7 +2761,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -2939,7 +2781,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.10", + "rustls 0.23.12", "rustls-pki-types", "tokio", ] @@ -3010,21 +2852,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.14", + "toml_edit 0.22.20", ] [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] @@ -3042,15 +2884,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.13", + "winnow 0.6.18", ] [[package]] @@ -3070,15 +2912,15 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -3100,7 +2942,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -3191,7 +3033,7 @@ version = "0.16.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a997fdd14e32ebbe80ee87887720e756efed0a7dbf97bd1170eb7c9a3956378b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "dashmap 5.5.3", "serde", "twilight-model", @@ -3204,7 +3046,7 @@ version = "0.16.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3edd60e2b95ec0d651c475dacad8a75e4780d900f8f2ea0f4c215182d920fc" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "fastrand", "flate2", "futures-core", @@ -3280,7 +3122,7 @@ checksum = "e6c07ac7c9d2d086291765ceecafcd52ce6dc33ab0ade02e06509eb416d88470" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.75", ] [[package]] @@ -3298,7 +3140,7 @@ version = "0.16.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eaf296e6aa8784699046bb1ceb33f3c684a59c4657c2b159eb4ec1e1ce44a49" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.6.0", "serde", "serde-value", "serde_repr", @@ -3411,9 +3253,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -3432,18 +3274,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "valuable" version = "0.1.0" @@ -3503,9 +3333,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" @@ -3579,7 +3409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" dependencies = [ "windows-core", - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3588,7 +3418,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", ] [[package]] @@ -3606,7 +3436,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -3626,18 +3465,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -3648,9 +3487,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -3660,9 +3499,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -3672,15 +3511,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -3690,9 +3529,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -3702,9 +3541,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -3714,9 +3553,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -3726,9 +3565,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -3741,88 +3580,32 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "yoke" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", -] - [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", -] - -[[package]] -name = "zerofrom" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", - "synstructure", + "syn 2.0.75", ] [[package]] @@ -3831,28 +3614,6 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -[[package]] -name = "zerovec" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", -] - [[package]] name = "zune-core" version = "0.4.12" @@ -3861,9 +3622,9 @@ checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a" [[package]] name = "zune-jpeg" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448" +checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768" dependencies = [ "zune-core", ] diff --git a/flake.lock b/flake.lock index e0afe5f..5887e56 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1722187037, - "narHash": "sha256-z7KBGvH/CCW6LojddUac0kSizfUpDAoHbLWPnDUxVNs=", + "lastModified": 1723898192, + "narHash": "sha256-MXIK60F11Tc7B+vRcLOWPx6IweAu3u54YpdByM/9OuA=", "owner": "cachix", "repo": "devenv", - "rev": "0779ee801db9ea0d7f969ac0185ec96b28a4aaf7", + "rev": "64bb347c3b0cee39da55ec6f52a5bef8d833c431", "type": "github" }, "original": { @@ -93,11 +93,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1722148092, - "narHash": "sha256-5QS64rfIFDzU1jmZrOK6wyZOCi6Vn/90apWRI6Hy+xk=", + "lastModified": 1723703304, + "narHash": "sha256-7ehq0nfRHpU3oNAkRpklaDxQZUpuaUig2sR2LI+IL/U=", "owner": "nix-community", "repo": "fenix", - "rev": "b39d8959f286dc7b9da91ae92f6af56de0169e87", + "rev": "6e4233dc54850e8aff6eff401400e9a9343881eb", "type": "github" }, "original": { @@ -417,11 +417,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1722099723, - "narHash": "sha256-61f+rvQAObm/TuBEqYFNUTngm/wXcuNhGtQbAmfZVvY=", + "lastModified": 1723648323, + "narHash": "sha256-AT6K9JREduWC1zcIJIx8JTZa4sYZD6VvyB/xRnjphqY=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "a46788318cce3b62e14606f70a14896b223ee5ec", + "rev": "64a140527b383e3a2fe95908881624fc5374c60c", "type": "github" }, "original": { diff --git a/lyra/Cargo.toml b/lyra/Cargo.toml index 6893f6d..4aeef86 100644 --- a/lyra/Cargo.toml +++ b/lyra/Cargo.toml @@ -38,22 +38,22 @@ lyra_ext = { path = "../lyra_ext" } paste = "1.0.15" const-str = "0.5.7" const_panic = { version = "0.2.8", features = ["derive"] } -bitflags = "2.5.0" +bitflags = "2.6.0" dashmap = "6.0.1" dotenvy = "0.15.7" dotenvy_macro = "0.15.7" thiserror = "1.0.63" color-eyre = "0.6.3" futures = "0.3.30" -tokio = { version = "1.39.2", features = [ +tokio = { version = "1.39.3", features = [ "sync", "signal", "rt-multi-thread", "macros", ] } -serde = "1.0.204" -serde_json = "1.0.120" -regex = "1.10.5" +serde = "1.0.208" +serde_json = "1.0.125" +regex = "1.10.6" linkify = "0.10.0" fuzzy-matcher = "0.3.7" log = "0.4.22" @@ -64,9 +64,7 @@ itertools = "0.13.0" rayon = "1.10.0" sqlx = { version = "0.8.0", features = ["postgres", "runtime-tokio-rustls"] } mixbox = "2.0.0" -lavalink-rs = { git = "https://gitlab.com/vicky5124/lavalink-rs", rev = "56fd9191", features = [ - "twilight16", -] } +lavalink-rs = { version = "0.13.0", features = ["twilight16"] } aho-corasick = "1.1.3" twilight = "0.16.0-rc.1" diff --git a/lyra_ext/Cargo.toml b/lyra_ext/Cargo.toml index 208fdf1..b339372 100644 --- a/lyra_ext/Cargo.toml +++ b/lyra_ext/Cargo.toml @@ -16,7 +16,7 @@ pedantic = { level = "deny", priority = -1 } nursery = { level = "deny", priority = -1 } [dependencies] -rstest = "0.21.0" +rstest = "0.22.0" heck = "0.5.0" const-str = "0.5.7" unicode-segmentation = "1" From 81d76b660c8ec279cf909497d39b577c79ac1e47 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:48:25 +0700 Subject: [PATCH 05/12] Remove Unnecessary LazyLocks --- lyra/src/command/util.rs | 8 +++----- lyra/src/component/connection.rs | 2 +- lyra/src/core/const.rs | 29 +++++++++------------------ lyra/src/lavalink/model.rs | 2 +- lyra/src/lavalink/model/connection.rs | 4 ++-- lyra/src/lavalink/model/queue.rs | 2 +- 6 files changed, 17 insertions(+), 30 deletions(-) diff --git a/lyra/src/command/util.rs b/lyra/src/command/util.rs index 4240c4a..1290fd0 100644 --- a/lyra/src/command/util.rs +++ b/lyra/src/command/util.rs @@ -264,10 +264,8 @@ async fn handle_suppressed_auto_join( ?ctx ); let requested_to_speak_message = requested_to_speak.model().await?; - let wait_for_speaker = tokio::time::timeout( - *r#const::misc::WAIT_FOR_BOT_EVENTS_TIMEOUT, - wait_for_speaker, - ); + let wait_for_speaker = + tokio::time::timeout(r#const::misc::WAIT_FOR_BOT_EVENTS_TIMEOUT, wait_for_speaker); if wait_for_speaker.await.is_err() { return Err(AutoJoinSuppressedError::StillNotSpeaker { @@ -323,7 +321,7 @@ pub async fn prompt_for_confirmation( }); let wait_for_modal_submit = tokio::time::timeout( - *r#const::misc::DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT, + r#const::misc::DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT, wait_for_modal_submit, ) .await; diff --git a/lyra/src/component/connection.rs b/lyra/src/component/connection.rs index 163fa0d..5748464 100644 --- a/lyra/src/component/connection.rs +++ b/lyra/src/component/connection.rs @@ -111,7 +111,7 @@ async fn start_inactivity_timeout( ); for _ in 0..const_connection::INACTIVITY_TIMEOUT_POLL_N { - tokio::time::sleep(*const_connection::INACTIVITY_TIMEOUT_POLL_INTERVAL).await; + tokio::time::sleep(const_connection::INACTIVITY_TIMEOUT_POLL_INTERVAL).await; if users_in_voice(&ctx, channel_id).is_some_and(|n| n >= 1) { return Ok(()); } diff --git a/lyra/src/core/const.rs b/lyra/src/core/const.rs index 731f1a1..53edb40 100644 --- a/lyra/src/core/const.rs +++ b/lyra/src/core/const.rs @@ -69,37 +69,26 @@ pub mod metadata { } pub mod connection { - use std::{sync::LazyLock, time::Duration}; + use std::time::Duration; pub const INACTIVITY_TIMEOUT_SECS: u16 = 600; pub const INACTIVITY_TIMEOUT_POLL_N: u8 = 10; - pub static CHANGED_TIMEOUT: LazyLock = LazyLock::new(|| Duration::from_millis(250)); - - pub static GET_LAVALINK_CONNECTION_INFO_TIMEOUT: LazyLock = - LazyLock::new(|| Duration::from_millis(2_000)); - - pub static INACTIVITY_TIMEOUT_POLL_INTERVAL: LazyLock = LazyLock::new(|| { - Duration::from_secs( - u64::from(INACTIVITY_TIMEOUT_SECS) / u64::from(INACTIVITY_TIMEOUT_POLL_N), - ) - }); + pub const CHANGED_TIMEOUT: Duration = Duration::from_millis(250); + pub const GET_LAVALINK_CONNECTION_INFO_TIMEOUT: Duration = Duration::from_millis(2_000); + pub const INACTIVITY_TIMEOUT_POLL_INTERVAL: Duration = + Duration::from_secs(INACTIVITY_TIMEOUT_SECS as u64 / INACTIVITY_TIMEOUT_POLL_N as u64); } pub mod misc { - use std::{sync::LazyLock, time::Duration}; + use std::time::Duration; pub const ADD_TRACKS_WRAP_LIMIT: usize = 3; pub const WAIT_FOR_NOT_SUPPRESSED_TIMEOUT_SECS: u8 = 30; - pub static WAIT_FOR_BOT_EVENTS_TIMEOUT: LazyLock = - LazyLock::new(|| Duration::from_millis(1_000)); - - pub static DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT: LazyLock = - LazyLock::new(|| Duration::from_secs(60)); - - pub static QUEUE_ADVANCE_LOCKED_TIMEOUT: LazyLock = - LazyLock::new(|| Duration::from_millis(250)); + pub const WAIT_FOR_BOT_EVENTS_TIMEOUT: Duration = Duration::from_millis(1_000); + pub const DESTRUCTIVE_COMMAND_CONFIRMATION_TIMEOUT: Duration = Duration::from_secs(60); + pub const QUEUE_ADVANCE_LOCKED_TIMEOUT: Duration = Duration::from_millis(250); } pub mod text { diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index 2a5ae36..a8d66d9 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -261,7 +261,7 @@ pub trait DelegateMethods { let info = self .get_connection_info( guild_id, - *r#const::connection::GET_LAVALINK_CONNECTION_INFO_TIMEOUT, + r#const::connection::GET_LAVALINK_CONNECTION_INFO_TIMEOUT, ) .await?; tracing::trace!("getting lavalink connection info took {:?}", now.elapsed()); diff --git a/lyra/src/lavalink/model/connection.rs b/lyra/src/lavalink/model/connection.rs index 590c6d8..2ebdb30 100644 --- a/lyra/src/lavalink/model/connection.rs +++ b/lyra/src/lavalink/model/connection.rs @@ -35,7 +35,7 @@ impl Connection { pub async fn changed(&self) -> bool { tracing::trace!("waiting for connection change notification"); - let duration = *r#const::connection::CHANGED_TIMEOUT; + let duration = r#const::connection::CHANGED_TIMEOUT; let future = self.change.notified(); tokio::time::timeout(duration, future).await.is_ok() } @@ -98,7 +98,7 @@ pub async fn wait_for_with( rx: &mut broadcast::Receiver, predicate: impl Fn(&Event) -> bool + Send + Sync, ) -> EventRecvResult> { - let event = tokio::time::timeout(*r#const::misc::WAIT_FOR_BOT_EVENTS_TIMEOUT, async { + let event = tokio::time::timeout(r#const::misc::WAIT_FOR_BOT_EVENTS_TIMEOUT, async { loop { let event = rx.recv().await?; if predicate(&event) { diff --git a/lyra/src/lavalink/model/queue.rs b/lyra/src/lavalink/model/queue.rs index b114368..5239deb 100644 --- a/lyra/src/lavalink/model/queue.rs +++ b/lyra/src/lavalink/model/queue.rs @@ -229,7 +229,7 @@ impl Queue { pub async fn not_advance_locked(&self) -> bool { let future = self.advance_lock.notified(); - let duration = *crate::core::r#const::misc::QUEUE_ADVANCE_LOCKED_TIMEOUT; + let duration = crate::core::r#const::misc::QUEUE_ADVANCE_LOCKED_TIMEOUT; tokio::time::timeout(duration, future).await.is_err() } From ef6c9f1af11c0e363aebc03886fa1ab089751c46 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Sat, 24 Aug 2024 01:52:00 +0700 Subject: [PATCH 06/12] Implemented `/skip`, `/back` --- lyra/src/command/declare.rs | 4 ++- lyra/src/component/playback.rs | 2 ++ lyra/src/component/playback/back.rs | 42 +++++++++++++++++++++++ lyra/src/component/playback/jump/first.rs | 2 +- lyra/src/component/playback/skip.rs | 34 ++++++++++++++++++ lyra/src/lavalink/model/queue.rs | 13 +++++++ 6 files changed, 95 insertions(+), 2 deletions(-) diff --git a/lyra/src/command/declare.rs b/lyra/src/command/declare.rs index 6881949..158716b 100644 --- a/lyra/src/command/declare.rs +++ b/lyra/src/command/declare.rs @@ -18,7 +18,7 @@ use crate::{ config::Config, connection::{Join, Leave}, misc::Ping, - playback::{Jump, JumpAutocomplete, PlayPause, Restart, Seek}, + playback::{Back, Jump, JumpAutocomplete, PlayPause, Restart, Seek, Skip}, queue::{ AddToQueue, Clear, FairQueue, Move, MoveAutocomplete, Play, PlayAutocomplete, PlayFile, Remove, RemoveAutocomplete, RemoveRange, RemoveRangeAutocomplete, Repeat, Shuffle, @@ -185,6 +185,8 @@ declare_slash_commands![ Seek, Restart, Jump, + Skip, + Back, ]; declare_message_commands![AddToQueue,]; diff --git a/lyra/src/component/playback.rs b/lyra/src/component/playback.rs index 67b8d92..eae086b 100644 --- a/lyra/src/component/playback.rs +++ b/lyra/src/component/playback.rs @@ -5,10 +5,12 @@ mod restart; mod seek; mod skip; +pub use back::Back; pub use jump::{Autocomplete as JumpAutocomplete, Jump}; pub use play_pause::PlayPause; pub use restart::Restart; pub use seek::Seek; +pub use skip::Skip; use crate::{ command::require, diff --git a/lyra/src/component/playback/back.rs b/lyra/src/component/playback/back.rs index 8b13789..9d09c50 100644 --- a/lyra/src/component/playback/back.rs +++ b/lyra/src/component/playback/back.rs @@ -1 +1,43 @@ +use twilight_interactions::command::{CommandModel, CreateCommand}; +use crate::command::{check, macros::out, model::BotSlashCommand, require}; + +/// Jumps to the track before the current one in the queue. Will wrap around if queue repeat is enabled. +#[derive(CreateCommand, CommandModel)] +#[command(name = "back")] +pub struct Back; + +impl BotSlashCommand for Back { + #[allow(clippy::significant_drop_tightening)] + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let mut data_w = data.write().await; + let queue = require::queue_not_empty_mut(&mut data_w)?; + let mut txt; + + if let Ok(current_track) = require::current_track(queue) { + check::current_track_is_users(¤t_track, in_voice_with_user)?; + txt = format!("โฎ๏ธ ~~`{}`~~", current_track.track.data().info.title); + } else { + txt = String::new(); + } + + queue.downgrade_repeat_mode(); + queue.acquire_advance_lock(); + queue.recede(); + + // SAFETY: since the queue is not empty, receding must always yield a new current track + let item = unsafe { queue.current().unwrap_unchecked() }; + player.context.play_now(item.data()).await?; + + if txt.is_empty() { + txt = format!("โฎ๏ธ `{}`", item.data().info.title); + } + + out!(txt, ctx); + } +} diff --git a/lyra/src/component/playback/jump/first.rs b/lyra/src/component/playback/jump/first.rs index 08a68d7..4f5e158 100644 --- a/lyra/src/component/playback/jump/first.rs +++ b/lyra/src/component/playback/jump/first.rs @@ -7,7 +7,7 @@ use crate::command::{ require, }; -/// Jumps to a the first track in the queue +/// Jumps to the first track in the queue #[derive(CreateCommand, CommandModel)] #[command(name = "first")] pub struct First; diff --git a/lyra/src/component/playback/skip.rs b/lyra/src/component/playback/skip.rs index 8b13789..a275796 100644 --- a/lyra/src/component/playback/skip.rs +++ b/lyra/src/component/playback/skip.rs @@ -1 +1,35 @@ +use twilight_interactions::command::{CommandModel, CreateCommand}; +use crate::command::{check, macros::out, model::BotSlashCommand, require}; + +/// Skip playing the current track. +#[derive(CreateCommand, CommandModel)] +#[command(name = "skip")] +pub struct Skip; + +impl BotSlashCommand for Skip { + #[allow(clippy::significant_drop_tightening)] + async fn run(self, ctx: crate::command::SlashCtx) -> crate::error::CommandResult { + let mut ctx = require::guild(ctx)?; + let in_voice_with_user = check::user_in(require::in_voice(&ctx)?.and_unsuppressed()?)?; + let player = require::player(&ctx)?; + let data = player.data(); + + let mut data_w = data.write().await; + let queue = require::queue_not_empty_mut(&mut data_w)?; + let current_track = require::current_track(queue)?; + check::current_track_is_users(¤t_track, in_voice_with_user)?; + let txt = format!("โญ๏ธ ~~`{}`~~", current_track.track.data().info.title); + + queue.downgrade_repeat_mode(); + queue.acquire_advance_lock(); + queue.advance(); + if let Some(item) = queue.current() { + player.context.play_now(item.data()).await?; + } else { + player.context.stop_now().await?; + } + + out!(txt, ctx); + } +} diff --git a/lyra/src/lavalink/model/queue.rs b/lyra/src/lavalink/model/queue.rs index 5239deb..ff1dd35 100644 --- a/lyra/src/lavalink/model/queue.rs +++ b/lyra/src/lavalink/model/queue.rs @@ -222,6 +222,19 @@ impl Queue { } } + pub fn recede(&mut self) { + match self.repeat_mode { + RepeatMode::Off => { + self.index = self.index.saturating_sub(1); + } + RepeatMode::All => { + let len = self.len(); + self.index = ((self.index + len).saturating_sub(1)) % len; + } + RepeatMode::Track => {} + } + } + pub fn acquire_advance_lock(&self) { tracing::trace!("acquired queue advance lock"); self.advance_lock.notify_one(); From fc79e1a86fd58da902697ad891f9f65b889c2683 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Tue, 3 Sep 2024 00:52:16 +0700 Subject: [PATCH 07/12] Handle player voice connection updates --- flake.lock | 18 ++++++------- lyra/src/command/require.rs | 21 ++++++++++++++- lyra/src/component/connection.rs | 12 +++++++++ lyra/src/component/connection/join.rs | 16 +++++++++++ lyra/src/component/playback.rs | 37 +++++++++++++------------- lyra/src/error/component/connection.rs | 2 ++ lyra/src/gateway/interaction.rs | 4 +-- lyra/src/lavalink/model.rs | 24 ++++++++++------- 8 files changed, 94 insertions(+), 40 deletions(-) diff --git a/flake.lock b/flake.lock index 5887e56..8542ffc 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1723898192, - "narHash": "sha256-MXIK60F11Tc7B+vRcLOWPx6IweAu3u54YpdByM/9OuA=", + "lastModified": 1725121898, + "narHash": "sha256-UZVuDnDCS5EgFNLtO9u0Q8+ZRql7t5fMbxd54VMtLdY=", "owner": "cachix", "repo": "devenv", - "rev": "64bb347c3b0cee39da55ec6f52a5bef8d833c431", + "rev": "3ec083c9d58c9007d2be2a6145b578c67322d575", "type": "github" }, "original": { @@ -93,11 +93,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1723703304, - "narHash": "sha256-7ehq0nfRHpU3oNAkRpklaDxQZUpuaUig2sR2LI+IL/U=", + "lastModified": 1725258763, + "narHash": "sha256-7s5RfYlTljWnKGkK4hOMJCJ0sNQoLYjMxezX3Vijy/0=", "owner": "nix-community", "repo": "fenix", - "rev": "6e4233dc54850e8aff6eff401400e9a9343881eb", + "rev": "0774f58cf1025bbb713971deecc7f07c856be6ed", "type": "github" }, "original": { @@ -417,11 +417,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1723648323, - "narHash": "sha256-AT6K9JREduWC1zcIJIx8JTZa4sYZD6VvyB/xRnjphqY=", + "lastModified": 1725191098, + "narHash": "sha256-YH0kH5CSOnAuPUB1BUzUqvnKiv5SgDhfMNjrkki9Ahk=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "64a140527b383e3a2fe95908881624fc5374c60c", + "rev": "779d9eee2ea403da447278a7007c9627c8878856", "type": "github" }, "original": { diff --git a/lyra/src/command/require.rs b/lyra/src/command/require.rs index 914fb50..429044a 100644 --- a/lyra/src/command/require.rs +++ b/lyra/src/command/require.rs @@ -23,7 +23,8 @@ use crate::{ }, gateway::GuildIdAware, lavalink::{ - OwnedPlayerData, PlayerDataRead, PlayerDataWrite, Queue, QueueItem, UnwrappedPlayerData, + DelegateMethods, OwnedPlayerData, PlayerDataRead, PlayerDataWrite, Queue, QueueItem, + UnwrappedPlayerData, }, LavalinkAndGuildIdAware, }; @@ -62,6 +63,24 @@ impl PlayerInterface { Ok(()) } + pub async fn update_voice_channel(&self, voice_is_empty: bool) -> LavalinkResult<()> { + let mut update_player = lavalink_rs::model::http::UpdatePlayer { + voice: Some( + self.context + .client + .get_connection_info_traced(self.context.guild_id) + .await?, + ), + ..Default::default() + }; + if voice_is_empty { + update_player.paused = Some(true); + self.data().write().await.set_pause(true); + } + self.context.update_player(&update_player, true).await?; + Ok(()) + } + pub async fn seek_to_with<'data, 'guard>( &self, timestamp: Duration, diff --git a/lyra/src/component/connection.rs b/lyra/src/component/connection.rs index 5748464..5f62cc8 100644 --- a/lyra/src/component/connection.rs +++ b/lyra/src/component/connection.rs @@ -18,6 +18,7 @@ use twilight_model::id::{ use self::join::JoinedChannel; use crate::{ + command::require, component::connection::{ join::JoinedChannelType, leave::{disconnect, pre_disconnect_cleanup, LeaveResponse}, @@ -171,6 +172,14 @@ pub async fn handle_voice_state_update( && state.channel_id != Some(old_channel_id) && users_in_voice(ctx, connected_channel_id).is_some_and(|n| n == 0) { + if let Ok(player) = require::player(ctx) { + player.set_pause(true).await?; + ctx.http() + .create_message(text_channel_id) + .content("โšกโ–ถ Paused `(Bot is not used by anyone)`") + .await?; + }; + traced::tokio_spawn(start_inactivity_timeout( InactivityTimeoutContext::new_via(ctx), connected_channel_id, @@ -229,6 +238,9 @@ async fn match_state_channel_id( empty: voice_is_empty, }; + if let Ok(player) = require::player(ctx) { + player.update_voice_channel(voice_is_empty).await?; + } let forcefully_moved_notice = if voice_is_empty { format!( "\n`(Bot was forcefully moved to an empty voice channel, and automatically disconnecting if no one else joins in` `)`", diff --git a/lyra/src/component/connection/join.rs b/lyra/src/component/connection/join.rs index ff4561b..70f3188 100644 --- a/lyra/src/component/connection/join.rs +++ b/lyra/src/component/connection/join.rs @@ -242,6 +242,22 @@ async fn impl_connect_to( ctx.sender() .command(&UpdateVoiceState::new(guild_id, channel_id, true, false))?; + if let Ok(player) = require::player(ctx) { + if old_channel_id.is_some() { + tracing::trace!("waiting for voice server update..."); + let _ = ctx + .bot() + .standby() + .wait_for_event(move |e: &Event| match e { + Event::VoiceServerUpdate(v) => v.guild_id == guild_id, + _ => false, + }) + .await; + tracing::trace!("voice server update received"); + player.update_voice_channel(voice_is_empty).await?; + } + } + if joined.kind == JoinedChannelType::Stage { ctx.bot() .http() diff --git a/lyra/src/component/playback.rs b/lyra/src/component/playback.rs index eae086b..dff8d5e 100644 --- a/lyra/src/component/playback.rs +++ b/lyra/src/component/playback.rs @@ -14,14 +14,12 @@ pub use skip::Skip; use crate::{ command::require, - core::model::{BotStateAware, HttpAware}, + core::model::{BotStateAware, CacheAware, HttpAware}, error::component::playback::HandleVoiceStateUpdateError, gateway::voice::Context, LavalinkAndGuildIdAware, }; -use super::connection::users_in_voice; - #[tracing::instrument(skip_all, name = "voice_state_update")] pub async fn handle_voice_state_update( ctx: &Context, @@ -31,7 +29,7 @@ pub async fn handle_voice_state_update( let maybe_old_state = ctx.old_voice_state(); tracing::trace!("handling voice state update"); - let (connected_channel_id, text_channel_id) = { + let text_channel_id = { let Some(connection) = ctx.get_connection() else { tracing::trace!("no active connection"); return Ok(()); @@ -43,26 +41,29 @@ pub async fn handle_voice_state_update( } tracing::trace!("no connection change notification"); - (connection.channel_id, connection.text_channel_id) + connection.text_channel_id }; let Ok(player) = require::player(ctx) else { return Ok(()); }; - if let Some(old_state) = maybe_old_state { - let old_channel_id = old_state.channel_id(); - if state.user_id != ctx.bot().user_id() - && old_channel_id == connected_channel_id - && state.channel_id.is_some_and(|c| c != old_channel_id) - && users_in_voice(ctx, connected_channel_id).is_some_and(|n| n == 0) - { - player.set_pause(true).await?; - ctx.http() - .create_message(text_channel_id) - .content("โšกโ–ถ Paused `(Bot is not used by anyone)`") - .await?; - } + if state.user_id == ctx.bot().user_id() + && state.suppress + && maybe_old_state.is_some_and(|old_state| { + state.channel_id.is_some_and(|channel_id| { + channel_id == old_state.channel_id() + && ctx.cache().channel(channel_id).is_some_and(|channel| { + channel.kind == twilight_model::channel::ChannelType::GuildStageVoice + }) + }) && !old_state.suppress() + }) + { + player.set_pause(true).await?; + ctx.http() + .create_message(text_channel_id) + .content("โšกโ–ถ Paused `(Bot was moved to audience)`") + .await?; } Ok(()) diff --git a/lyra/src/error/component/connection.rs b/lyra/src/error/component/connection.rs index fc1ce96..24c69ec 100644 --- a/lyra/src/error/component/connection.rs +++ b/lyra/src/error/component/connection.rs @@ -323,6 +323,7 @@ pub enum HandleVoiceStateUpdateError { MessageValidation(#[from] twilight_validate::message::MessageValidationError), MatchStateChannelID(#[from] MatchStateChannelIdError), PreDisconnectCleanup(#[from] leave::PreDisconnectCleanupError), + Lavalink(#[from] lavalink_rs::error::LavalinkError), } #[derive(Error, Debug)] @@ -331,4 +332,5 @@ pub enum MatchStateChannelIdError { Http(#[from] twilight_http::Error), MessageValidation(#[from] twilight_validate::message::MessageValidationError), Cache(#[from] crate::error::Cache), + Lavalink(#[from] lavalink_rs::error::LavalinkError), } diff --git a/lyra/src/gateway/interaction.rs b/lyra/src/gateway/interaction.rs index 88d919e..1807003 100644 --- a/lyra/src/gateway/interaction.rs +++ b/lyra/src/gateway/interaction.rs @@ -130,9 +130,7 @@ impl Context { if let Some(guild_id) = inner_guild_id { if let Some(mut connection) = bot.lavalink().get_connection_mut(guild_id) { - if connection.text_channel_id != channel_id { - connection.text_channel_id = channel_id; - } + connection.text_channel_id = channel_id; } } diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index a8d66d9..fd3b40d 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -246,17 +246,10 @@ pub trait DelegateMethods { guild_id: impl Into + Send, timeout: std::time::Duration, ) -> LavalinkResult; - - async fn create_player_context_with_data( + async fn get_connection_info_traced( &self, guild_id: impl Into + Send, - connection_info: impl Into + Send, - user_data: Arc, - ) -> LavalinkResult; - async fn new_player( - &self, - guild_id: impl Into + Send + Copy, - ) -> LavalinkResult { + ) -> LavalinkResult { let now = tokio::time::Instant::now(); let info = self .get_connection_info( @@ -265,7 +258,20 @@ pub trait DelegateMethods { ) .await?; tracing::trace!("getting lavalink connection info took {:?}", now.elapsed()); + Ok(info) + } + async fn create_player_context_with_data( + &self, + guild_id: impl Into + Send, + connection_info: impl Into + Send, + user_data: Arc, + ) -> LavalinkResult; + async fn new_player( + &self, + guild_id: impl Into + Send + Copy, + ) -> LavalinkResult { + let info = self.get_connection_info_traced(guild_id).await?; let data = Arc::new(RwLock::new(RawPlayerData::new())); let player = self .create_player_context_with_data(guild_id, info, data) From 98a138c9e730ff17000033e13bebe2a1911c0f53 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Fri, 6 Sep 2024 22:54:10 +0700 Subject: [PATCH 08/12] Handled unrecognised connections, track stuck & exception, fixed `/remove` index correction --- flake.lock | 18 ++-- lyra/src/command/require.rs | 2 +- lyra/src/command/util.rs | 2 +- lyra/src/component/connection/join.rs | 44 ++++----- lyra/src/component/connection/leave.rs | 2 +- lyra/src/component/queue.rs | 3 +- lyra/src/component/queue/clear.rs | 3 +- lyra/src/component/queue/play.rs | 4 +- lyra/src/component/queue/repeat.rs | 6 +- lyra/src/component/tuning/volume/down.rs | 4 +- .../component/tuning/volume/toggle_mute.rs | 4 +- lyra/src/component/tuning/volume/up.rs | 6 +- lyra/src/core/model.rs | 4 +- lyra/src/error.rs | 4 + lyra/src/error/command.rs | 11 ++- lyra/src/error/command/util.rs | 5 ++ lyra/src/error/component/connection.rs | 14 +++ lyra/src/error/lavalink.rs | 1 + lyra/src/gateway/interaction.rs | 13 ++- lyra/src/lavalink.rs | 8 +- lyra/src/lavalink/model.rs | 90 +++++++++++++++++-- lyra/src/lavalink/track.rs | 49 ++++++++-- lyra/src/runner.rs | 17 ++-- 23 files changed, 235 insertions(+), 79 deletions(-) diff --git a/flake.lock b/flake.lock index 8542ffc..ef45a86 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1725121898, - "narHash": "sha256-UZVuDnDCS5EgFNLtO9u0Q8+ZRql7t5fMbxd54VMtLdY=", + "lastModified": 1725567101, + "narHash": "sha256-gFCoG9udcJShDUxj+6KjUYB7Kuk+NAxJaMJJFMeRBJ4=", "owner": "cachix", "repo": "devenv", - "rev": "3ec083c9d58c9007d2be2a6145b578c67322d575", + "rev": "a5460083c31d190ce44c8286a7ff9b6d2ade016b", "type": "github" }, "original": { @@ -93,11 +93,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1725258763, - "narHash": "sha256-7s5RfYlTljWnKGkK4hOMJCJ0sNQoLYjMxezX3Vijy/0=", + "lastModified": 1725604324, + "narHash": "sha256-+VgeYuaCQn5vmoH1GTYQzvVTtWxirZmdDQJKr8uLgQI=", "owner": "nix-community", "repo": "fenix", - "rev": "0774f58cf1025bbb713971deecc7f07c856be6ed", + "rev": "d9afdb4465ba2f20bb73b0ff5d2c2837cafc2e14", "type": "github" }, "original": { @@ -417,11 +417,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1725191098, - "narHash": "sha256-YH0kH5CSOnAuPUB1BUzUqvnKiv5SgDhfMNjrkki9Ahk=", + "lastModified": 1725548942, + "narHash": "sha256-ZnF5MaOAeiiKIATYN4rrqNsnhSQOQ+Hvfg0mHLvN04Y=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "779d9eee2ea403da447278a7007c9627c8878856", + "rev": "124c7482167ff6eea4f7663c0be87ea568ccd8c6", "type": "github" }, "original": { diff --git a/lyra/src/command/require.rs b/lyra/src/command/require.rs index 429044a..8249c48 100644 --- a/lyra/src/command/require.rs +++ b/lyra/src/command/require.rs @@ -24,7 +24,7 @@ use crate::{ gateway::GuildIdAware, lavalink::{ DelegateMethods, OwnedPlayerData, PlayerDataRead, PlayerDataWrite, Queue, QueueItem, - UnwrappedPlayerData, + UnwrappedData, }, LavalinkAndGuildIdAware, }; diff --git a/lyra/src/command/util.rs b/lyra/src/command/util.rs index 1290fd0..9823327 100644 --- a/lyra/src/command/util.rs +++ b/lyra/src/command/util.rs @@ -351,7 +351,7 @@ pub async fn auto_new_player(ctx: &GuildCtx) -> LavalinkResult player, - None => lavalink.new_player(guild_id).await?, + None => lavalink.new_player(guild_id, ctx.channel_id()).await?, }; Ok(player) diff --git a/lyra/src/component/connection/join.rs b/lyra/src/component/connection/join.rs index 70f3188..5acfdd7 100644 --- a/lyra/src/component/connection/join.rs +++ b/lyra/src/component/connection/join.rs @@ -213,31 +213,25 @@ async fn impl_connect_to( let voice_is_empty = users_in_voice(ctx, channel_id).ok_or(CacheError)? == 0; let lavalink = ctx.lavalink(); - let response = old_channel_id.map_or_else( - || { - let connection = Connection::new(channel_id, ctx.channel_id()); - connection.notify_change(); - lavalink.new_connection_with(guild_id, connection); - Response::Joined { - voice: joined, - empty: voice_is_empty, - } - }, - |from| { - // SAFETY: `old_channel_id` is of variant `Some`, meaning another connection exists, - // so `ctx.lavalink().get_connection_mut(guild_id).unwrap_unchecked()` is safe - let mut connection = - unsafe { lavalink.get_connection_mut(guild_id).unwrap_unchecked() }; - connection.channel_id = channel_id; - connection.notify_change(); - drop(connection); - Response::Moved { - from, - to: joined, - empty: voice_is_empty, - } - }, - ); + let response = if let Some(from) = old_channel_id { + let mut connection = lavalink.try_get_connection_mut(guild_id)?; + connection.channel_id = channel_id; + connection.notify_change(); + drop(connection); + Response::Moved { + from, + to: joined, + empty: voice_is_empty, + } + } else { + let connection = Connection::new(channel_id, ctx.channel_id()); + connection.notify_change(); + lavalink.new_connection_with(guild_id, connection); + Response::Joined { + voice: joined, + empty: voice_is_empty, + } + }; ctx.sender() .command(&UpdateVoiceState::new(guild_id, channel_id, true, false))?; diff --git a/lyra/src/component/connection/leave.rs b/lyra/src/component/connection/leave.rs index d4eb825..fa39938 100644 --- a/lyra/src/component/connection/leave.rs +++ b/lyra/src/component/connection/leave.rs @@ -58,7 +58,7 @@ async fn leave(ctx: &GuildCtx) -> Result "**`โ‰ก-`**", }; - let current = queue.position(); + // SAFETY: `queue.index() + 1` is non-zero + let current = unsafe { NonZeroUsize::new_unchecked(queue.index() + 1) }; let before_current = positions.partition_point(|&i| i < current); *queue.index_mut() -= positions[..before_current].len(); diff --git a/lyra/src/component/queue/clear.rs b/lyra/src/component/queue/clear.rs index 8895500..bbe87b1 100644 --- a/lyra/src/component/queue/clear.rs +++ b/lyra/src/component/queue/clear.rs @@ -10,6 +10,7 @@ use crate::{ require, }, error::CommandResult, + gateway::GuildIdAware, lavalink::Event, LavalinkAware, }; @@ -24,7 +25,7 @@ impl BotSlashCommand for Clear { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; let in_voice = require::in_voice(&ctx)?.and_unsuppressed()?; - let connection = ctx.lavalink().connection_from(&in_voice); + let connection = ctx.lavalink().try_get_connection(ctx.guild_id())?; let in_voice_with_user = check::user_in(in_voice)?; let player = require::player(&ctx)?; diff --git a/lyra/src/component/queue/play.rs b/lyra/src/component/queue/play.rs index 957c94c..fc0edc3 100644 --- a/lyra/src/component/queue/play.rs +++ b/lyra/src/component/queue/play.rs @@ -40,9 +40,7 @@ use crate::{ CommandResult, LoadFailed as LoadFailedError, }, gateway::GuildIdAware, - lavalink::{ - CorrectPlaylistInfo, CorrectTrackInfo, UnwrappedPlayerData, UnwrappedPlayerInfoUri, - }, + lavalink::{CorrectPlaylistInfo, CorrectTrackInfo, UnwrappedData, UnwrappedPlayerInfoUri}, LavalinkAware, }; diff --git a/lyra/src/component/queue/repeat.rs b/lyra/src/component/queue/repeat.rs index 0d0d851..e55bac6 100644 --- a/lyra/src/component/queue/repeat.rs +++ b/lyra/src/component/queue/repeat.rs @@ -6,8 +6,7 @@ use crate::{ macros::out_or_upd, model::BotSlashCommand, poll::Topic, - require::{self, PartialInVoice}, - SlashCtx, + require, SlashCtx, }, error::CommandResult, gateway::GuildIdAware, @@ -60,7 +59,6 @@ impl BotSlashCommand for Repeat { }; let in_voice = require::in_voice(&ctx)?; - let partial_in_voice = PartialInVoice::from(&in_voice); let player = require::player(&ctx)?; let data = player.data(); @@ -75,7 +73,7 @@ impl BotSlashCommand for Repeat { .await?; ctx.lavalink() - .connection_from(&partial_in_voice) + .try_get_connection(guild_id)? .dispatch(Event::QueueRepeat); data.write().await.queue_mut().set_repeat_mode(mode); diff --git a/lyra/src/component/tuning/volume/down.rs b/lyra/src/component/tuning/volume/down.rs index 2aab979..8586fcb 100644 --- a/lyra/src/component/tuning/volume/down.rs +++ b/lyra/src/component/tuning/volume/down.rs @@ -23,7 +23,7 @@ pub struct Down { impl BotSlashCommand for Down { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; - let (in_voice, player) = check_user_is_dj_and_require_unsuppressed_player(&ctx)?; + let (_, player) = check_user_is_dj_and_require_unsuppressed_player(&ctx)?; let guild_id = ctx.guild_id(); let data = player.data(); @@ -45,7 +45,7 @@ impl BotSlashCommand for Down { super::clipping_warning(new_percent), ) } else { - ctx.lavalink().connection_mut_from(&in_voice).mute = true; + ctx.lavalink().try_get_connection_mut(guild_id)?.mute = true; ctx.http() .update_guild_member(guild_id, ctx.bot().user_id()) .mute(true) diff --git a/lyra/src/component/tuning/volume/toggle_mute.rs b/lyra/src/component/tuning/volume/toggle_mute.rs index 0dcfcc8..c663ac7 100644 --- a/lyra/src/component/tuning/volume/toggle_mute.rs +++ b/lyra/src/component/tuning/volume/toggle_mute.rs @@ -17,10 +17,10 @@ pub struct ToggleMute; impl BotSlashCommand for ToggleMute { async fn run(self, ctx: SlashCtx) -> CommandResult { let mut ctx = require::guild(ctx)?; - let in_voice = unmuting_checks(&ctx)?; + let _ = unmuting_checks(&ctx)?; let guild_id = ctx.guild_id(); - let mut connection = ctx.lavalink().connection_mut_from(&in_voice); + let mut connection = ctx.lavalink().try_get_connection_mut(guild_id)?; let mute = !connection.mute; ctx.http() diff --git a/lyra/src/component/tuning/volume/up.rs b/lyra/src/component/tuning/volume/up.rs index c223672..9e644c6 100644 --- a/lyra/src/component/tuning/volume/up.rs +++ b/lyra/src/component/tuning/volume/up.rs @@ -30,7 +30,7 @@ impl BotSlashCommand for Up { const MAX_PERCENT: NonZeroU16 = unsafe { NonZeroU16::new_unchecked(1_000) }; let mut ctx = require::guild(ctx)?; - let (in_voice, player) = check_user_is_dj_and_require_player(&ctx)?; + let (_, player) = check_user_is_dj_and_require_player(&ctx)?; let lavalink = ctx.lavalink(); let guild_id = ctx.guild_id(); @@ -38,8 +38,8 @@ impl BotSlashCommand for Up { #[allow(clippy::cast_possible_truncation)] let percent_u16 = self.percent.unwrap_or(10).unsigned_abs() as u16; - let (old_percent_str, new_percent) = if lavalink.connection_from(&in_voice).mute { - lavalink.connection_mut_from(&in_voice).mute = false; + let (old_percent_str, new_percent) = if lavalink.try_get_connection(guild_id)?.mute { + lavalink.try_get_connection_mut(guild_id)?.mute = false; ctx.http() .update_guild_member(guild_id, ctx.bot().user_id()) .mute(false) diff --git a/lyra/src/core/model.rs b/lyra/src/core/model.rs index e23b170..b706796 100644 --- a/lyra/src/core/model.rs +++ b/lyra/src/core/model.rs @@ -127,7 +127,7 @@ pub trait HttpAware { pub struct BotState { cache: InMemoryCache, - http: Client, + http: Arc, standby: Standby, lavalink: Lavalink, db: Pool, @@ -135,7 +135,7 @@ pub struct BotState { } impl BotState { - pub fn new(db: Pool, http: Client, lavalink: Lavalink) -> Self { + pub fn new(db: Pool, http: Arc, lavalink: Lavalink) -> Self { let info = BotInfo { started: Instant::now(), guild_counter: GuildCounter::new(), diff --git a/lyra/src/error.rs b/lyra/src/error.rs index f093f80..360c9e9 100644 --- a/lyra/src/error.rs +++ b/lyra/src/error.rs @@ -212,3 +212,7 @@ pub struct NotInGuild; #[derive(Error, Debug)] #[error("confirmation timed out")] pub struct ConfirmationTimedOut; + +#[derive(Error, Debug)] +#[error("unrecognised voice connection")] +pub struct UnrecognisedConnection; diff --git a/lyra/src/error/command.rs b/lyra/src/error/command.rs index 9cd1afc..0df9ad3 100644 --- a/lyra/src/error/command.rs +++ b/lyra/src/error/command.rs @@ -51,6 +51,7 @@ pub enum Error { HandlePoll(#[from] check::HandlePollError), NotPlaying(#[from] super::NotPlaying), Paused(#[from] super::Paused), + UnrecognisedConnection(#[from] super::UnrecognisedConnection), } pub enum FlattenedError<'a> { @@ -89,6 +90,7 @@ pub enum FlattenedError<'a> { Lavalink, NoPlayer, NotInGuild, + UnrecognisedConnection, } pub use FlattenedError as Fe; @@ -287,6 +289,9 @@ impl<'a> Fe<'a> { super::component::connection::join::ResidualImplConnectToError::Lavalink(_) => { Self::Lavalink } + super::component::connection::join::ResidualImplConnectToError::UnrecognisedConnection(_) => { + Self::UnrecognisedConnection + }, super::component::connection::join::ResidualImplConnectToError::CheckUserAllowed(e) => { Self::from_check_user_allowed_residual(e) } @@ -377,6 +382,9 @@ impl<'a> Fe<'a> { ) -> Self { match error { super::component::connection::leave::ResidualError::GatewaySend(_) => Self::GatewaySend, + super::component::connection::leave::ResidualError::UnrecognisedConnection(_) => { + Self::UnrecognisedConnection + } super::component::connection::leave::ResidualError::InVoiceWithoutUser(e) => { Self::InVoiceWithoutUser(e) } @@ -518,9 +526,10 @@ impl Error { Self::Cache(_) => Fe::Cache, Self::NotPlaying(_) => Fe::NotPlaying, Self::Paused(_) => Fe::Paused, - Self::RequireUnsuppressed(e) => Fe::from_require_unsuppressed_error(e), + Self::UnrecognisedConnection(_) => Fe::UnrecognisedConnection, Self::PositionOutOfRange(e) => Fe::PositionOutOfRange(e), Self::InVoiceWithoutUser(e) => Fe::InVoiceWithoutUser(e), + Self::RequireUnsuppressed(e) => Fe::from_require_unsuppressed_error(e), Self::CheckUsersTrack(e) => Fe::from_users_track_error(e), Self::RequireInVoiceWithSomeoneElse(e) => { Fe::from_require_in_voice_with_someone_else_error(e) diff --git a/lyra/src/error/command/util.rs b/lyra/src/error/command/util.rs index aad2728..760dc90 100644 --- a/lyra/src/error/command/util.rs +++ b/lyra/src/error/command/util.rs @@ -114,6 +114,11 @@ impl AutoJoinAttemptError { ), )) } + crate::error::component::connection::join::ImplConnectToError::UnrecognisedConnection(_) => { + // SAFETY: if an auto-join was performed, then the `require::in_voice(_)` call was unsuccessful, + // which is impossible as this error will only be raised if there is an unrecognised connection found. + unsafe { std::hint::unreachable_unchecked() } + }, crate::error::component::connection::join::ImplConnectToError::CheckUserAllowed(e) => { Self::from_check_user_allowed(e) } diff --git a/lyra/src/error/component/connection.rs b/lyra/src/error/component/connection.rs index 24c69ec..4338109 100644 --- a/lyra/src/error/component/connection.rs +++ b/lyra/src/error/component/connection.rs @@ -26,6 +26,7 @@ pub mod join { GatewaySend(#[from] twilight_gateway::error::ChannelError), TwilightHttp(#[from] twilight_http::Error), Lavalink(#[from] lavalink_rs::error::LavalinkError), + UnrecognisedConnection(#[from] crate::error::UnrecognisedConnection), } #[derive(thiserror::Error, Debug)] @@ -125,6 +126,7 @@ pub mod join { GatewaySend(#[from] twilight_gateway::error::ChannelError), TwilightHttp(#[from] twilight_http::Error), Lavalink(#[from] lavalink_rs::error::LavalinkError), + UnrecognisedConnection(#[from] crate::error::UnrecognisedConnection), } #[derive(thiserror::Error, Debug)] @@ -203,6 +205,13 @@ pub mod join { ResidualImplConnectToError::Lavalink(e), )), )), + ImplConnectToError::UnrecognisedConnection(e) => { + Self::Other(ResidualError::ImplJoin(ResidualImplJoinError::ConnectTo( + ResidualConnectToError::ImplConnectTo( + ResidualImplConnectToError::UnrecognisedConnection(e), + ), + ))) + } ImplConnectToError::CheckUserAllowed(e) => Self::from_check_user_allowed(e), } } @@ -266,6 +275,7 @@ pub mod leave { CheckUserOnlyIn(#[from] crate::error::command::check::UserOnlyInError), PreDisconnectCleanup(#[from] PreDisconnectCleanupError), GatewaySend(#[from] twilight_gateway::error::ChannelError), + UnrecognisedConnection(#[from] crate::error::UnrecognisedConnection), } impl Error { @@ -284,6 +294,9 @@ pub mod leave { Self::GatewaySend(e) => { NotInVoiceMatchedError::Other(ResidualError::GatewaySend(e)) } + Self::UnrecognisedConnection(e) => { + NotInVoiceMatchedError::Other(ResidualError::UnrecognisedConnection(e)) + } } } } @@ -300,6 +313,7 @@ pub mod leave { CheckUserOnlyIn(#[from] crate::error::command::check::UserOnlyInError), PreDisconnectCleanupError(#[from] PreDisconnectCleanupError), GatewaySend(#[from] twilight_gateway::error::ChannelError), + UnrecognisedConnection(#[from] crate::error::UnrecognisedConnection), } } diff --git a/lyra/src/error/lavalink.rs b/lyra/src/error/lavalink.rs index a556ca2..05f32d9 100644 --- a/lyra/src/error/lavalink.rs +++ b/lyra/src/error/lavalink.rs @@ -8,6 +8,7 @@ pub struct NoPlayerError; #[error("processing lavalink event failed: {:?}", .0)] pub enum ProcessError { Lavalink(#[from] lavalink_rs::error::LavalinkError), + TwilightHttp(#[from] twilight_http::Error), } pub type ProcessResult = Result<(), ProcessError>; diff --git a/lyra/src/gateway/interaction.rs b/lyra/src/gateway/interaction.rs index 1807003..836859e 100644 --- a/lyra/src/gateway/interaction.rs +++ b/lyra/src/gateway/interaction.rs @@ -15,6 +15,7 @@ use crate::{ command::{ macros::{bad, cant, caut, crit, err, hid, nope, note, out_upd, sus, sus_fol}, model::NonPingInteraction, + require, util::MessageLinkAware, AutocompleteCtx, MessageCtx, SlashCtx, }, @@ -129,9 +130,13 @@ impl Context { }; if let Some(guild_id) = inner_guild_id { - if let Some(mut connection) = bot.lavalink().get_connection_mut(guild_id) { + let lavalink = bot.lavalink(); + if let Some(mut connection) = lavalink.get_connection_mut(guild_id) { connection.text_channel_id = channel_id; } + if let Ok(player) = require::player(&(lavalink, guild_id)) { + player.data().write().await.set_text_channel_id(channel_id); + } } let Err(source) = result else { @@ -293,6 +298,12 @@ async fn match_error( let play = InteractionClient::mention_command::(); caut!(format!("Not yet played anything. Use {} first.", play), i); } + Fe::UnrecognisedConnection => { + crit!( + "The bot wasn't disconnected properly last session. Please wait for it to automatically leave the voice channel, then try again.", + i + ); + } _ => { err!(format!("Something went wrong: ```rs\n{error:#?}```"), ?i); Err(ProcessError::CommandExecute { diff --git a/lyra/src/lavalink.rs b/lyra/src/lavalink.rs index dbf0ed8..d176b3e 100644 --- a/lyra/src/lavalink.rs +++ b/lyra/src/lavalink.rs @@ -6,10 +6,10 @@ mod track; pub use self::{ model::{ - wait_for_with, ClientAndGuildIdAware, ClientAware, Connection, CorrectPlaylistInfo, - CorrectTrackInfo, DelegateMethods, Event, EventRecvResult, IndexerType, Lavalink, - OwnedPlayerData, Pitch, PlayerDataRead, PlayerDataWrite, Queue, QueueItem, RepeatMode, - UnwrappedPlayerData, UnwrappedPlayerInfoUri, + wait_for_with, ClientAndGuildIdAware, ClientAware, ClientData, Connection, + CorrectPlaylistInfo, CorrectTrackInfo, DelegateMethods, Event, EventRecvResult, + IndexerType, Lavalink, OwnedPlayerData, Pitch, PlayerDataRead, PlayerDataWrite, Queue, + QueueItem, RepeatMode, UnwrappedData, UnwrappedPlayerInfoUri, }, process::handlers, }; diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index fd3b40d..c5a5465 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -14,11 +14,16 @@ use lavalink_rs::{ }; use lyra_ext::time::track_timestamp::TrackTimestamp; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use twilight_model::id::{marker::GuildMarker, Id}; +use twilight_http::Client; +use twilight_model::id::{ + marker::{ChannelMarker, GuildMarker}, + Id, +}; use crate::{ command::require::{InVoice, PartialInVoice}, - core::r#const, + core::{model::HttpAware, r#const}, + error::UnrecognisedConnection, gateway::GuildIdAware, }; @@ -37,6 +42,7 @@ pub type OwnedPlayerData = Arc; pub type PlayerDataRead<'a> = RwLockReadGuard<'a, RawPlayerData>; pub type PlayerDataWrite<'a> = RwLockWriteGuard<'a, RawPlayerData>; +pub type OwnedClientData = Arc; pub trait ClientAware { fn lavalink(&self) -> &Lavalink; } @@ -55,16 +61,32 @@ pub trait ClientAndGuildIdAware: ClientAware + GuildIdAware { } } +type ClientRefAndGuildId<'a> = (&'a Lavalink, Id); + +impl ClientAware for ClientRefAndGuildId<'_> { + fn lavalink(&self) -> &Lavalink { + self.0 + } +} +impl GuildIdAware for ClientRefAndGuildId<'_> { + fn guild_id(&self) -> Id { + self.1 + } +} +impl ClientAndGuildIdAware for ClientRefAndGuildId<'_> {} + pub struct RawPlayerData { queue: Queue, volume: NonZeroU16, pitch: Pitch, track_timestamp: TrackTimestamp, + text_channel_id: Id, } impl RawPlayerData { - pub fn new() -> Self { + pub fn new(text_channel_id: Id) -> Self { Self { + text_channel_id, // SAFETY: `100` is non-zero volume: unsafe { NonZeroU16::new_unchecked(100) }, pitch: Pitch::new(), @@ -124,6 +146,14 @@ impl RawPlayerData { pub fn set_speed(&mut self, multiplier: f64) { self.track_timestamp.set_speed(multiplier); } + + pub const fn text_channel_id(&self) -> Id { + self.text_channel_id + } + + pub fn set_text_channel_id(&mut self, text_channel_id: Id) { + self.text_channel_id = text_channel_id; + } } pub struct Lavalink { @@ -270,9 +300,10 @@ pub trait DelegateMethods { async fn new_player( &self, guild_id: impl Into + Send + Copy, + channel_id: Id, ) -> LavalinkResult { let info = self.get_connection_info_traced(guild_id).await?; - let data = Arc::new(RwLock::new(RawPlayerData::new())); + let data = Arc::new(RwLock::new(RawPlayerData::new(channel_id))); let player = self .create_player_context_with_data(guild_id, info, data) .await?; @@ -307,6 +338,23 @@ impl Lavalink { self.connections.get(&guild_id) } + #[inline] + pub fn try_get_connection( + &self, + guild_id: Id, + ) -> Result { + self.get_connection(guild_id).ok_or(UnrecognisedConnection) + } + + #[inline] + pub fn try_get_connection_mut( + &self, + guild_id: Id, + ) -> Result { + self.get_connection_mut(guild_id) + .ok_or(UnrecognisedConnection) + } + pub fn connection_from(&self, cx: &impl GetConnection) -> ConnectionRef { // SAFETY: because the caller has an instance of `InVoice`, // this proves that there is a voice connection currently. @@ -380,17 +428,27 @@ impl DelegateMethods for LavalinkClient { } } -pub trait UnwrappedPlayerData { - fn data_unwrapped(&self) -> OwnedPlayerData; +pub trait UnwrappedData { + type Data; + fn data_unwrapped(&self) -> Self::Data; } -impl UnwrappedPlayerData for PlayerContext { - fn data_unwrapped(&self) -> OwnedPlayerData { +impl UnwrappedData for PlayerContext { + type Data = OwnedPlayerData; + fn data_unwrapped(&self) -> Self::Data { // SAFETY: Player data exists of type `Arc>` unsafe { self.data().unwrap_unchecked() } } } +impl UnwrappedData for LavalinkClient { + type Data = OwnedClientData; + fn data_unwrapped(&self) -> Self::Data { + // SAFETY: Lavalink data exists of type `Arc` + unsafe { self.data().unwrap_unchecked() } + } +} + pub trait UnwrappedPlayerInfoUri { fn into_uri_unwrapped(self) -> String; fn uri_unwrapped(&self) -> &str; @@ -413,3 +471,19 @@ pub trait GetConnection: GuildIdAware {} impl GetConnection for InVoice<'_> {} impl GetConnection for PartialInVoice {} + +pub struct ClientData { + http: Arc, +} + +impl HttpAware for ClientData { + fn http(&self) -> &Client { + &self.http + } +} + +impl ClientData { + pub fn new(http: Arc) -> Self { + Self { http } + } +} diff --git a/lyra/src/lavalink/track.rs b/lyra/src/lavalink/track.rs index 52a22ae..946d9c8 100644 --- a/lyra/src/lavalink/track.rs +++ b/lyra/src/lavalink/track.rs @@ -5,8 +5,9 @@ use lavalink_rs::{ }; use crate::{ + core::model::HttpAware, error::lavalink::ProcessResult, - lavalink::{model::CorrectTrackInfo, UnwrappedPlayerData}, + lavalink::{model::CorrectTrackInfo, UnwrappedData}, }; #[tracing::instrument(err, skip_all, name = "track_start")] @@ -71,17 +72,55 @@ async fn impl_end(lavalink: LavalinkClient, _: String, event: &TrackEnd) -> Proc } #[tracing::instrument(err, skip_all, name = "track_exception")] -async fn impl_exception(_: LavalinkClient, _: String, event: &TrackException) -> ProcessResult { +async fn impl_exception( + lavalink: LavalinkClient, + _: String, + event: &TrackException, +) -> ProcessResult { + let guild_id = event.guild_id; tracing::error!(?event, "track exception"); - Ok(()) // TODO: handle track exception + let Some(player) = lavalink.get_player_context(guild_id) else { + return Ok(()); + }; + + let channel_id = { + let data = player.data_unwrapped(); + let data_r = data.read().await; + data_r.text_channel_id() + }; + + lavalink + .data_unwrapped() + .http() + .create_message(channel_id) + .content(&format!( + "๐Ÿ’”**`ใƒผ`** ~~`{}`~~ `(Error playing this track)`", + event.track.info.title() + )) + .await?; + + Ok(()) } #[tracing::instrument(err, skip_all, name = "track_stuck")] -async fn impl_stuck(_: LavalinkClient, _: String, event: &TrackStuck) -> ProcessResult { +async fn impl_stuck(lavalink: LavalinkClient, _: String, event: &TrackStuck) -> ProcessResult { + let guild_id = event.guild_id; tracing::warn!(?event, "track stuck"); - Ok(()) // TODO: handle track stuck + let Some(player) = lavalink.get_player_context(guild_id) else { + return Ok(()); + }; + + let channel_id = player.data_unwrapped().read().await.text_channel_id(); + lavalink + .data_unwrapped() + .http() + .create_message(channel_id) + .content("๐ŸŒ€ Playback interrupted. Please wait or try using the bot again later.") + .await?; + + Ok(()) } #[hook] diff --git a/lyra/src/runner.rs b/lyra/src/runner.rs index 2b577ca..720b1a1 100644 --- a/lyra/src/runner.rs +++ b/lyra/src/runner.rs @@ -29,7 +29,11 @@ use twilight_model::{ id::{marker::UserMarker, Id}, }; -use crate::{core::r#const::metadata::BANNER, lavalink::handlers, LavalinkAware}; +use crate::{ + core::r#const::metadata::BANNER, + lavalink::{handlers, ClientData}, + LavalinkAware, +}; use super::{ core::{ @@ -52,11 +56,12 @@ const INTENTS: Intents = Intents::GUILDS.union(Intents::GUILD_VOICE_STATES); static SHUTDOWN: AtomicBool = AtomicBool::new(false); -fn build_http_client() -> Client { +fn build_http_client() -> Arc { ClientBuilder::default() .default_allowed_mentions(AllowedMentions::default()) .token(CONFIG.token.to_owned()) .build() + .into() } fn build_shard_config() -> ShardConfig { @@ -93,7 +98,8 @@ pub async fn start() -> Result<(), StartError> { let http = build_http_client(); let user_id = http.current_user().await?.model().await?.id; - let lavalink = build_lavalink_client(user_id).await; + let data = ClientData::new(http.clone()); + let lavalink = build_lavalink_client(user_id, data).await; let shards = build_and_split_shards(&http).await?; let shards_len = shards.len(); @@ -122,7 +128,7 @@ async fn build_and_split_shards( } #[tracing::instrument(skip_all, name = "lavalink")] -async fn build_lavalink_client(user_id: Id) -> Lavalink { +async fn build_lavalink_client(user_id: Id, data: ClientData) -> Lavalink { let events = handlers(); let nodes = Vec::from([lavalink_rs::node::NodeBuilder { @@ -132,7 +138,8 @@ async fn build_lavalink_client(user_id: Id) -> Lavalink { ..Default::default() }]); - let client = LavalinkClient::new(events, nodes, NodeDistributionStrategy::new()).await; + let strategy = NodeDistributionStrategy::new(); + let client = LavalinkClient::new_with_data(events, nodes, strategy, data.into()).await; client.into() } From a9f83209b97005267343604b2abc8baa2e0f2c9d Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Sun, 8 Sep 2024 03:44:50 +0700 Subject: [PATCH 09/12] Implement response deferrals, Updated dependencies --- Cargo.lock | 350 +++++++++------------ flake.lock | 18 +- lyra/Cargo.toml | 12 +- lyra/src/command/macros.rs | 137 ++++---- lyra/src/command/model/ctx.rs | 11 +- lyra/src/command/model/ctx/command_data.rs | 3 + lyra/src/command/model/ctx/message.rs | 68 ++-- lyra/src/component/config/access/view.rs | 2 +- lyra/src/component/connection/join.rs | 6 +- lyra/src/component/queue/play.rs | 9 +- lyra/src/core/model.rs | 4 +- lyra/src/core/model/interaction.rs | 166 +++++++++- lyra/src/error/command.rs | 22 +- lyra/src/error/component/connection.rs | 8 +- lyra/src/error/component/queue.rs | 2 +- lyra/src/gateway/interaction.rs | 58 ++-- lyra/src/lavalink/model.rs | 2 +- lyra_ext/src/iter/multi_interleave.rs | 2 +- 18 files changed, 524 insertions(+), 356 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f378ae5..6490294 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "ahash" version = "0.8.11" @@ -61,9 +67,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "approx" @@ -111,7 +117,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.7.4", "object", "rustc-demangle", ] @@ -187,9 +193,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fd4c6dcc3b0aea2f5c0b4b82c2b15fe39ddbc76041a310848f4706edf76bb31" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -243,9 +249,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.13" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "jobserver", "libc", @@ -314,9 +320,9 @@ checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] name = "const_panic" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b" +checksum = "7782af8f90fe69a4bb41e460abe1727d493403d8b2cc43201a3a3e906b24379f" dependencies = [ "const_panic_proc_macros", "typewit", @@ -324,13 +330,13 @@ dependencies = [ [[package]] name = "const_panic_proc_macros" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395398edf7e8b0a812e905c150c438520ab077c4f9f931556302c48dd4e8ada9" +checksum = "e15066a3ad67370e5311151e6c2574f51e80bd9a88ad8af5823d4f4ca4a230e0" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", "unicode-xid", ] @@ -352,9 +358,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -448,7 +454,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -459,7 +465,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -477,9 +483,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -517,33 +523,33 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +checksum = "cd33f37ee6a119146a1781d3356a7c26028f83d779b2e04ecd45fdc75c76877b" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +checksum = "7431fa049613920234f22c47fdc33e6cf3ee83067091ea4277a3f8c4587aae38" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "derive_builder_macro" -version = "0.20.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +checksum = "4abae7035bf79b9877b779505d8cf3749285b80c43941eda66604841889451dc" dependencies = [ "derive_builder_core", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -641,9 +647,9 @@ checksum = "dd2e7510819d6fbf51a5545c8f922716ecfb14df168a3242f7d33e0239efe6a1" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "fdeflate" @@ -656,13 +662,13 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.31" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", "libz-sys", - "miniz_oxide", + "miniz_oxide 0.8.0", ] [[package]] @@ -758,7 +764,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -876,9 +882,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ "atomic-waker", "bytes", @@ -1028,7 +1034,7 @@ dependencies = [ "hyper", "hyper-util", "rustls 0.22.4", - "rustls-native-certs", + "rustls-native-certs 0.7.3", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -1037,16 +1043,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls 0.23.12", - "rustls-native-certs", + "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -1114,9 +1120,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1177,7 +1183,7 @@ dependencies = [ "http", "http-body-util", "hyper", - "hyper-rustls 0.27.2", + "hyper-rustls 0.27.3", "hyper-util", "lavalink_rs_macros", "oneshot", @@ -1200,7 +1206,7 @@ checksum = "426ef2204d4fa6806fa6a00fdd747f7312645fcd30cf9208a5bdfeec6f3b9a41" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1214,9 +1220,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.157" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libgit2-sys" @@ -1238,9 +1244,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libsqlite3-sys" -version = "0.28.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -1249,9 +1255,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.19" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc53a7799a7496ebc9fd29f31f7df80e83c9bda5299768af5f9e59eeea74647" +checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" dependencies = [ "cc", "libc", @@ -1300,7 +1306,7 @@ dependencies = [ "color-eyre", "const-str", "const_panic", - "dashmap 6.0.1", + "dashmap 6.1.0", "dotenvy", "dotenvy_macro", "futures", @@ -1362,7 +1368,7 @@ dependencies = [ "itertools", "quote", "serde", - "syn 2.0.75", + "syn 2.0.77", "toml", ] @@ -1407,6 +1413,15 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + [[package]] name = "mio" version = "1.0.2" @@ -1593,14 +1608,14 @@ dependencies = [ "by_address", "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -1620,7 +1635,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.3", + "redox_syscall", "smallvec", "windows-targets 0.52.6", ] @@ -1663,7 +1678,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -1715,7 +1730,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.7.4", ] [[package]] @@ -1735,11 +1750,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.21.1", + "toml_edit", ] [[package]] @@ -1777,9 +1792,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1834,15 +1849,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_syscall" version = "0.5.3" @@ -1963,7 +1969,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.75", + "syn 2.0.77", "unicode-ident", ] @@ -1975,18 +1981,18 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags 2.6.0", "errno", @@ -1995,17 +2001,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.22.4" @@ -2015,7 +2010,7 @@ dependencies = [ "log", "ring", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki", "subtle", "zeroize", ] @@ -2029,31 +2024,35 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.6", + "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls-native-certs" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ - "base64 0.21.7", + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] @@ -2074,19 +2073,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.102.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustls-webpki" -version = "0.102.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" dependencies = [ "ring", "rustls-pki-types", @@ -2107,11 +2096,11 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2120,16 +2109,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "2.11.1" @@ -2164,9 +2143,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] @@ -2183,20 +2162,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -2223,7 +2202,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -2370,9 +2349,9 @@ dependencies = [ [[package]] name = "sqlformat" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" +checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" dependencies = [ "nom", "unicode_categories", @@ -2380,9 +2359,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27144619c6e5802f1380337a209d2ac1c431002dd74c6e60aebff3c506dc4f0c" +checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2393,9 +2372,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a999083c1af5b5d6c071d34a708a19ba3e02106ad82ef7bbd69f5e48266b613b" +checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" dependencies = [ "atoi", "byteorder", @@ -2418,8 +2397,8 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls 0.23.12", + "rustls-pemfile", "serde", "serde_json", "sha2", @@ -2435,22 +2414,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23217eb7d86c584b8cbe0337b9eacf12ab76fe7673c513141ec42565698bb88" +checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] name = "sqlx-macros-core" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a099220ae541c5db479c6424bdf1b200987934033c2584f79a0e1693601e776" +checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", @@ -2466,7 +2445,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.75", + "syn 2.0.77", "tempfile", "tokio", "url", @@ -2474,9 +2453,9 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5afe4c38a9b417b6a9a5eeffe7235d0a106716495536e7727d1c7f4b1ff3eba6" +checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" dependencies = [ "atoi", "base64 0.22.1", @@ -2516,9 +2495,9 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1dbb157e65f10dbe01f729339c06d239120221c9ad9fa0ba8408c4cc18ecf21" +checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" dependencies = [ "atoi", "base64 0.22.1", @@ -2554,9 +2533,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.0" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2cdd83c008a622d94499c0006d8ee5f821f36c89b7d625c900e5dc30b5c5ee" +checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" dependencies = [ "atoi", "flume", @@ -2611,9 +2590,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -2664,7 +2643,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -2738,9 +2717,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2761,7 +2740,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -2788,9 +2767,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -2806,7 +2785,7 @@ dependencies = [ "futures-util", "log", "rustls 0.22.4", - "rustls-native-certs", + "rustls-native-certs 0.7.3", "rustls-pki-types", "tokio", "tokio-rustls 0.25.0", @@ -2815,9 +2794,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2840,7 +2819,7 @@ dependencies = [ "http", "httparse", "ring", - "rustls-native-certs", + "rustls-native-certs 0.7.3", "rustls-pki-types", "sha1_smol", "simdutf8", @@ -2859,7 +2838,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.20", + "toml_edit", ] [[package]] @@ -2871,17 +2850,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.20" @@ -2892,7 +2860,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.18", + "winnow", ] [[package]] @@ -2942,7 +2910,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -3122,7 +3090,7 @@ checksum = "e6c07ac7c9d2d086291765ceecafcd52ce6dc33ab0ade02e06509eb416d88470" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] @@ -3223,9 +3191,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] name = "unicode-segmentation" @@ -3235,9 +3203,9 @@ checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" [[package]] name = "unicode_categories" @@ -3360,9 +3328,12 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "weezl" @@ -3372,11 +3343,11 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" [[package]] name = "whoami" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall", "wasite", ] @@ -3569,15 +3540,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.6.18" @@ -3605,7 +3567,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.75", + "syn 2.0.77", ] [[package]] diff --git a/flake.lock b/flake.lock index ef45a86..e2a711b 100644 --- a/flake.lock +++ b/flake.lock @@ -41,11 +41,11 @@ "pre-commit-hooks": "pre-commit-hooks" }, "locked": { - "lastModified": 1725567101, - "narHash": "sha256-gFCoG9udcJShDUxj+6KjUYB7Kuk+NAxJaMJJFMeRBJ4=", + "lastModified": 1725637114, + "narHash": "sha256-+hsiHWbqkS098soB1o4URP3frnjhoRvyVfWs6byv4Zk=", "owner": "cachix", "repo": "devenv", - "rev": "a5460083c31d190ce44c8286a7ff9b6d2ade016b", + "rev": "c31e347a96dbb7718a0279afa993752a7dfc6a39", "type": "github" }, "original": { @@ -93,11 +93,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1725604324, - "narHash": "sha256-+VgeYuaCQn5vmoH1GTYQzvVTtWxirZmdDQJKr8uLgQI=", + "lastModified": 1725690497, + "narHash": "sha256-5fT+96rV7Hx29HG+4/oBbr3V+yExKuLN2vcBcPbVBlU=", "owner": "nix-community", "repo": "fenix", - "rev": "d9afdb4465ba2f20bb73b0ff5d2c2837cafc2e14", + "rev": "4b8d964df93d1f918ee6c4f003b3548c432cc866", "type": "github" }, "original": { @@ -417,11 +417,11 @@ "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1725548942, - "narHash": "sha256-ZnF5MaOAeiiKIATYN4rrqNsnhSQOQ+Hvfg0mHLvN04Y=", + "lastModified": 1725630423, + "narHash": "sha256-gNCLk3Zg7JlAwmWbVHTH6f3+iqdeQ4fheOotCZy8x5M=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "124c7482167ff6eea4f7663c0be87ea568ccd8c6", + "rev": "08c7bbc2dbe4dcc8968484f1a0e1e6fe7a1d4f6d", "type": "github" }, "original": { diff --git a/lyra/Cargo.toml b/lyra/Cargo.toml index 4aeef86..58fee18 100644 --- a/lyra/Cargo.toml +++ b/lyra/Cargo.toml @@ -37,22 +37,22 @@ lyra_ext = { path = "../lyra_ext" } paste = "1.0.15" const-str = "0.5.7" -const_panic = { version = "0.2.8", features = ["derive"] } +const_panic = { version = "0.2.9", features = ["derive"] } bitflags = "2.6.0" -dashmap = "6.0.1" +dashmap = "6.1.0" dotenvy = "0.15.7" dotenvy_macro = "0.15.7" thiserror = "1.0.63" color-eyre = "0.6.3" futures = "0.3.30" -tokio = { version = "1.39.3", features = [ +tokio = { version = "1.40.0", features = [ "sync", "signal", "rt-multi-thread", "macros", ] } -serde = "1.0.208" -serde_json = "1.0.125" +serde = "1.0.210" +serde_json = "1.0.128" regex = "1.10.6" linkify = "0.10.0" fuzzy-matcher = "0.3.7" @@ -62,7 +62,7 @@ tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } rand = "0.8.5" itertools = "0.13.0" rayon = "1.10.0" -sqlx = { version = "0.8.0", features = ["postgres", "runtime-tokio-rustls"] } +sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio-rustls"] } mixbox = "2.0.0" lavalink-rs = { version = "0.13.0", features = ["twilight16"] } aho-corasick = "1.1.3" diff --git a/lyra/src/command/macros.rs b/lyra/src/command/macros.rs index ffe4057..27a7804 100644 --- a/lyra/src/command/macros.rs +++ b/lyra/src/command/macros.rs @@ -1,9 +1,11 @@ macro_rules! out { ($cnt: expr, $ctx: expr) => { + use crate::core::model::AcknowledgementAware; $ctx.respond($cnt).await?; return Ok(()); }; ($cnt: expr, ?$ctx: expr) => { + use crate::core::model::AcknowledgementAware; $ctx.respond($cnt).await?; }; } @@ -20,37 +22,25 @@ macro_rules! out { macro_rules! out_or_fol { ($cnt: expr, $ctx: expr) => { - if $ctx.acknowledged() { - $ctx.followup(&$cnt).await?; - return Ok(()); - } - $ctx.respond($cnt).await?; + use crate::core::model::AcknowledgementAware; + $ctx.respond_or_followup($cnt).await?; return Ok(()); }; ($cnt: expr, ?$ctx: expr) => { - if $ctx.acknowledged() { - $ctx.followup(&$cnt).await?; - } else { - $ctx.respond($cnt).await?; - } + use crate::core::model::AcknowledgementAware; + $ctx.respond_or_followup($cnt).await?; }; } macro_rules! out_or_upd { ($cnt: expr, $ctx: expr) => { - if $ctx.acknowledged() { - $ctx.update_no_components_embeds(&$cnt).await?; - return Ok(()); - } - $ctx.respond($cnt).await?; + use crate::core::model::AcknowledgementAware; + $ctx.respond_or_update($cnt).await?; return Ok(()); }; ($cnt: expr, ?$ctx: expr) => { - if $ctx.acknowledged() { - $ctx.update_no_components_embeds(&$cnt).await?; - } else { - $ctx.respond($cnt).await?; - } + use crate::core::model::AcknowledgementAware; + $ctx.respond_or_update($cnt).await?; }; } @@ -66,42 +56,43 @@ macro_rules! out_upd { macro_rules! hid { ($cnt: expr, $ctx: expr) => { - $ctx.ephem($cnt).await?; + #[allow(unused_imports)] + use crate::core::model::AcknowledgementAware; + $ctx.respond_ephemeral($cnt).await?; return Ok(()); }; ($cnt: expr, ?$ctx: expr) => { - $ctx.ephem($cnt).await?; + #[allow(unused_imports)] + use crate::core::model::AcknowledgementAware; + $ctx.respond_ephemeral($cnt).await?; }; } macro_rules! hid_fol { ($cnt: expr, $ctx: expr) => { - $ctx.followup_ephem(&$cnt).await?; + #[allow(unused_imports)] + use crate::core::model::AcknowledgementAware; + $ctx.followup_ephemeral($cnt).await?; + return Ok(()); + }; + ($cnt: expr, ?$ctx: expr) => {{ + use crate::core::model::AcknowledgementAware; + $ctx.followup_ephemeral($cnt).await? + }}; +} + +macro_rules! hid_or_fol { + ($cnt: expr, $ctx: expr) => { + use crate::core::model::AcknowledgementAware; + $ctx.respond_ephemeral_or_followup($cnt).await?; return Ok(()); }; ($cnt: expr, ?$ctx: expr) => { - $ctx.followup_ephem(&$cnt).await? + use crate::core::model::AcknowledgementAware; + $ctx.respond_ephemeral_or_followup($cnt).await?; }; } -// macro_rules! hid_or_fol { -// ($cnt: expr, $ctx: expr) => { -// if $ctx.acknowledged() { -// $ctx.followup_ephem(&$cnt).await?; -// return Ok(()); -// } -// $ctx.ephem($cnt).await?; -// return Ok(()); -// }; -// ($cnt: expr, ?$ctx: expr) => { -// if $ctx.acknowledged() { -// $ctx.followup_ephem(&$cnt).await?; -// } else { -// $ctx.ephem($cnt).await?; -// } -// }; -// } - macro_rules! generate_hid_variants { ($($name: ident => $emoji: ident),+$(,)?) => { $( @@ -138,30 +129,30 @@ macro_rules! generate_hid_fol_variants { } } -// macro_rules! generate_hid_or_fol_variants { -// ($($name: ident => $emoji: ident),+$(,)?) => { -// $( -// macro_rules! $name { -// ($cnt: expr, $ctx: expr) => { -// use crate::core::consts::exit_code; -// hid_or_fol!(format!("{} {}", exit_code::$emoji, $cnt), $ctx); -// }; -// ($cnt: expr, ?$ctx: expr) => { -// use crate::core::consts::exit_code; -// hid_or_fol!(format!("{} {}", exit_code::$emoji, $cnt), ?$ctx); -// }; -// } -// )+ - -// pub(crate) use {$($name,)+}; -// } -// } +macro_rules! generate_hid_or_fol_variants { + ($($name: ident => $emoji: ident),+$(,)?) => { + $( + macro_rules! $name { + ($cnt: expr, $ctx: expr) => { + use crate::core::r#const::exit_code; + crate::command::macros::hid_or_fol!(format!("{} {}", exit_code::$emoji, $cnt), $ctx); + }; + ($cnt: expr, ?$ctx: expr) => { + use crate::core::r#const::exit_code; + crate::command::macros::hid_or_fol!(format!("{} {}", exit_code::$emoji, $cnt), ?$ctx); + }; + } + )+ + + pub(crate) use {$($name,)+}; + } +} generate_hid_variants! { note => NOTICE, sus => DUBIOUS, caut => WARNING, - what => NOT_FOUND, + // what => NOT_FOUND, bad => INVALID, nope => PROHIBITED, cant => FORBIDDEN, @@ -181,16 +172,16 @@ generate_hid_fol_variants! { // crit_fol => UNKNOWN_ERROR } -// generate_hid_or_fol_variants! { -// note_or_fol => NOTICE, -// dub_or_fol => DUBIOUS, -// caut_or_fol => WARNING, -// miss_or_fol => NOT_FOUND, -// bad_or_fol => INVALID, -// nope_or_fol => PROHIBITED, -// cant_or_fol => FORBIDDEN, -// err_or_fol => KNOWN_ERROR, -// crit_or_fol => UNKNOWN_ERROR -// } +generate_hid_or_fol_variants! { + // note_or_fol => NOTICE, + // dub_or_fol => DUBIOUS, + // caut_or_fol => WARNING, + what_or_fol => NOT_FOUND, + bad_or_fol => INVALID, + nope_or_fol => PROHIBITED, + cant_or_fol => FORBIDDEN, + // err_or_fol => KNOWN_ERROR, + crit_or_fol => UNKNOWN_ERROR +} -pub(crate) use {hid, hid_fol, out, out_or_fol, out_or_upd, out_upd}; +pub(crate) use {hid, hid_fol, hid_or_fol, out, out_or_fol, out_or_upd, out_upd}; diff --git a/lyra/src/command/model/ctx.rs b/lyra/src/command/model/ctx.rs index 84df11c..8c7a929 100644 --- a/lyra/src/command/model/ctx.rs +++ b/lyra/src/command/model/ctx.rs @@ -6,6 +6,7 @@ mod modal; use std::{marker::PhantomData, sync::Arc}; +use tokio::sync::oneshot; use twilight_cache_inmemory::{model::CachedMember, InMemoryCache, Reference}; use twilight_gateway::{Latency, MessageSender}; use twilight_http::Client as HttpClient; @@ -88,6 +89,7 @@ where sender: MessageSender, data: Option, acknowledged: bool, + acknowledgement: Option>, kind: PhantomData Of>, location: PhantomData In>, } @@ -104,6 +106,7 @@ impl TryFrom> for Ctx { sender: value.sender, data: value.data, acknowledged: value.acknowledged, + acknowledgement: value.acknowledgement, kind: value.kind, location: PhantomData:: GuildMarker>, }) @@ -120,6 +123,7 @@ impl Ctx { location: self.location, data: None, acknowledged: false, + acknowledgement: None, kind: PhantomData:: ModalMarker>, } } @@ -128,12 +132,11 @@ impl Ctx { &self.latency } - pub const fn acknowledged(&self) -> bool { - self.acknowledged - } - pub fn acknowledge(&mut self) { self.acknowledged = true; + if let Some(tx) = std::mem::take(&mut self.acknowledgement) { + let _ = tx.send(()); + } } pub fn db(&self) -> &sqlx::Pool { diff --git a/lyra/src/command/model/ctx/command_data.rs b/lyra/src/command/model/ctx/command_data.rs index 56869e4..9d8f50a 100644 --- a/lyra/src/command/model/ctx/command_data.rs +++ b/lyra/src/command/model/ctx/command_data.rs @@ -1,5 +1,6 @@ use std::{hint::unreachable_unchecked, marker::PhantomData, sync::Arc}; +use tokio::sync::oneshot; use twilight_gateway::{Latency, MessageSender}; use twilight_model::{ application::interaction::application_command::{ @@ -26,6 +27,7 @@ impl Ctx { bot: OwnedBotState, latency: Latency, sender: MessageSender, + acknowledgement: oneshot::Sender<()>, ) -> Self { Self { data: Some(PartialInteractionData::Command(PartialCommandData::new( @@ -36,6 +38,7 @@ impl Ctx { latency, sender, acknowledged: false, + acknowledgement: Some(acknowledgement), kind: PhantomData:: T>, location: PhantomData, } diff --git a/lyra/src/command/model/ctx/message.rs b/lyra/src/command/model/ctx/message.rs index fbe68d0..9238020 100644 --- a/lyra/src/command/model/ctx/message.rs +++ b/lyra/src/command/model/ctx/message.rs @@ -8,10 +8,10 @@ use crate::{core::model::MessageResponse, error::command::FollowupError}; use super::{ AppCtxKind, AppCtxMarker, ComponentMarker, Ctx, Kind, Location, ModalMarker, RespondResult, + UnitRespondResult, }; type MessageRespondResult = RespondResult; -type MessageFollowupResult = Result; pub trait RespondVia: Kind {} impl RespondVia for AppCtxMarker {} @@ -32,20 +32,7 @@ impl Ctx { Ok(response?) } - pub async fn respond(&mut self, content: impl Into + Send) -> MessageRespondResult { - let data = Self::base_response_data_builder().content(content).build(); - self.respond_with(Some(data)).await - } - - pub async fn update_no_components_embeds(&self, content: &str) -> MessageFollowupResult { - Ok(self - .interface() - .await? - .update_no_components_embeds(content) - .await?) - } - - pub async fn respond_embeds_only( + pub async fn respond_embeds( &mut self, embeds: impl IntoIterator + Send, ) -> MessageRespondResult { @@ -65,7 +52,33 @@ impl Ctx { self.respond_with(Some(data)).await } - pub async fn ephem(&mut self, content: impl Into + Send) -> MessageRespondResult { + pub async fn defer(&mut self) -> UnitRespondResult { + self.acknowledge(); + Ok(self.interface().await?.defer().await?) + } +} + +impl crate::core::model::AcknowledgementAware for Ctx { + type FollowupError = FollowupError; + type RespondError = crate::error::command::RespondError; + type RespondOrFollowupError = crate::error::command::RespondOrFollowupError; + + fn acknowledged(&self) -> bool { + self.acknowledged + } + + async fn respond( + &mut self, + content: impl Into + Send, + ) -> Result { + let data = Self::base_response_data_builder().content(content).build(); + self.respond_with(Some(data)).await + } + + async fn respond_ephemeral( + &mut self, + content: impl Into + Send, + ) -> MessageRespondResult { let data = Self::base_response_data_builder() .content(content) .flags(MessageFlags::EPHEMERAL) @@ -73,11 +86,28 @@ impl Ctx { self.respond_with(Some(data)).await } - pub async fn followup(&self, content: &str) -> MessageFollowupResult { + async fn update( + &self, + content: impl Into + Send, + ) -> Result { + Ok(self + .interface() + .await? + .update_no_components_embeds(content) + .await?) + } + + async fn followup( + &self, + content: impl Into + Send, + ) -> Result { Ok(self.interface().await?.followup(content).await?) } - pub async fn followup_ephem(&self, content: &str) -> MessageFollowupResult { - Ok(self.interface().await?.followup_ephem(content).await?) + async fn followup_ephemeral( + &self, + content: impl Into + Send, + ) -> Result { + Ok(self.interface().await?.followup_ephemeral(content).await?) } } diff --git a/lyra/src/component/config/access/view.rs b/lyra/src/component/config/access/view.rs index c12dd5f..4758a6a 100644 --- a/lyra/src/component/config/access/view.rs +++ b/lyra/src/component/config/access/view.rs @@ -31,7 +31,7 @@ impl BotSlashCommand for View { ); let embed = embed.validate()?.build(); - ctx.respond_embeds_only([embed]).await?; + ctx.respond_embeds([embed]).await?; Ok(()) } } diff --git a/lyra/src/component/connection/join.rs b/lyra/src/component/connection/join.rs index 5acfdd7..c470de7 100644 --- a/lyra/src/component/connection/join.rs +++ b/lyra/src/component/connection/join.rs @@ -18,7 +18,7 @@ use twilight_model::{ use crate::{ command::{ check, - macros::{bad, cant, nope, note, note_fol, out, sus_fol}, + macros::{bad, cant, nope, note, note_fol, out_or_fol, sus_fol}, model::{BotSlashCommand, CtxKind, GuildCtx, RespondViaMessage}, require::{self, InVoiceCachedVoiceState}, SlashCtx, @@ -328,7 +328,7 @@ async fn handle_response( let (joined, empty) = match response { Response::Joined { voice, empty } => { let stage = matches!(voice.kind, JoinedChannelType::Stage); - out!( + out_or_fol!( stage_fmt(&format!("๐Ÿ–‡๏ธ {}", voice.id.mention()), stage), ?ctx ); @@ -336,7 +336,7 @@ async fn handle_response( } Response::Moved { from, to, empty } => { let stage = matches!(to.kind, JoinedChannelType::Stage); - out!( + out_or_fol!( stage_fmt( &format!("๏ธ๐Ÿ“Ž๐Ÿ–‡๏ธ ~~{}~~ โžœ __{}__", from.mention(), to.id.mention()), stage, diff --git a/lyra/src/component/queue/play.rs b/lyra/src/component/queue/play.rs index fc0edc3..b6af833 100644 --- a/lyra/src/component/queue/play.rs +++ b/lyra/src/component/queue/play.rs @@ -26,7 +26,7 @@ use twilight_util::builder::command::CommandBuilder; use crate::{ command::{ - macros::{bad, crit, out_or_fol, what}, + macros::{bad, bad_or_fol, crit_or_fol, out_or_fol, what_or_fol}, model::{BotAutocomplete, BotMessageCommand, BotSlashCommand, GuildCtx, RespondViaMessage}, require, util, AutocompleteCtx, MessageCtx, SlashCtx, }, @@ -323,6 +323,7 @@ async fn play( ctx: &mut GuildCtx, queries: impl IntoIterator> + Send, ) -> Result<(), play::Error> { + ctx.defer().await?; let load_ctx = LoadTrackContext::new_via(ctx); match load_ctx.process_many(queries).await { Ok(results) => { @@ -401,13 +402,13 @@ async fn play( Err(e) => match e { LoadTrackProcessManyError::Query(query) => match query { QueryError::LoadFailed(LoadFailedError(query)) => { - crit!(format!("Failed to load tracks for query: `{}`", query), ctx); + crit_or_fol!(format!("Failed to load tracks for query: `{}`", query), ctx); } QueryError::NoMatches(query) => { - what!(format!("No matches found for query: `{}`", query), ctx); + what_or_fol!(format!("No matches found for query: `{}`", query), ctx); } QueryError::SearchResult(query) => { - bad!( + bad_or_fol!( format!( "Given query is not a URL: `{}`. Try using the command's autocomplete to search for tracks.", query diff --git a/lyra/src/core/model.rs b/lyra/src/core/model.rs index b706796..cde596a 100644 --- a/lyra/src/core/model.rs +++ b/lyra/src/core/model.rs @@ -24,8 +24,8 @@ use twilight_standby::Standby; use crate::{error::core::DeserializeBodyFromHttpError, lavalink::Lavalink, LavalinkAware}; pub use self::interaction::{ - Client as InteractionClient, Interface as InteractionInterface, MessageResponse, - UnitFollowupResult, UnitRespondResult, + AcknowledgementAware, Client as InteractionClient, Interface as InteractionInterface, + MessageResponse, UnitFollowupResult, UnitRespondResult, }; pub struct Config { diff --git a/lyra/src/core/model/interaction.rs b/lyra/src/core/model/interaction.rs index 6d40d90..6f64f8a 100644 --- a/lyra/src/core/model/interaction.rs +++ b/lyra/src/core/model/interaction.rs @@ -90,13 +90,15 @@ impl Interface<'_> { self.inner.0.update_response(self.interaction_token()) } - pub async fn update_no_components_embeds(&self, content: &str) -> MessageFollowupResult { - Ok(self - .update() + pub async fn update_no_components_embeds( + &self, + content: impl Into + Send, + ) -> MessageRespondResult { + self.update() .components(None) .embeds(None) - .content(Some(content)) - .await?) + .content(Some(&content.into())) + .await } pub async fn update_message_embeds_only( @@ -107,7 +109,15 @@ impl Interface<'_> { Ok(self.update_message_with(Some(data)).await?) } - pub async fn ephem(&self, content: impl Into + Send) -> MessageRespondResult { + pub async fn respond(&self, content: impl Into + Send) -> MessageRespondResult { + let data = Self::base_response_data_builder().content(content).build(); + self.respond_with(Some(data)).await + } + + pub async fn respond_ephemeral( + &self, + content: impl Into + Send, + ) -> MessageRespondResult { let data = Self::base_response_data_builder() .content(content) .flags(MessageFlags::EPHEMERAL) @@ -115,22 +125,25 @@ impl Interface<'_> { self.respond_with(Some(data)).await } - pub async fn followup(&self, content: &str) -> MessageFollowupResult { + pub async fn followup(&self, content: impl Into + Send) -> MessageFollowupResult { Ok(self .inner .0 .create_followup(self.interaction_token()) - .content(content) + .content(&content.into()) .await?) } - pub async fn followup_ephem(&self, content: &str) -> MessageFollowupResult { + pub async fn followup_ephemeral( + &self, + content: impl Into + Send, + ) -> MessageFollowupResult { Ok(self .inner .0 .create_followup(self.interaction_token()) .flags(MessageFlags::EPHEMERAL) - .content(content) + .content(&content.into()) .await?) } @@ -190,6 +203,36 @@ impl Interface<'_> { Ok(()) } + async fn defer_as(&self, ephemeral: bool) -> UnitRespondResult { + let mut data = Self::base_response_data_builder(); + if ephemeral { + data = data.flags(MessageFlags::EPHEMERAL); + } + + self.inner + .0 + .create_response( + self.interaction_id, + self.interaction_token(), + &InteractionResponse { + kind: InteractionResponseType::DeferredChannelMessageWithSource, + data: data.build().into(), + }, + ) + .await?; + Ok(()) + } + + #[inline] + pub async fn defer(&self) -> UnitRespondResult { + self.defer_as(false).await + } + + #[inline] + pub async fn defer_ephem(&self) -> UnitRespondResult { + self.defer_as(true).await + } + pub async fn update_followup( &self, message_id: Id, @@ -204,6 +247,109 @@ impl Interface<'_> { } } +pub trait AcknowledgementAware { + type FollowupError; + type RespondError; + type RespondOrFollowupError: From + From; + + fn acknowledged(&self) -> bool; + async fn respond( + &mut self, + content: impl Into + Send, + ) -> Result; + async fn respond_ephemeral( + &mut self, + content: impl Into + Send, + ) -> Result; + async fn update( + &self, + content: impl Into + Send, + ) -> Result; + async fn followup( + &self, + content: impl Into + Send, + ) -> Result; + async fn followup_ephemeral( + &self, + content: impl Into + Send, + ) -> Result; + + async fn respond_or_update( + &mut self, + content: impl Into + Send, + ) -> Result { + if self.acknowledged() { + return self.update(&content.into()).await; + } + self.respond(content).await + } + async fn respond_or_followup( + &mut self, + content: impl Into + Send, + ) -> Result { + if self.acknowledged() { + return Ok(self.followup(&content.into()).await?); + } + + Ok(self.respond(content).await?) + } + async fn respond_ephemeral_or_followup( + &mut self, + content: impl Into + Send, + ) -> Result { + if self.acknowledged() { + return Ok(self.followup_ephemeral(&content.into()).await?); + } + + Ok(self.respond_ephemeral(content).await?) + } +} + +impl AcknowledgementAware for (Interface<'_>, bool) { + type FollowupError = crate::error::core::FollowupError; + type RespondError = twilight_http::Error; + type RespondOrFollowupError = crate::error::core::FollowupError; + + fn acknowledged(&self) -> bool { + self.1 + } + + async fn respond_ephemeral( + &mut self, + content: impl Into + Send, + ) -> Result { + self.0.respond_ephemeral(content).await + } + + async fn respond( + &mut self, + content: impl Into + Send, + ) -> Result { + self.0.respond(content).await + } + + async fn update( + &self, + content: impl Into + Send, + ) -> Result { + self.0.update_no_components_embeds(content).await + } + + async fn followup( + &self, + content: impl Into + Send, + ) -> Result { + self.0.followup(content).await + } + + async fn followup_ephemeral( + &self, + content: impl Into + Send, + ) -> Result { + self.0.followup_ephemeral(content).await + } +} + impl<'a> Client<'a> { pub const fn new(client: twilight_http::client::InteractionClient<'a>) -> Self { Self(client) diff --git a/lyra/src/error/command.rs b/lyra/src/error/command.rs index 0df9ad3..43598db 100644 --- a/lyra/src/error/command.rs +++ b/lyra/src/error/command.rs @@ -18,6 +18,13 @@ pub enum FollowupError { Followup(#[from] super::core::FollowupError), } +#[derive(thiserror::Error, Debug)] +#[error("creating a response or followup failed: {}", 0)] +pub enum RespondOrFollowupError { + Respond(#[from] RespondError), + Followup(#[from] FollowupError), +} + #[derive(thiserror::Error, Debug)] #[error("command failed: {}", .0)] pub enum Error { @@ -243,6 +250,13 @@ impl<'a> Fe<'a> { } } + const fn from_respond_or_followup(error: &'a RespondOrFollowupError) -> Self { + match error { + RespondOrFollowupError::Respond(e) => Self::from_respond(e), + RespondOrFollowupError::Followup(e) => Self::from_followup(e), + } + } + const fn from_prompt_for_confirmation(error: &'a util::PromptForConfirmationError) -> Self { match error { util::PromptForConfirmationError::StandbyCanceled(_) => Self::StandbyCanceled, @@ -342,8 +356,8 @@ impl<'a> Fe<'a> { super::component::connection::join::HandleResponseError::DeserializeBody(_) => { Self::DeserializeBody } - super::component::connection::join::HandleResponseError::Respond(e) => { - Self::from_respond(e) + super::component::connection::join::HandleResponseError::RespondOrFollowup(e) => { + Self::from_respond_or_followup(e) } super::component::connection::join::HandleResponseError::Followup(e) => { Self::from_followup(e) @@ -490,7 +504,9 @@ impl<'a> Fe<'a> { Self::from_require_unsuppressed_error(e) } super::component::queue::play::Error::Respond(e) => Self::from_respond(e), - super::component::queue::play::Error::Followup(e) => Self::from_followup(e), + super::component::queue::play::Error::RespondOrFollowup(e) => { + Self::from_respond_or_followup(e) + } super::component::queue::play::Error::AutoJoinOrCheckInVoiceWithUser(e) => { Self::from_auto_join_or_check_in_voice_with_user(e) } diff --git a/lyra/src/error/component/connection.rs b/lyra/src/error/component/connection.rs index 4338109..b2d3c5e 100644 --- a/lyra/src/error/component/connection.rs +++ b/lyra/src/error/component/connection.rs @@ -63,9 +63,9 @@ pub mod join { #[error(transparent)] pub enum HandleResponseError { Cache(#[from] crate::error::Cache), - Respond(#[from] crate::error::command::RespondError), DeserializeBody(#[from] twilight_http::response::DeserializeBodyError), Followup(#[from] crate::error::command::FollowupError), + RespondOrFollowup(#[from] crate::error::command::RespondOrFollowupError), } #[derive(thiserror::Error, Debug)] @@ -152,9 +152,9 @@ pub mod join { HandleResponseError::Cache(e) => { Self::Other(ResidualError::HandleResponse(HandleResponseError::Cache(e))) } - HandleResponseError::Respond(e) => Self::Other(ResidualError::HandleResponse( - HandleResponseError::Respond(e), - )), + HandleResponseError::RespondOrFollowup(e) => Self::Other( + ResidualError::HandleResponse(HandleResponseError::RespondOrFollowup(e)), + ), HandleResponseError::DeserializeBody(e) => Self::Other( ResidualError::HandleResponse(HandleResponseError::DeserializeBody(e)), ), diff --git a/lyra/src/error/component/queue.rs b/lyra/src/error/component/queue.rs index 12d5898..c74b226 100644 --- a/lyra/src/error/component/queue.rs +++ b/lyra/src/error/component/queue.rs @@ -21,7 +21,7 @@ pub mod play { pub enum Error { RequireUnsuppressed(#[from] crate::error::command::require::UnsuppressedError), Respond(#[from] crate::error::command::RespondError), - Followup(#[from] crate::error::command::FollowupError), + RespondOrFollowup(#[from] crate::error::command::RespondOrFollowupError), AutoJoinOrCheckInVoiceWithUser( #[from] crate::error::command::util::AutoJoinOrCheckInVoiceWithUserError, ), diff --git a/lyra/src/gateway/interaction.rs b/lyra/src/gateway/interaction.rs index 836859e..d26f273 100644 --- a/lyra/src/gateway/interaction.rs +++ b/lyra/src/gateway/interaction.rs @@ -1,5 +1,6 @@ use std::{hint::unreachable_unchecked, sync::Arc}; +use tokio::sync::oneshot; use twilight_gateway::{Latency, MessageSender}; use twilight_mention::Mention; use twilight_model::{ @@ -13,7 +14,10 @@ use twilight_model::{ use super::model::Process; use crate::{ command::{ - macros::{bad, cant, caut, crit, err, hid, nope, note, out_upd, sus, sus_fol}, + macros::{ + bad, bad_or_fol, cant_or_fol, caut, crit, crit_or_fol, err, hid, nope, nope_or_fol, + note, out_upd, sus, sus_fol, + }, model::NonPingInteraction, require, util::MessageLinkAware, @@ -34,7 +38,7 @@ use crate::{ }, declare::{CommandExecuteError, Fuunacee}, util::AutoJoinSuppressedError, - Error as CommandError, Fe, RespondError, + Error as CommandError, Fe, }, gateway::{ProcessError, ProcessResult}, AutoJoinAttemptFailed as AutoJoinAttemptFailedError, @@ -101,6 +105,7 @@ impl Context { let inner_guild_id = self.inner.guild_id; // SAFETY: interaction type is not `Ping`, so `channel` is present let channel_id = unsafe { self.inner.channel_id_unchecked() }; + let (tx, mut rx) = oneshot::channel::<()>(); let result = match data.kind { CommandType::ChatInput => { @@ -110,11 +115,11 @@ impl Context { bot.clone(), self.latency, self.sender, + tx, ) .execute(*data) .await } - CommandType::User => todo!(), CommandType::Message => { MessageCtx::from_partial_data( self.inner, @@ -122,10 +127,12 @@ impl Context { bot.clone(), self.latency, self.sender, + tx, ) .execute(*data) .await } + CommandType::User => todo!(), _ => unimplemented!(), }; @@ -143,6 +150,7 @@ impl Context { return Ok(()); }; + let acknowledged = rx.try_recv().is_ok(); match source.flatten_until_user_not_allowed_as() { Fuunacee::UserNotAllowed => { nope!("You are not allowed to use commands in this context.", i); @@ -153,7 +161,7 @@ impl Context { // so the unflattened source error must be `CommandExecuteError::Command(_)` unsafe { unreachable_unchecked() } }; - match_error(error, name, i).await + match_error(error, name, acknowledged, i).await } _ => { crit!(format!( @@ -172,12 +180,14 @@ impl Context { }; let name = data.name.clone().into(); + let (tx, _) = oneshot::channel::<()>(); let Err(source) = ::from_partial_data( self.inner, &data, self.bot, self.latency, self.sender, + tx, ) .execute(*data) .await @@ -217,13 +227,17 @@ impl Context { async fn match_error( error: CommandError, command_name: Box, + acknowledged: bool, i: InteractionInterface<'_>, ) -> Result<(), ProcessError> { match error.flatten_as() { Fe::Cache => { tracing::warn!("cache error: {:#?}", error); - crit!("Something isn't working at the moment, try again later.", i); + crit_or_fol!( + "Something isn't working at the moment, try again later.", + (i, acknowledged) + ); } Fe::UserNotDj => { nope!("You need to be a ***DJ*** to do that.", i); @@ -246,12 +260,12 @@ async fn match_error( ); } Fe::InVoiceWithoutUser(e) => { - nope!( + nope_or_fol!( format!( "You are not with the bot in {}; You need to be a ***DJ*** to do that.", e.0.mention(), ), - i + (i, acknowledged) ); } Fe::InVoiceWithSomeoneElse(e) => { @@ -260,9 +274,11 @@ async fn match_error( Fe::InVoiceWithoutSomeoneElse(e) => { bad!(format!("Not enough people are in {}.", e.0.mention()), i); } - Fe::Suppressed(e) => Ok(match_suppressed(e, i).await?), + Fe::Suppressed(e) => Ok(match_suppressed(e, (i, acknowledged)).await?), Fe::AutoJoinSuppressed(e) => Ok(match_autojoin_suppressed(e, i).await?), - Fe::AutoJoinAttemptFailed(e) => Ok(match_autojoin_attempt_failed(e, i).await?), + Fe::AutoJoinAttemptFailed(e) => { + Ok(match_autojoin_attempt_failed(e, (i, acknowledged)).await?) + } Fe::Stopped => todo!(), Fe::NotPlaying => { bad!("Currently not playing anything.", i); @@ -316,14 +332,14 @@ async fn match_error( async fn match_suppressed( error: &SuppressedError, - i: InteractionInterface<'_>, -) -> UnitRespondResult { + mut ia: (InteractionInterface<'_>, bool), +) -> UnitFollowupResult { match error { SuppressedError::Muted => { - bad!("Currently server muted.", i); + bad_or_fol!("Currently server muted.", ia); } SuppressedError::NotSpeaker => { - bad!("Not currently a speaker in this stage channel.", i); + bad_or_fol!("Not currently a speaker in this stage channel.", ia); } } } @@ -350,33 +366,33 @@ async fn match_autojoin_suppressed( async fn match_autojoin_attempt_failed( error: &AutoJoinAttemptFailedError, - i: InteractionInterface<'_>, -) -> Result<(), RespondError> { + mut ia: (InteractionInterface<'_>, bool), +) -> UnitFollowupResult { match error { AutoJoinAttemptFailedError::UserNotInVoice(_) => { let join = InteractionClient::mention_command::(); - bad!( + bad_or_fol!( format!( "Please join a voice channel, or use {} to connect to a channel.", join ), - i + ia ); } AutoJoinAttemptFailedError::UserNotAllowed(_) => { - nope!("Attempting to join your currently connected channel failed as you are not allowed to use the bot here.", i); + nope_or_fol!("Attempting to join your currently connected channel failed as you are not allowed to use the bot here.", ia); } AutoJoinAttemptFailedError::Forbidden(e) => { - cant!( + cant_or_fol!( format!( "Attempting to join {} failed due to insufficient permissions.", e.0.mention() ), - i + ia ); } AutoJoinAttemptFailedError::UserNotStageManager(_) => { - nope!("Attempting to join your currently connected stage failed as you are not a **Stage Manager**.", i); + nope_or_fol!("Attempting to join your currently connected stage failed as you are not a **Stage Manager**.", ia); } } } diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index c5a5465..e506413 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -483,7 +483,7 @@ impl HttpAware for ClientData { } impl ClientData { - pub fn new(http: Arc) -> Self { + pub const fn new(http: Arc) -> Self { Self { http } } } diff --git a/lyra_ext/src/iter/multi_interleave.rs b/lyra_ext/src/iter/multi_interleave.rs index fe5f5ab..3937722 100644 --- a/lyra_ext/src/iter/multi_interleave.rs +++ b/lyra_ext/src/iter/multi_interleave.rs @@ -19,7 +19,7 @@ impl MultiInterleave where I: Iterator, { - fn new(iterators: Box<[I]>) -> Self { + const fn new(iterators: Box<[I]>) -> Self { Self { iterators, current: 0, From 0c4384b39a22d08aae73a5ba4ceed5ad575a69e7 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:32:56 +0700 Subject: [PATCH 10/12] Fixed youtube source not working --- flake.lock | 6 +++--- flake.nix | 1 + lavalink/application.yml | 10 +++++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/flake.lock b/flake.lock index e2a711b..019fd7e 100644 --- a/flake.lock +++ b/flake.lock @@ -93,11 +93,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1725690497, - "narHash": "sha256-5fT+96rV7Hx29HG+4/oBbr3V+yExKuLN2vcBcPbVBlU=", + "lastModified": 1725777030, + "narHash": "sha256-mieHgCmdbr/Tok3djbEVnNyLtnxEv5PHngN8wBu+RJA=", "owner": "nix-community", "repo": "fenix", - "rev": "4b8d964df93d1f918ee6c4f003b3548c432cc866", + "rev": "7c59cc75e7d2328753b00766ab0a78a075609b25", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 95c277d..fce5642 100644 --- a/flake.nix +++ b/flake.nix @@ -51,6 +51,7 @@ act sqlx-cli pgcli + cargo-edit ]; # https://devenv.sh/scripts/ diff --git a/lavalink/application.yml b/lavalink/application.yml index 7091a9d..6e07308 100644 --- a/lavalink/application.yml +++ b/lavalink/application.yml @@ -6,7 +6,11 @@ plugins: allowDirectPlaylistIds: true # Whether just playlist IDs can match. If false, only complete URLs will be loaded. # The clients to use for track loading. See below for a list of valid clients. # Clients are queried in the order they are given (left-to-right) - clients: ["MUSIC", "ANDROID", "WEB"] + clients: + - MUSIC + - ANDROID_TESTSUITE + - WEB + - TVHTML5EMBEDDED lavasrc: providers: # Custom providers for track loading. This is the default - "dzisrc:%ISRC%" # Deezer ISRC provider @@ -33,7 +37,7 @@ lavalink: snapshot: false - dependency: "com.github.topi314.lavasearch:lavasearch-plugin:1.0.0" snapshot: false - - dependency: "dev.lavalink.youtube:youtube-plugin:1.5.0" + - dependency: "dev.lavalink.youtube:youtube-plugin:1.7.2" snapshot: false server: sources: @@ -43,7 +47,7 @@ lavalink: twitch: true vimeo: true http: true - local: false + local: false bufferDurationMs: 400 # The duration of the NAS buffer. Higher values fare better against longer GC pauses frameBufferDurationMs: 5000 # How many milliseconds of audio to keep buffered opusEncodingQuality: 10 # Opus encoder quality. Valid values range from 0 to 10, where 10 is best quality but is the most expensive on the CPU. From 84df3cb1a832165f150280830a12d5940fc9f804 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Sun, 8 Sep 2024 20:39:52 +0700 Subject: [PATCH 11/12] Ran alejandra --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index fce5642..7680f58 100644 --- a/flake.nix +++ b/flake.nix @@ -51,7 +51,7 @@ act sqlx-cli pgcli - cargo-edit + cargo-edit ]; # https://devenv.sh/scripts/ From a56d6e8b4c07b1cf09b27bc72b792f96914db089 Mon Sep 17 00:00:00 2001 From: fdnt7 <43757589+fdnt7@users.noreply.github.com> Date: Fri, 13 Sep 2024 15:44:00 +0700 Subject: [PATCH 12/12] Use lyra-music's twilight, Optimisations & Initial playback controller development --- Cargo.lock | 123 +++++------- flake.lock | 299 +++++++++++++++++++++++++---- lyra/Cargo.toml | 24 +-- lyra/src/command/declare.rs | 2 +- lyra/src/command/poll.rs | 8 +- lyra/src/core/model.rs | 42 +++- lyra/src/core/model/emoji.rs | 50 +++++ lyra/src/core/model/interaction.rs | 2 +- lyra/src/error/lavalink.rs | 1 + lyra/src/gateway/guild.rs | 14 +- lyra/src/lavalink/model.rs | 10 +- lyra/src/lavalink/track.rs | 16 ++ lyra/src/runner.rs | 13 +- 13 files changed, 448 insertions(+), 156 deletions(-) create mode 100644 lyra/src/core/model/emoji.rs diff --git a/Cargo.lock b/Cargo.lock index 6490294..eca398d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -67,9 +67,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "anyhow" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" +checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" [[package]] name = "approx" @@ -164,22 +164,11 @@ dependencies = [ "generic-array", ] -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - [[package]] name = "brotli-decompressor" -version = "2.5.1" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -835,14 +824,14 @@ dependencies = [ [[package]] name = "getset" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" +checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" dependencies = [ - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -1051,7 +1040,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.12", + "rustls 0.23.13", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -1061,9 +1050,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" dependencies = [ "bytes", "futures-channel", @@ -1173,8 +1162,7 @@ dependencies = [ [[package]] name = "lavalink-rs" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded9801aae30b05b30dc854b0b9a3e96f73626c4e73a7b13a2ee8c4740edb25" +source = "git+https://github.com/lyra-music/lavalink-rs?branch=lyra#fb9498b5d54213ece85097bd608e235a7afdb98e" dependencies = [ "arc-swap", "bytes", @@ -1758,27 +1746,25 @@ dependencies = [ ] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "proc-macro-error-attr2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" dependencies = [ - "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", - "version_check", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ + "proc-macro-error-attr2", "proc-macro2", "quote", - "version_check", + "syn 2.0.77", ] [[package]] @@ -1851,9 +1837,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] @@ -1990,9 +1976,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.36" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -2017,9 +2003,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.12" +version = "0.23.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" dependencies = [ "once_cell", "ring", @@ -2073,9 +2059,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.7" +version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", @@ -2397,7 +2383,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pemfile", "serde", "serde_json", @@ -2760,7 +2746,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.12", + "rustls 0.23.13", "rustls-pki-types", "tokio", ] @@ -2807,9 +2793,9 @@ dependencies = [ [[package]] name = "tokio-websockets" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b069bad86dda43d908b4221fe04fe49d2ed8e0a24d319a5c6a8d250e76fe15b" +checksum = "988c6e20955aa5043e0822cb27093ebaabb430a126cda0223824b6d65ea900c1" dependencies = [ "base64 0.21.7", "bytes", @@ -2992,14 +2978,12 @@ dependencies = [ [[package]] name = "twilight" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f917ea79e3c1cb325a5cd060e8d3b9f6872ae124a3e1e1192c0e17b3b0a20132" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" [[package]] name = "twilight-cache-inmemory" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a997fdd14e32ebbe80ee87887720e756efed0a7dbf97bd1170eb7c9a3956378b" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "bitflags 2.6.0", "dashmap 5.5.3", @@ -3011,8 +2995,7 @@ dependencies = [ [[package]] name = "twilight-gateway" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3edd60e2b95ec0d651c475dacad8a75e4780d900f8f2ea0f4c215182d920fc" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "bitflags 2.6.0", "fastrand", @@ -3032,8 +3015,7 @@ dependencies = [ [[package]] name = "twilight-gateway-queue" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d30e99f204d3803b47c679214ba9e9527cfb1dd0b0deccbe821f7e4682d9a2" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "tokio", "tracing", @@ -3042,10 +3024,9 @@ dependencies = [ [[package]] name = "twilight-http" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a03a8133c9ae23510aa6bbe81ca7c4d5bc6fc45c52075d350fd0578b71397413" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ - "brotli", + "brotli-decompressor", "fastrand", "http", "http-body-util", @@ -3065,8 +3046,7 @@ dependencies = [ [[package]] name = "twilight-http-ratelimiting" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f814c51752ba6838d3b32e82638176de9679e9a2a084a65251b7879281bdc6" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "tokio", "tracing", @@ -3075,8 +3055,7 @@ dependencies = [ [[package]] name = "twilight-interactions" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc671758050eefe63e2b87f6af2fc1807ac3ac373bbafff1ae1a4cc4af6cb722" +source = "git+https://github.com/lyra-music/twilight-interactions?branch=lyra#068a8459d4529134d4c655d6e823037e6b697d44" dependencies = [ "twilight-interactions-derive", "twilight-model", @@ -3085,8 +3064,7 @@ dependencies = [ [[package]] name = "twilight-interactions-derive" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c07ac7c9d2d086291765ceecafcd52ce6dc33ab0ade02e06509eb416d88470" +source = "git+https://github.com/lyra-music/twilight-interactions?branch=lyra#068a8459d4529134d4c655d6e823037e6b697d44" dependencies = [ "proc-macro2", "quote", @@ -3096,8 +3074,7 @@ dependencies = [ [[package]] name = "twilight-mention" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dc35d15fbaf51d2a4a0ca961e746bca9a01f26f338f99d9134c15b6561b4c5b" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "twilight-model", ] @@ -3105,8 +3082,7 @@ dependencies = [ [[package]] name = "twilight-model" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eaf296e6aa8784699046bb1ceb33f3c684a59c4657c2b159eb4ec1e1ce44a49" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "bitflags 2.6.0", "serde", @@ -3118,8 +3094,7 @@ dependencies = [ [[package]] name = "twilight-standby" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e968572b4e0c2b0e7a05b4b31b1cbc767c7ec0df0407410045ae8c267af6e1fd" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "dashmap 5.5.3", "futures-core", @@ -3131,8 +3106,7 @@ dependencies = [ [[package]] name = "twilight-util" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8a5a519ea63090a9e009974bf25e8cb3be9284da7248011ed37377d4180295" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "twilight-model", "twilight-validate", @@ -3141,8 +3115,7 @@ dependencies = [ [[package]] name = "twilight-validate" version = "0.16.0-rc.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d71bf2b683421a1674080e821eef108a35cef841ed73562f835bc5cdd478b49" +source = "git+https://github.com/lyra-music/twilight?branch=lyra#f58b5402cc17a53e68312cc5969fde81fdfb006a" dependencies = [ "twilight-model", ] @@ -3176,9 +3149,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" diff --git a/flake.lock b/flake.lock index 019fd7e..a2bc184 100644 --- a/flake.lock +++ b/flake.lock @@ -7,11 +7,47 @@ "devenv", "flake-compat" ], + "git-hooks": [ + "devenv", + "pre-commit-hooks" + ], + "nixpkgs": [ + "devenv", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1724232775, + "narHash": "sha256-6u2DycIEgrgNYlLxyGqdFVmBNiKIitnQKJ1pbRP5oko=", + "owner": "cachix", + "repo": "cachix", + "rev": "03b6cb3f953097bff378fb8b9ea094bd091a4ec7", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "cachix", + "type": "github" + } + }, + "cachix_2": { + "inputs": { + "devenv": "devenv_3", + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "flake-compat" + ], "nixpkgs": [ + "devenv", + "cachix", "devenv", "nixpkgs" ], "pre-commit-hooks": [ + "devenv", + "cachix", "devenv", "pre-commit-hooks" ] @@ -34,18 +70,18 @@ "inputs": { "cachix": "cachix", "flake-compat": "flake-compat_2", - "nix": "nix_2", + "nix": "nix_3", "nixpkgs": [ "nixpkgs" ], - "pre-commit-hooks": "pre-commit-hooks" + "pre-commit-hooks": "pre-commit-hooks_2" }, "locked": { - "lastModified": 1725637114, - "narHash": "sha256-+hsiHWbqkS098soB1o4URP3frnjhoRvyVfWs6byv4Zk=", + "lastModified": 1726063457, + "narHash": "sha256-VtMnPqbP2MLJSjEqmGEUb+cTG1dthc+Bfch2/McymbI=", "owner": "cachix", "repo": "devenv", - "rev": "c31e347a96dbb7718a0279afa993752a7dfc6a39", + "rev": "39bf6ce569103c9390d37322daa59468c31b3ce7", "type": "github" }, "original": { @@ -56,15 +92,53 @@ }, "devenv_2": { "inputs": { + "cachix": "cachix_2", "flake-compat": [ "devenv", "cachix", "flake-compat" ], + "nix": "nix_2", + "nixpkgs": [ + "devenv", + "cachix", + "nixpkgs" + ], + "pre-commit-hooks": [ + "devenv", + "cachix", + "git-hooks" + ] + }, + "locked": { + "lastModified": 1723156315, + "narHash": "sha256-0JrfahRMJ37Rf1i0iOOn+8Z4CLvbcGNwa2ChOAVrp/8=", + "owner": "cachix", + "repo": "devenv", + "rev": "ff5eb4f2accbcda963af67f1a1159e3f6c7f5f91", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "devenv_3": { + "inputs": { + "flake-compat": [ + "devenv", + "cachix", + "devenv", + "cachix", + "flake-compat" + ], "nix": "nix", "nixpkgs": "nixpkgs", "poetry2nix": "poetry2nix", "pre-commit-hooks": [ + "devenv", + "cachix", "devenv", "cachix", "pre-commit-hooks" @@ -93,11 +167,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1725777030, - "narHash": "sha256-mieHgCmdbr/Tok3djbEVnNyLtnxEv5PHngN8wBu+RJA=", + "lastModified": 1726116637, + "narHash": "sha256-tU2GhwU887mPg6C4c2k+CEBAnKY6R0tSeQYtoqjZmLM=", "owner": "nix-community", "repo": "fenix", - "rev": "7c59cc75e7d2328753b00766ab0a78a075609b25", + "rev": "96a04a213838c5001619ad57400c5a176fa040b1", "type": "github" }, "original": { @@ -138,6 +212,28 @@ "type": "github" } }, + "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems" @@ -157,15 +253,12 @@ } }, "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1667395993, + "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f", "type": "github" }, "original": { @@ -196,10 +289,28 @@ "type": "github" } }, + "libgit2": { + "flake": false, + "locked": { + "lastModified": 1697646580, + "narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=", + "owner": "libgit2", + "repo": "libgit2", + "rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5", + "type": "github" + }, + "original": { + "owner": "libgit2", + "repo": "libgit2", + "type": "github" + } + }, "nix": { "inputs": { "flake-compat": "flake-compat", "nixpkgs": [ + "devenv", + "cachix", "devenv", "cachix", "devenv", @@ -225,6 +336,8 @@ "nix-github-actions": { "inputs": { "nixpkgs": [ + "devenv", + "cachix", "devenv", "cachix", "devenv", @@ -249,10 +362,14 @@ "nix_2": { "inputs": { "flake-compat": [ + "devenv", + "cachix", "devenv", "flake-compat" ], "nixpkgs": [ + "devenv", + "cachix", "devenv", "nixpkgs" ], @@ -273,6 +390,34 @@ "type": "github" } }, + "nix_3": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], + "flake-parts": "flake-parts", + "libgit2": "libgit2", + "nixpkgs": "nixpkgs_2", + "nixpkgs-23-11": "nixpkgs-23-11", + "nixpkgs-regression": "nixpkgs-regression_3", + "pre-commit-hooks": "pre-commit-hooks" + }, + "locked": { + "lastModified": 1725980365, + "narHash": "sha256-uDwWyizzlQ0HFzrhP6rVp2+2NNA+/TM5zT32dR8GUlg=", + "owner": "domenkozar", + "repo": "nix", + "rev": "1e61e9f40673f84c3b02573145492d8af581bec5", + "type": "github" + }, + "original": { + "owner": "domenkozar", + "ref": "devenv-2.24", + "repo": "nix", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1692808169, @@ -289,6 +434,22 @@ "type": "github" } }, + "nixpkgs-23-11": { + "locked": { + "lastModified": 1717159533, + "narHash": "sha256-oamiKNfr2MS6yH64rUn99mIZjc45nGJlj9eGth/3Xuw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "a62e6edd6d5e1fa0329b8653c801147986f8d446", + "type": "github" + } + }, "nixpkgs-regression": { "locked": { "lastModified": 1643052045, @@ -321,23 +482,55 @@ "type": "github" } }, + "nixpkgs-regression_3": { + "locked": { + "lastModified": 1643052045, + "narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", + "type": "github" + } + }, "nixpkgs-stable": { "locked": { - "lastModified": 1710695816, - "narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", + "lastModified": 1720386169, + "narHash": "sha256-NGKVY4PjzwAa4upkGtAMz1npHGoRzWotlSnVlqI40mo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "614b4613980a522ba49f0d194531beddbb7220d3", + "rev": "194846768975b7ad2c4988bdb82572c00222c0d7", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { + "locked": { + "lastModified": 1717432640, + "narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "88269ab3044128b7c2f4c7d68448b2fb50456870", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1716977621, "narHash": "sha256-Q1UQzYcMJH4RscmpTkjlgqQDX5yi1tZL0O345Ri6vXQ=", @@ -358,6 +551,8 @@ "flake-utils": "flake-utils", "nix-github-actions": "nix-github-actions", "nixpkgs": [ + "devenv", + "cachix", "devenv", "cachix", "devenv", @@ -382,9 +577,44 @@ "inputs": { "flake-compat": [ "devenv", - "flake-compat" + "nix" ], "flake-utils": "flake-utils_2", + "gitignore": [ + "devenv", + "nix" + ], + "nixpkgs": [ + "devenv", + "nix", + "nixpkgs" + ], + "nixpkgs-stable": [ + "devenv", + "nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1712897695, + "narHash": "sha256-nMirxrGteNAl9sWiOhoN5tIHyjBbVi5e2tgZUgZlK3Y=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "40e6053ecb65fcbf12863338a6dcefb3f55f1bf8", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_2": { + "inputs": { + "flake-compat": [ + "devenv", + "flake-compat" + ], "gitignore": "gitignore", "nixpkgs": [ "devenv", @@ -393,11 +623,11 @@ "nixpkgs-stable": "nixpkgs-stable" }, "locked": { - "lastModified": 1713775815, - "narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=", + "lastModified": 1725513492, + "narHash": "sha256-tyMUA6NgJSvvQuzB7A1Sf8+0XCHyfSPRx/b00o6K0uo=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4", + "rev": "7570de7b9b504cfe92025dd1be797bf546f66528", "type": "github" }, "original": { @@ -410,18 +640,18 @@ "inputs": { "devenv": "devenv", "fenix": "fenix", - "nixpkgs": "nixpkgs_2", - "systems": "systems_3" + "nixpkgs": "nixpkgs_3", + "systems": "systems_2" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1725630423, - "narHash": "sha256-gNCLk3Zg7JlAwmWbVHTH6f3+iqdeQ4fheOotCZy8x5M=", + "lastModified": 1725985110, + "narHash": "sha256-0HKj+JI6rtxaE6Kzcd6HyFNbEFJRsLy5DoNgVF1pyRM=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "08c7bbc2dbe4dcc8968484f1a0e1e6fe7a1d4f6d", + "rev": "bcc708992104c2059f310fbc3ac00bfc377f9ea8", "type": "github" }, "original": { @@ -460,21 +690,6 @@ "repo": "default", "type": "github" } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/lyra/Cargo.toml b/lyra/Cargo.toml index 58fee18..35663ea 100644 --- a/lyra/Cargo.toml +++ b/lyra/Cargo.toml @@ -64,21 +64,23 @@ itertools = "0.13.0" rayon = "1.10.0" sqlx = { version = "0.8.2", features = ["postgres", "runtime-tokio-rustls"] } mixbox = "2.0.0" -lavalink-rs = { version = "0.13.0", features = ["twilight16"] } +lavalink-rs = { git = "https://github.com/lyra-music/lavalink-rs", branch = "lyra", features = [ + "twilight16", +] } aho-corasick = "1.1.3" -twilight = "0.16.0-rc.1" -twilight-cache-inmemory = { version = "0.16.0-rc.1", features = [ +twilight = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-cache-inmemory = { git = "https://github.com/lyra-music/twilight", branch = "lyra", features = [ "permission-calculator", ] } -twilight-gateway = "0.16.0-rc.1" -twilight-http = "0.16.0-rc.1" -twilight-model = "0.16.0-rc.1" -twilight-standby = "0.16.0-rc.1" -twilight-validate = "0.16.0-rc.1" -twilight-mention = "0.16.0-rc.1" -twilight-util = { version = "0.16.0-rc.1", features = [ +twilight-gateway = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-http = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-model = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-standby = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-validate = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-mention = { git = "https://github.com/lyra-music/twilight", branch = "lyra" } +twilight-util = { git = "https://github.com/lyra-music/twilight", branch = "lyra", features = [ "permission-calculator", "builder", ] } -twilight-interactions = "0.16.0-rc.1" +twilight-interactions = { git = "https://github.com/lyra-music/twilight-interactions", branch = "lyra" } diff --git a/lyra/src/command/declare.rs b/lyra/src/command/declare.rs index 158716b..733f06f 100644 --- a/lyra/src/command/declare.rs +++ b/lyra/src/command/declare.rs @@ -57,7 +57,7 @@ macro_rules! declare_slash_commands { } }); - pub static POPULATED_COMMANDS_MAP: OnceLock, Command>> = OnceLock::new(); + pub static POPULATED_COMMANDS_MAP: OnceLock> = OnceLock::new(); $( impl CommandInfoAware for $raw_cmd { diff --git a/lyra/src/command/poll.rs b/lyra/src/command/poll.rs index c15fd2c..7edeb10 100644 --- a/lyra/src/command/poll.rs +++ b/lyra/src/command/poll.rs @@ -15,7 +15,7 @@ use twilight_model::{ application::interaction::{Interaction, InteractionData}, channel::message::{ component::{ActionRow, Button, ButtonStyle}, - Component, Embed, ReactionType, + Component, Embed, EmojiReactionType, }, guild::Permissions, id::{ @@ -280,22 +280,24 @@ fn generate_upvote_button_id_and_row() -> (String, Component) { let upvote_button = Component::Button(Button { custom_id: Some(upvote_button_id.clone()), disabled: false, - emoji: Some(ReactionType::Unicode { + emoji: Some(EmojiReactionType::Unicode { name: String::from("โž•"), }), label: None, style: ButtonStyle::Primary, url: None, + sku_id: None, }); let downvote_button = Component::Button(Button { custom_id: Some(downvote_button_id), disabled: false, - emoji: Some(ReactionType::Unicode { + emoji: Some(EmojiReactionType::Unicode { name: String::from("โž–"), }), label: None, style: ButtonStyle::Danger, url: None, + sku_id: None, }); let row = Component::ActionRow(ActionRow { components: vec![upvote_button, downvote_button], diff --git a/lyra/src/core/model.rs b/lyra/src/core/model.rs index cde596a..274f83a 100644 --- a/lyra/src/core/model.rs +++ b/lyra/src/core/model.rs @@ -1,3 +1,4 @@ +mod emoji; mod interaction; use std::{ @@ -10,13 +11,16 @@ use std::{ use dashmap::DashMap; use sqlx::{Pool, Postgres}; +use tokio::sync::OnceCell; use twilight_cache_inmemory::InMemoryCache; use twilight_gateway::ShardId; use twilight_http::Client; use twilight_model::{ - guild::Permissions, - id::{marker::UserMarker, Id}, - oauth::Application, + guild::{Emoji, Permissions}, + id::{ + marker::{ApplicationMarker, UserMarker}, + Id, + }, user::CurrentUser, }; use twilight_standby::Standby; @@ -132,6 +136,8 @@ pub struct BotState { lavalink: Lavalink, db: Pool, info: BotInfo, + application_id: OnceCell>, + application_emojis: OnceCell<&'static [Emoji]>, } impl BotState { @@ -148,6 +154,8 @@ impl BotState { lavalink, db, info, + application_id: OnceCell::new(), + application_emojis: OnceCell::new(), } } @@ -163,13 +171,33 @@ impl BotState { &self.info } - async fn app(&self) -> Result { - Ok(self.http.current_user_application().await?.model().await?) + pub async fn application_id( + &self, + ) -> Result, DeserializeBodyFromHttpError> { + self.application_id + .get_or_try_init(|| async { + let application = self.http.current_user_application().await?.model().await?; + Ok(application.id) + }) + .await + .copied() } - pub async fn interaction(&self) -> Result { - let client = self.http.interaction(self.app().await?.id); + pub async fn application_emojis( + &self, + ) -> Result<&'static [Emoji], DeserializeBodyFromHttpError> { + self.application_emojis + .get_or_try_init(|| async { + let application_id = self.application_id().await?; + let req = self.http.get_application_emojis(application_id); + Ok(&*req.await?.models().await?.leak()) + }) + .await + .copied() + } + pub async fn interaction(&self) -> Result { + let client = self.http.interaction(self.application_id().await?); Ok(InteractionClient::new(client)) } diff --git a/lyra/src/core/model/emoji.rs b/lyra/src/core/model/emoji.rs new file mode 100644 index 0000000..77c695c --- /dev/null +++ b/lyra/src/core/model/emoji.rs @@ -0,0 +1,50 @@ +use std::sync::OnceLock; + +use twilight_model::channel::message::EmojiReactionType; + +use crate::error::core::DeserializeBodyFromHttpError; + +use super::BotState; + +macro_rules! generate_emojis { + ($ (($name: ident, $default: expr)) ,* $(,)? ) => {$( + pub async fn $name( + bot: &BotState, + ) -> Result<&'static EmojiReactionType, DeserializeBodyFromHttpError> { + ::paste::paste! { + static [<$name:upper>]: OnceLock = OnceLock::new(); + if let Some(emoji) = [<$name:upper>].get() { + return Ok(emoji); + } + } + + let emojis = bot.application_emojis().await?; + let emoji = emojis.iter().find(|e| e.name == stringify!($name)); + let reaction = emoji.map_or( + { + EmojiReactionType::Unicode { + name: String::from($default), + } + }, + |emoji| EmojiReactionType::Custom { + animated: emoji.animated, + id: emoji.id, + name: Some(emoji.name.clone()), + }, + ); + ::paste::paste!(Ok([<$name:upper>].get_or_init(|| reaction))) + } + )*}; +} + +generate_emojis![ + (shuffle_off, "๐Ÿ”€"), + (shuffle_on, "๐Ÿ”€"), + (previous, "โฎ๏ธ"), + (play, "โ–ถ๏ธ"), + (pause, "โธ๏ธ"), + (next, "โญ๏ธ"), + (repeat_off, "โžก๏ธ"), + (repeat_all, "๐Ÿ”"), + (repeat_track, "๐Ÿ”‚"), +]; diff --git a/lyra/src/core/model/interaction.rs b/lyra/src/core/model/interaction.rs index 6f64f8a..07bea27 100644 --- a/lyra/src/core/model/interaction.rs +++ b/lyra/src/core/model/interaction.rs @@ -374,7 +374,7 @@ impl<'a> Client<'a> { POPULATED_COMMANDS_MAP.get_or_init(|| { commands .into_iter() - .map(|c| (c.name.clone().into(), c)) + .map(|c| (&*c.name.clone().leak(), c)) .collect() }); diff --git a/lyra/src/error/lavalink.rs b/lyra/src/error/lavalink.rs index 05f32d9..9d93050 100644 --- a/lyra/src/error/lavalink.rs +++ b/lyra/src/error/lavalink.rs @@ -9,6 +9,7 @@ pub struct NoPlayerError; pub enum ProcessError { Lavalink(#[from] lavalink_rs::error::LavalinkError), TwilightHttp(#[from] twilight_http::Error), + Sqlx(#[from] sqlx::Error), } pub type ProcessResult = Result<(), ProcessError>; diff --git a/lyra/src/gateway/guild.rs b/lyra/src/gateway/guild.rs index 6828df1..406b9ba 100644 --- a/lyra/src/gateway/guild.rs +++ b/lyra/src/gateway/guild.rs @@ -1,6 +1,9 @@ use lyra_ext::num::u64_to_i64_truncating; use twilight_gateway::ShardId; -use twilight_model::gateway::payload::incoming::{GuildCreate, GuildDelete}; +use twilight_model::{ + gateway::payload::incoming::{GuildCreate, GuildDelete}, + guild::Guild, +}; use super::model::Process; use crate::{ @@ -17,9 +20,12 @@ pub(super) struct CreateContext<'a> { impl CreateContext<'_> { async fn increment_guild_count(&self) -> Result<(), sqlx::Error> { /* FIXME: wait until twilight stop deserializing missing `Guild::unavailable` to false: - https://github.com/twilight-rs/twilight/pull/2330 + https://github.com/twilight-rs/twilight/issues/2372 */ - if !self.inner.unavailable { + if let GuildCreate::Available(Guild { + unavailable: false, .. + }) = self.inner + { return Ok(()); } @@ -31,7 +37,7 @@ impl CreateContext<'_> { NOT EXISTS ( SELECT 1 FROM guild_configs WHERE id = $1 );", - u64_to_i64_truncating(self.inner.id.get()) + u64_to_i64_truncating(self.inner.id().get()) ) .execute(self.bot.db()) .await?; diff --git a/lyra/src/lavalink/model.rs b/lyra/src/lavalink/model.rs index e506413..141d8a2 100644 --- a/lyra/src/lavalink/model.rs +++ b/lyra/src/lavalink/model.rs @@ -13,6 +13,7 @@ use lavalink_rs::{ player_context::PlayerContext, }; use lyra_ext::time::track_timestamp::TrackTimestamp; +use sqlx::{Pool, Postgres}; use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use twilight_http::Client; use twilight_model::id::{ @@ -474,6 +475,7 @@ impl GetConnection for PartialInVoice {} pub struct ClientData { http: Arc, + db: Pool, } impl HttpAware for ClientData { @@ -483,7 +485,11 @@ impl HttpAware for ClientData { } impl ClientData { - pub const fn new(http: Arc) -> Self { - Self { http } + pub const fn new(http: Arc, db: Pool) -> Self { + Self { http, db } + } + + pub const fn db(&self) -> &Pool { + &self.db } } diff --git a/lyra/src/lavalink/track.rs b/lyra/src/lavalink/track.rs index 946d9c8..3317361 100644 --- a/lyra/src/lavalink/track.rs +++ b/lyra/src/lavalink/track.rs @@ -3,6 +3,7 @@ use lavalink_rs::{ hook, model::events::{TrackEnd, TrackException, TrackStart, TrackStuck}, }; +use lyra_ext::num::u64_to_i64_truncating; use crate::{ core::model::HttpAware, @@ -30,6 +31,21 @@ async fn impl_start(lavalink: LavalinkClient, _: String, event: &TrackStart) -> .await .reset_track_timestamp(); + let Some(track) = player.data_unwrapped().read().await.queue().current() else { + return Ok(()); + }; + + let rec = sqlx::query!( + "SELECT now_playing FROM guild_configs WHERE id = $1;", + u64_to_i64_truncating(guild_id.0) + ) + .fetch_one(lavalink.data_unwrapped().db()) + .await?; + + if !rec.now_playing { + return Ok(()); + } + Ok(()) } diff --git a/lyra/src/runner.rs b/lyra/src/runner.rs index 720b1a1..f76fa11 100644 --- a/lyra/src/runner.rs +++ b/lyra/src/runner.rs @@ -15,9 +15,8 @@ use sqlx::{ }; use tokio::task::JoinHandle; use twilight_gateway::{ - error::{ReceiveMessageErrorType, StartRecommendedError}, - CloseFrame, Config as ShardConfig, ConfigBuilder, Event, EventTypeFlags, Intents, - MessageSender, Shard, StreamExt, + error::StartRecommendedError, CloseFrame, Config as ShardConfig, ConfigBuilder, Event, + EventTypeFlags, Intents, MessageSender, Shard, StreamExt, }; use twilight_http::{client::ClientBuilder, Client}; use twilight_model::{ @@ -98,7 +97,7 @@ pub async fn start() -> Result<(), StartError> { let http = build_http_client(); let user_id = http.current_user().await?.model().await?.id; - let data = ClientData::new(http.clone()); + let data = ClientData::new(http.clone(), db.clone()); let lavalink = build_lavalink_client(user_id, data).await; let shards = build_and_split_shards(&http).await?; @@ -149,12 +148,6 @@ async fn handle_gateway_events(mut shard: Shard, bot: Arc) { let event = match item { Ok(Event::GatewayClose(_)) if SHUTDOWN.load(Ordering::Relaxed) => break, Ok(event) => event, - Err(source) - if SHUTDOWN.load(Ordering::Relaxed) - && matches!(source.kind(), ReceiveMessageErrorType::WebSocket) => - { - break - } Err(source) => { tracing::warn!(?source, "error receiving event");