From b7c85455cea4a925dfd4e27110a14e32e8959b82 Mon Sep 17 00:00:00 2001 From: DaXcess Date: Mon, 7 Nov 2022 12:58:22 +0100 Subject: [PATCH] Updates, bugfixes. You name it. --- src/bot/commands/music/join.rs | 10 +- src/consts.rs | 4 + src/main.rs | 9 +- src/{player/mod.rs => player.rs} | 5 +- src/session/manager.rs | 4 +- src/session/mod.rs | 180 +++++++++++++------------------ src/session/pbi.rs | 110 +++++++++++++++++++ src/{stats/mod.rs => stats.rs} | 0 src/utils/embed.rs | 1 - 9 files changed, 206 insertions(+), 117 deletions(-) rename src/{player/mod.rs => player.rs} (98%) create mode 100644 src/session/pbi.rs rename src/{stats/mod.rs => stats.rs} (100%) diff --git a/src/bot/commands/music/join.rs b/src/bot/commands/music/join.rs index bac365c..593e4d5 100644 --- a/src/bot/commands/music/join.rs +++ b/src/bot/commands/music/join.rs @@ -143,7 +143,13 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu } else { // Create the session, and handle potential errors if let Err(why) = session_manager - .create_session(&ctx, guild.id, channel_id, command.user.id) + .create_session( + &ctx, + guild.id, + channel_id, + command.channel_id, + command.user.id, + ) .await { // Need to link first @@ -188,7 +194,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu .icon_url("https://spoticord.com/static/image/speaker.png") .description(format!("Come listen along in <#{}>", channel_id)) .footer("Spotify will automatically start playing on Spoticord") - .status(Status::Success) + .status(Status::Info) .build(), ) .await; diff --git a/src/consts.rs b/src/consts.rs index 920f012..fe8da6a 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,3 +1,7 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const MOTD: &str = "OPEN BETA (v2)"; + +/// The time it takes for Spoticord to disconnect when no music is being played +pub const DISCONNECT_TIME: u64 = 5 * 60; + // pub const MOTD: &str = "some good 'ol music"; diff --git a/src/main.rs b/src/main.rs index 92e5dcc..0ba86cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,14 +41,6 @@ async fn main() { env_logger::init(); - let orig_hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |panic_info| { - error!("Panic: {}", panic_info); - - orig_hook(panic_info); - std::process::exit(1); - })); - let args: Vec = env::args().collect(); if args.len() > 2 { @@ -143,6 +135,7 @@ async fn main() { } _ = async { + #[cfg(unix)] match term { Some(ref mut term) => { let term = term.downcast_mut::().unwrap(); diff --git a/src/player/mod.rs b/src/player.rs similarity index 98% rename from src/player/mod.rs rename to src/player.rs index 42fff9d..d85100d 100644 --- a/src/player/mod.rs +++ b/src/player.rs @@ -66,7 +66,10 @@ impl SpoticordPlayer { self.session = Some(session.clone()); // Volume mixer - let mixer = (mixer::find(Some("softvol")).unwrap())(MixerConfig::default()); + let mixer = (mixer::find(Some("softvol")).unwrap())(MixerConfig { + volume_ctrl: librespot::playback::config::VolumeCtrl::Linear, + ..MixerConfig::default() + }); let client = self.client.clone(); diff --git a/src/session/manager.rs b/src/session/manager.rs index dd489a1..4f7bac5 100644 --- a/src/session/manager.rs +++ b/src/session/manager.rs @@ -47,10 +47,12 @@ impl SessionManager { ctx: &Context, guild_id: GuildId, channel_id: ChannelId, + text_channel_id: ChannelId, owner_id: UserId, ) -> Result<(), SessionCreateError> { // Create session first to make sure locks are kept for as little time as possible - let session = SpoticordSession::new(ctx, guild_id, channel_id, owner_id).await?; + let session = + SpoticordSession::new(ctx, guild_id, channel_id, text_channel_id, owner_id).await?; let mut sessions = self.sessions.write().await; let mut owner_map = self.owner_map.write().await; diff --git a/src/session/mod.rs b/src/session/mod.rs index 4a175cd..9c574f0 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -1,14 +1,19 @@ -use self::manager::{SessionCreateError, SessionManager}; +use self::{ + manager::{SessionCreateError, SessionManager}, + pbi::PlaybackInfo, +}; use crate::{ + consts::DISCONNECT_TIME, database::{Database, DatabaseError}, ipc::{self, packet::IpcPacket, Client}, - utils::{self, spotify}, + utils::{embed::Status, spotify}, }; use ipc_channel::ipc::{IpcError, TryRecvError}; use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; use log::*; use serenity::{ async_trait, + http::Http, model::prelude::{ChannelId, GuildId, UserId}, prelude::{Context, RwLock}, }; @@ -26,114 +31,16 @@ use std::{ use tokio::sync::Mutex; pub mod manager; - -#[derive(Clone)] -pub struct PlaybackInfo { - last_updated: u128, - position_ms: u32, - - pub track: Option, - pub episode: Option, - pub spotify_id: Option, - - pub duration_ms: u32, - pub is_playing: bool, -} - -impl PlaybackInfo { - fn new(duration_ms: u32, position_ms: u32, is_playing: bool) -> Self { - Self { - last_updated: utils::get_time_ms(), - track: None, - episode: None, - spotify_id: None, - duration_ms, - position_ms, - is_playing, - } - } - - // Update position, duration and playback state - async fn update_pos_dur(&mut self, position_ms: u32, duration_ms: u32, is_playing: bool) { - self.position_ms = position_ms; - self.duration_ms = duration_ms; - self.is_playing = is_playing; - - self.last_updated = utils::get_time_ms(); - } - - // Update spotify id, track and episode - fn update_track_episode( - &mut self, - spotify_id: SpotifyId, - track: Option, - episode: Option, - ) { - self.spotify_id = Some(spotify_id); - self.track = track; - self.episode = episode; - } - - pub fn get_position(&self) -> u32 { - if self.is_playing { - let now = utils::get_time_ms(); - let diff = now - self.last_updated; - - self.position_ms + diff as u32 - } else { - self.position_ms - } - } - - pub fn get_name(&self) -> Option { - if let Some(track) = &self.track { - Some(track.name.clone()) - } else if let Some(episode) = &self.episode { - Some(episode.name.clone()) - } else { - None - } - } - - pub fn get_artists(&self) -> Option { - if let Some(track) = &self.track { - Some( - track - .artists - .iter() - .map(|a| a.name.clone()) - .collect::>() - .join(", "), - ) - } else if let Some(episode) = &self.episode { - Some(episode.show.name.clone()) - } else { - None - } - } - - pub fn get_thumbnail_url(&self) -> Option { - if let Some(track) = &self.track { - let mut images = track.album.images.clone(); - images.sort_by(|a, b| b.width.cmp(&a.width)); - - Some(images.get(0).unwrap().url.clone()) - } else if let Some(episode) = &self.episode { - let mut images = episode.show.images.clone(); - images.sort_by(|a, b| b.width.cmp(&a.width)); - - Some(images.get(0).unwrap().url.clone()) - } else { - None - } - } -} +mod pbi; #[derive(Clone)] pub struct SpoticordSession { owner: Arc>>, guild_id: GuildId, channel_id: ChannelId, + text_channel_id: ChannelId, + + http: Arc, session_manager: SessionManager, @@ -150,6 +57,7 @@ impl SpoticordSession { ctx: &Context, guild_id: GuildId, channel_id: ChannelId, + text_channel_id: ChannelId, owner_id: UserId, ) -> Result { // Get the Spotify token of the owner @@ -237,6 +145,8 @@ impl SpoticordSession { owner: Arc::new(RwLock::new(Some(owner_id.clone()))), guild_id, channel_id, + text_channel_id, + http: ctx.http.clone(), session_manager: session_manager.clone(), call: call.clone(), track: track_handle.clone(), @@ -336,6 +246,8 @@ impl SpoticordSession { } IpcPacket::Paused(track, position_ms, duration_ms) => { + ipc_instance.start_disconnect_timer().await; + // Convert to SpotifyId let track_id = SpotifyId::from_uri(&track).unwrap(); @@ -547,6 +459,28 @@ impl SpoticordSession { } } + /// Disconnect from voice channel with a message + pub async fn disconnect_with_message(&self, content: &str) { + self.disconnect().await; + + if let Err(why) = self + .text_channel_id + .send_message(&self.http, |message| { + message.embed(|embed| { + embed.title("Disconnected from voice channel"); + embed.description(content); + embed.color(Status::Warning as u64); + + embed + }) + }) + .await + .map(|_| ()) + { + error!("Failed to send disconnect message: {:?}", why); + } + } + // Update playback info (duration, position, playing state) async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool { let is_none = { @@ -572,6 +506,44 @@ impl SpoticordSession { is_none } + /// Start the disconnect timer, which will disconnect the bot from the voice channel after a + /// certain amount of time + async fn start_disconnect_timer(&self) { + let pbi = self.playback_info.clone(); + let instance = self.clone(); + + tokio::spawn(async move { + let mut timer = tokio::time::interval(Duration::from_secs(DISCONNECT_TIME)); + + // Ignore first (immediate) tick + timer.tick().await; + + loop { + timer.tick().await; + + let is_playing = { + let pbi = pbi.read().await; + + if let Some(pbi) = &*pbi { + pbi.is_playing + } else { + false + } + }; + + if !is_playing { + info!("Player is not playing, disconnecting"); + instance + .disconnect_with_message( + "The player has been inactive for too long, and has been disconnected.", + ) + .await; + break; + } + } + }); + } + // Get the playback info for the current track pub async fn get_playback_info(&self) -> Option { self.playback_info.read().await.clone() diff --git a/src/session/pbi.rs b/src/session/pbi.rs new file mode 100644 index 0000000..fea90d6 --- /dev/null +++ b/src/session/pbi.rs @@ -0,0 +1,110 @@ +use librespot::core::spotify_id::SpotifyId; + +use crate::utils::{self, spotify}; + +#[derive(Clone)] +pub struct PlaybackInfo { + last_updated: u128, + position_ms: u32, + + pub track: Option, + pub episode: Option, + pub spotify_id: Option, + + pub duration_ms: u32, + pub is_playing: bool, +} + +impl PlaybackInfo { + /// Create a new instance of PlaybackInfo + pub fn new(duration_ms: u32, position_ms: u32, is_playing: bool) -> Self { + Self { + last_updated: utils::get_time_ms(), + track: None, + episode: None, + spotify_id: None, + duration_ms, + position_ms, + is_playing, + } + } + + /// Update position, duration and playback state + pub async fn update_pos_dur(&mut self, position_ms: u32, duration_ms: u32, is_playing: bool) { + self.position_ms = position_ms; + self.duration_ms = duration_ms; + self.is_playing = is_playing; + + self.last_updated = utils::get_time_ms(); + } + + /// Update spotify id, track and episode + pub fn update_track_episode( + &mut self, + spotify_id: SpotifyId, + track: Option, + episode: Option, + ) { + self.spotify_id = Some(spotify_id); + self.track = track; + self.episode = episode; + } + + /// Get the current playback position + pub fn get_position(&self) -> u32 { + if self.is_playing { + let now = utils::get_time_ms(); + let diff = now - self.last_updated; + + self.position_ms + diff as u32 + } else { + self.position_ms + } + } + + /// Get the name of the track or episode + pub fn get_name(&self) -> Option { + if let Some(track) = &self.track { + Some(track.name.clone()) + } else if let Some(episode) = &self.episode { + Some(episode.name.clone()) + } else { + None + } + } + + /// Get the artist(s) or show name of the current track + pub fn get_artists(&self) -> Option { + if let Some(track) = &self.track { + Some( + track + .artists + .iter() + .map(|a| a.name.clone()) + .collect::>() + .join(", "), + ) + } else if let Some(episode) = &self.episode { + Some(episode.show.name.clone()) + } else { + None + } + } + + /// Get the album art url + pub fn get_thumbnail_url(&self) -> Option { + if let Some(track) = &self.track { + let mut images = track.album.images.clone(); + images.sort_by(|a, b| b.width.cmp(&a.width)); + + Some(images.get(0).unwrap().url.clone()) + } else if let Some(episode) = &self.episode { + let mut images = episode.show.images.clone(); + images.sort_by(|a, b| b.width.cmp(&a.width)); + + Some(images.get(0).unwrap().url.clone()) + } else { + None + } + } +} diff --git a/src/stats/mod.rs b/src/stats.rs similarity index 100% rename from src/stats/mod.rs rename to src/stats.rs diff --git a/src/utils/embed.rs b/src/utils/embed.rs index 83f8351..075baaa 100644 --- a/src/utils/embed.rs +++ b/src/utils/embed.rs @@ -1,6 +1,5 @@ use serenity::builder::CreateEmbed; -#[allow(dead_code)] pub enum Status { Info = 0x0773D6, Success = 0x3BD65D,