Skip to content

Commit

Permalink
Implement playback controller buttons functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
fdnt7 committed Sep 15, 2024
1 parent 9f6e117 commit 0d5bd00
Show file tree
Hide file tree
Showing 38 changed files with 618 additions and 205 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
sqlx-cli
pgcli
cargo-edit
openssl
openssl
];

# https://devenv.sh/scripts/
Expand Down
9 changes: 5 additions & 4 deletions lyra/src/command/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::sync::Arc;
use twilight_model::{
application::interaction::{
application_command::{CommandData, CommandDataOption},
message_component::MessageComponentInteractionData,
Interaction, InteractionDataResolved,
},
channel::Channel,
Expand All @@ -19,9 +20,9 @@ use twilight_model::{
use crate::error::{command::AutocompleteResult, CommandResult};

pub use self::ctx::{
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,
Autocomplete as AutocompleteCtx, CommandDataAware, Component as ComponentCtx, 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 {
Expand Down Expand Up @@ -90,7 +91,7 @@ impl PartialCommandData {
#[non_exhaustive]
pub enum PartialInteractionData {
Command(PartialCommandData),
_Other,
Component(Box<MessageComponentInteractionData>),
}

pub trait CommandInfoAware {
Expand Down
5 changes: 3 additions & 2 deletions lyra/src/command/model/ctx.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod autocomplete;
mod command_data;
mod component;
mod menu;
mod message;
mod modal;
Expand Down Expand Up @@ -28,7 +29,7 @@ use crate::{
HttpAware, InteractionInterface, OwnedBotState, OwnedBotStateAware,
},
error::{
command::RespondError, core::DeserializeBodyFromHttpError, Cache, CacheResult, NotInGuild,
command::RespondError, core::DeserialiseBodyFromHttpError, Cache, CacheResult, NotInGuild,
},
gateway::{GuildIdAware, OptionallyGuildIdAware, SenderAware},
lavalink::Lavalink,
Expand Down Expand Up @@ -162,7 +163,7 @@ impl<T: Kind, U: Location> Ctx<T, U> {
&self.inner.token
}

pub async fn interface(&self) -> Result<InteractionInterface, DeserializeBodyFromHttpError> {
pub async fn interface(&self) -> Result<InteractionInterface, DeserialiseBodyFromHttpError> {
Ok(self.bot.interaction().await?.interfaces(&self.inner))
}

Expand Down
15 changes: 5 additions & 10 deletions lyra/src/command/model/ctx/command_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,22 +46,17 @@ impl<T: Aware> Ctx<T> {
}

impl<T: Aware, U: Location> Ctx<T, U> {
pub fn command_data(&self) -> &PartialCommandData {
// SAFETY: `self` is `Ctx<impl CommandDataAware, _>`,
// so `self.data` is present
let data = unsafe { self.data.as_ref().unwrap_unchecked() };
let PartialInteractionData::Command(data) = data else {
// SAFETY:
pub const fn command_data(&self) -> &PartialCommandData {
let Some(PartialInteractionData::Command(data)) = self.data.as_ref() else {
// SAFETY: `self` is `Ctx<impl CommandDataAware, _>`,
// so `data` will always be `PartialInteractionData::Command(_)`
unsafe { unreachable_unchecked() }
};
data
}

pub fn into_command_data(self) -> PartialCommandData {
// SAFETY: `self` is `Ctx<impl CommandDataAware, _>`,
// so `self.data` is present
let data = unsafe { self.data.unwrap_unchecked() };
let PartialInteractionData::Command(command_data) = data else {
let Some(PartialInteractionData::Command(command_data)) = self.data else {
// SAFETY: `self` is `Ctx<impl CommandDataAware, _>`,
// so `data` will always be `PartialInteractionData::Command(_)`
unsafe { unreachable_unchecked() }
Expand Down
49 changes: 49 additions & 0 deletions lyra/src/command/model/ctx/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use twilight_gateway::{Latency, MessageSender};
use twilight_model::{
application::interaction::message_component::MessageComponentInteractionData, channel::Message,
gateway::payload::incoming::InteractionCreate,
};

use crate::{command::model::PartialInteractionData, core::model::OwnedBotState};

use super::{ComponentMarker, Ctx, Location};

impl<U: Location> Ctx<ComponentMarker, U> {
pub const fn from_data(
inner: Box<InteractionCreate>,
data: Box<MessageComponentInteractionData>,
bot: OwnedBotState,
latency: Latency,
sender: MessageSender,
) -> Self {
Self {
inner,
bot,
latency,
sender,
data: Some(PartialInteractionData::Component(data)),
acknowledged: false,
acknowledgement: None,
kind: std::marker::PhantomData,
location: std::marker::PhantomData,
}
}

pub fn component_data_mut(&mut self) -> &mut MessageComponentInteractionData {
let Some(PartialInteractionData::Component(data)) = self.data.as_mut() else {
// SAFETY: `self` is `Ctx<ComponentMarker>`,
// so `data` will always be `PartialInteractionData::Component(_)`
unsafe { std::hint::unreachable_unchecked() }
};
data
}

pub fn message(&self) -> &Message {
// SAFETY: `self` is `Ctx<ComponentMarker>`, so `self.inner.message` exists
unsafe { self.inner.message.as_ref().unwrap_unchecked() }
}

pub fn take_custom_id(&mut self) -> String {
std::mem::take(&mut self.component_data_mut().custom_id)
}
}
13 changes: 13 additions & 0 deletions lyra/src/command/util.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::borrow::Cow;

use lavalink_rs::{error::LavalinkResult, player_context::PlayerContext};
use lyra_ext::unix_time;
use rand::{distributions::Alphanumeric, Rng};
Expand Down Expand Up @@ -218,6 +220,17 @@ impl DefaultAvatarUrlAware for User {
}
}

pub fn controller_fmt<'a>(
ctx: &impl AuthorIdAware,
via_controller: bool,
string: &'a str,
) -> Cow<'a, str> {
if via_controller {
return format!("{} {}", ctx.author_id().mention(), string).into();
}
string.into()
}

pub async fn auto_join_or_check_in_voice_with_user_and_check_not_suppressed(
ctx: &mut GuildCtx<impl RespondViaMessage>,
) -> Result<(), AutoJoinOrCheckInVoiceWithUserError> {
Expand Down
1 change: 0 additions & 1 deletion lyra/src/component.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
pub mod config;
pub mod connection;
pub mod controller;
pub mod debug;
pub mod info;
pub mod misc;
Expand Down
2 changes: 1 addition & 1 deletion lyra/src/component/connection/leave.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ pub(super) async fn disconnect_cleanup(
connection.dispatch(Event::QueueClear);
};
if let Some(player_ctx) = lavalink.get_player_context(guild_id) {
delete_now_playing_message(cx.http(), &player_ctx.data_unwrapped()).await;
delete_now_playing_message(cx, &player_ctx.data_unwrapped()).await;
}
lavalink.drop_connection(guild_id);
lavalink.delete_player(guild_id).await?;
Expand Down
1 change: 0 additions & 1 deletion lyra/src/component/controller.rs

This file was deleted.

6 changes: 3 additions & 3 deletions lyra/src/component/playback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ mod restart;
mod seek;
mod skip;

pub use back::Back;
pub use back::{back, Back};
pub use jump::{Autocomplete as JumpAutocomplete, Jump};
pub use play_pause::PlayPause;
pub use play_pause::{play_pause, PlayPause};
pub use restart::Restart;
pub use seek::Seek;
pub use skip::Skip;
pub use skip::{skip, Skip};

use crate::{
command::require,
Expand Down
61 changes: 42 additions & 19 deletions lyra/src/component/playback/back.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
use twilight_interactions::command::{CommandModel, CreateCommand};

use crate::command::{check, macros::out, model::BotSlashCommand, require};
use crate::{
command::{
check,
macros::out,
model::{BotSlashCommand, GuildCtx, RespondViaMessage},
require,
util::controller_fmt,
},
error::component::playback::PlayPauseError,
lavalink::OwnedPlayerData,
};

/// Jumps to the track before the current one in the queue. Will wrap around if queue repeat is enabled.
#[derive(CreateCommand, CommandModel)]
Expand All @@ -14,30 +24,43 @@ impl BotSlashCommand for Back {
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;
let data_r = data.read().await;
let queue = require::queue_not_empty(&data_r)?;
let txt;

if let Ok(current_track) = require::current_track(queue) {
check::current_track_is_users(&current_track, in_voice_with_user)?;
txt = format!("⏮️ ~~`{}`~~", current_track.track.data().info.title);
txt = Some(current_track.track.data().info.title.clone());
} else {
txt = String::new();
txt = None;
}
drop(data_r);

queue.downgrade_repeat_mode();
queue.acquire_advance_lock();
queue.recede();
Ok(back(txt, player, data, &mut ctx, false).await?)
}
}

// 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?;
pub async fn back(
current_track_title: Option<String>,
player: require::PlayerInterface,
data: OwnedPlayerData,
ctx: &mut GuildCtx<impl RespondViaMessage>,
via_controller: bool,
) -> Result<(), PlayPauseError> {
let mut data_w = data.write().await;
let queue = data_w.queue_mut();
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?;
let message = current_track_title.map_or_else(
|| format!("⏮️ `{}`", item.data().info.title),
|title| format!("⏮️ ~~`{title}`~~",),
);
drop(data_w);

if txt.is_empty() {
txt = format!("⏮️ `{}`", item.data().info.title);
}
drop(data_w);

out!(txt, ctx);
}
let content = controller_fmt(ctx, via_controller, &message);
out!(content, ctx);
}
44 changes: 31 additions & 13 deletions lyra/src/component/playback/play_pause.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
use twilight_interactions::command::{CommandModel, CreateCommand};

use crate::{
command::{check, macros::out, model::BotSlashCommand, require, SlashCtx},
error::CommandResult,
command::{
check,
macros::out,
model::{BotSlashCommand, GuildCtx, RespondViaMessage},
require,
util::controller_fmt,
SlashCtx,
},
error::{component::playback::PlayPauseError, CommandResult},
lavalink::OwnedPlayerData,
};

/// Toggles the playback of the current track.
Expand All @@ -20,18 +28,28 @@ impl BotSlashCommand for PlayPause {
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);
Ok(play_pause(player, data, &mut ctx, false).await?)
}
}

player
.set_pause_with(pause, &mut data.write().await)
.await?;
pub async fn play_pause(
player: require::PlayerInterface,
data: OwnedPlayerData,
ctx: &mut GuildCtx<impl RespondViaMessage>,
via_controller: bool,
) -> Result<(), PlayPauseError> {
let mut data_w = data.write().await;
let pause = !data_w.paused();

out!(message, ctx);
}
player.set_pause_with(pause, &mut data_w).await?;
drop(data_w);

let message = if pause {
"▶️ Paused"
} else {
"⏸️ Resumed"
};
let content = controller_fmt(ctx, via_controller, message);
out!(content, ctx);
}
Loading

0 comments on commit 0d5bd00

Please sign in to comment.