Updates, bugfixes. You name it.
This commit is contained in:
		
							parent
							
								
									9798a178d7
								
							
						
					
					
						commit
						b7c85455ce
					
				| @ -143,7 +143,13 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu | |||||||
|     } else { |     } else { | ||||||
|       // Create the session, and handle potential errors
 |       // Create the session, and handle potential errors
 | ||||||
|       if let Err(why) = session_manager |       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 |         .await | ||||||
|       { |       { | ||||||
|         // Need to link first
 |         // 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") |         .icon_url("https://spoticord.com/static/image/speaker.png") | ||||||
|         .description(format!("Come listen along in <#{}>", channel_id)) |         .description(format!("Come listen along in <#{}>", channel_id)) | ||||||
|         .footer("Spotify will automatically start playing on Spoticord") |         .footer("Spotify will automatically start playing on Spoticord") | ||||||
|         .status(Status::Success) |         .status(Status::Info) | ||||||
|         .build(), |         .build(), | ||||||
|     ) |     ) | ||||||
|     .await; |     .await; | ||||||
|  | |||||||
| @ -1,3 +1,7 @@ | |||||||
| pub const VERSION: &str = env!("CARGO_PKG_VERSION"); | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); | ||||||
| pub const MOTD: &str = "OPEN BETA (v2)"; | 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";
 | // pub const MOTD: &str = "some good 'ol music";
 | ||||||
|  | |||||||
| @ -41,14 +41,6 @@ async fn main() { | |||||||
| 
 | 
 | ||||||
|   env_logger::init(); |   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<String> = env::args().collect(); |   let args: Vec<String> = env::args().collect(); | ||||||
| 
 | 
 | ||||||
|   if args.len() > 2 { |   if args.len() > 2 { | ||||||
| @ -143,6 +135,7 @@ async fn main() { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         _ = async { |         _ = async { | ||||||
|  |           #[cfg(unix)] | ||||||
|           match term { |           match term { | ||||||
|             Some(ref mut term) => { |             Some(ref mut term) => { | ||||||
|               let term = term.downcast_mut::<tokio::signal::unix::Signal>().unwrap(); |               let term = term.downcast_mut::<tokio::signal::unix::Signal>().unwrap(); | ||||||
|  | |||||||
| @ -66,7 +66,10 @@ impl SpoticordPlayer { | |||||||
|     self.session = Some(session.clone()); |     self.session = Some(session.clone()); | ||||||
| 
 | 
 | ||||||
|     // Volume mixer
 |     // 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(); |     let client = self.client.clone(); | ||||||
| 
 | 
 | ||||||
| @ -47,10 +47,12 @@ impl SessionManager { | |||||||
|     ctx: &Context, |     ctx: &Context, | ||||||
|     guild_id: GuildId, |     guild_id: GuildId, | ||||||
|     channel_id: ChannelId, |     channel_id: ChannelId, | ||||||
|  |     text_channel_id: ChannelId, | ||||||
|     owner_id: UserId, |     owner_id: UserId, | ||||||
|   ) -> Result<(), SessionCreateError> { |   ) -> Result<(), SessionCreateError> { | ||||||
|     // Create session first to make sure locks are kept for as little time as possible
 |     // 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 sessions = self.sessions.write().await; | ||||||
|     let mut owner_map = self.owner_map.write().await; |     let mut owner_map = self.owner_map.write().await; | ||||||
|  | |||||||
| @ -1,14 +1,19 @@ | |||||||
| use self::manager::{SessionCreateError, SessionManager}; | use self::{ | ||||||
|  |   manager::{SessionCreateError, SessionManager}, | ||||||
|  |   pbi::PlaybackInfo, | ||||||
|  | }; | ||||||
| use crate::{ | use crate::{ | ||||||
|  |   consts::DISCONNECT_TIME, | ||||||
|   database::{Database, DatabaseError}, |   database::{Database, DatabaseError}, | ||||||
|   ipc::{self, packet::IpcPacket, Client}, |   ipc::{self, packet::IpcPacket, Client}, | ||||||
|   utils::{self, spotify}, |   utils::{embed::Status, spotify}, | ||||||
| }; | }; | ||||||
| use ipc_channel::ipc::{IpcError, TryRecvError}; | use ipc_channel::ipc::{IpcError, TryRecvError}; | ||||||
| use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; | use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId}; | ||||||
| use log::*; | use log::*; | ||||||
| use serenity::{ | use serenity::{ | ||||||
|   async_trait, |   async_trait, | ||||||
|  |   http::Http, | ||||||
|   model::prelude::{ChannelId, GuildId, UserId}, |   model::prelude::{ChannelId, GuildId, UserId}, | ||||||
|   prelude::{Context, RwLock}, |   prelude::{Context, RwLock}, | ||||||
| }; | }; | ||||||
| @ -26,114 +31,16 @@ use std::{ | |||||||
| use tokio::sync::Mutex; | use tokio::sync::Mutex; | ||||||
| 
 | 
 | ||||||
| pub mod manager; | pub mod manager; | ||||||
| 
 | mod pbi; | ||||||
| #[derive(Clone)] |  | ||||||
| pub struct PlaybackInfo { |  | ||||||
|   last_updated: u128, |  | ||||||
|   position_ms: u32, |  | ||||||
| 
 |  | ||||||
|   pub track: Option<spotify::Track>, |  | ||||||
|   pub episode: Option<spotify::Episode>, |  | ||||||
|   pub spotify_id: Option<SpotifyId>, |  | ||||||
| 
 |  | ||||||
|   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<spotify::Track>, |  | ||||||
|     episode: Option<spotify::Episode>, |  | ||||||
|   ) { |  | ||||||
|     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<String> { |  | ||||||
|     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<String> { |  | ||||||
|     if let Some(track) = &self.track { |  | ||||||
|       Some( |  | ||||||
|         track |  | ||||||
|           .artists |  | ||||||
|           .iter() |  | ||||||
|           .map(|a| a.name.clone()) |  | ||||||
|           .collect::<Vec<String>>() |  | ||||||
|           .join(", "), |  | ||||||
|       ) |  | ||||||
|     } else if let Some(episode) = &self.episode { |  | ||||||
|       Some(episode.show.name.clone()) |  | ||||||
|     } else { |  | ||||||
|       None |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   pub fn get_thumbnail_url(&self) -> Option<String> { |  | ||||||
|     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 |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| #[derive(Clone)] | #[derive(Clone)] | ||||||
| pub struct SpoticordSession { | pub struct SpoticordSession { | ||||||
|   owner: Arc<RwLock<Option<UserId>>>, |   owner: Arc<RwLock<Option<UserId>>>, | ||||||
|   guild_id: GuildId, |   guild_id: GuildId, | ||||||
|   channel_id: ChannelId, |   channel_id: ChannelId, | ||||||
|  |   text_channel_id: ChannelId, | ||||||
|  | 
 | ||||||
|  |   http: Arc<Http>, | ||||||
| 
 | 
 | ||||||
|   session_manager: SessionManager, |   session_manager: SessionManager, | ||||||
| 
 | 
 | ||||||
| @ -150,6 +57,7 @@ impl SpoticordSession { | |||||||
|     ctx: &Context, |     ctx: &Context, | ||||||
|     guild_id: GuildId, |     guild_id: GuildId, | ||||||
|     channel_id: ChannelId, |     channel_id: ChannelId, | ||||||
|  |     text_channel_id: ChannelId, | ||||||
|     owner_id: UserId, |     owner_id: UserId, | ||||||
|   ) -> Result<SpoticordSession, SessionCreateError> { |   ) -> Result<SpoticordSession, SessionCreateError> { | ||||||
|     // Get the Spotify token of the owner
 |     // Get the Spotify token of the owner
 | ||||||
| @ -237,6 +145,8 @@ impl SpoticordSession { | |||||||
|       owner: Arc::new(RwLock::new(Some(owner_id.clone()))), |       owner: Arc::new(RwLock::new(Some(owner_id.clone()))), | ||||||
|       guild_id, |       guild_id, | ||||||
|       channel_id, |       channel_id, | ||||||
|  |       text_channel_id, | ||||||
|  |       http: ctx.http.clone(), | ||||||
|       session_manager: session_manager.clone(), |       session_manager: session_manager.clone(), | ||||||
|       call: call.clone(), |       call: call.clone(), | ||||||
|       track: track_handle.clone(), |       track: track_handle.clone(), | ||||||
| @ -336,6 +246,8 @@ impl SpoticordSession { | |||||||
|           } |           } | ||||||
| 
 | 
 | ||||||
|           IpcPacket::Paused(track, position_ms, duration_ms) => { |           IpcPacket::Paused(track, position_ms, duration_ms) => { | ||||||
|  |             ipc_instance.start_disconnect_timer().await; | ||||||
|  | 
 | ||||||
|             // Convert to SpotifyId
 |             // Convert to SpotifyId
 | ||||||
|             let track_id = SpotifyId::from_uri(&track).unwrap(); |             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)
 |   // Update playback info (duration, position, playing state)
 | ||||||
|   async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool { |   async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool { | ||||||
|     let is_none = { |     let is_none = { | ||||||
| @ -572,6 +506,44 @@ impl SpoticordSession { | |||||||
|     is_none |     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
 |   // Get the playback info for the current track
 | ||||||
|   pub async fn get_playback_info(&self) -> Option<PlaybackInfo> { |   pub async fn get_playback_info(&self) -> Option<PlaybackInfo> { | ||||||
|     self.playback_info.read().await.clone() |     self.playback_info.read().await.clone() | ||||||
|  | |||||||
							
								
								
									
										110
									
								
								src/session/pbi.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/session/pbi.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<spotify::Track>, | ||||||
|  |   pub episode: Option<spotify::Episode>, | ||||||
|  |   pub spotify_id: Option<SpotifyId>, | ||||||
|  | 
 | ||||||
|  |   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<spotify::Track>, | ||||||
|  |     episode: Option<spotify::Episode>, | ||||||
|  |   ) { | ||||||
|  |     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<String> { | ||||||
|  |     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<String> { | ||||||
|  |     if let Some(track) = &self.track { | ||||||
|  |       Some( | ||||||
|  |         track | ||||||
|  |           .artists | ||||||
|  |           .iter() | ||||||
|  |           .map(|a| a.name.clone()) | ||||||
|  |           .collect::<Vec<String>>() | ||||||
|  |           .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<String> { | ||||||
|  |     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 | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -1,6 +1,5 @@ | |||||||
| use serenity::builder::CreateEmbed; | use serenity::builder::CreateEmbed; | ||||||
| 
 | 
 | ||||||
| #[allow(dead_code)] |  | ||||||
| pub enum Status { | pub enum Status { | ||||||
|   Info = 0x0773D6, |   Info = 0x0773D6, | ||||||
|   Success = 0x3BD65D, |   Success = 0x3BD65D, | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 DaXcess
						DaXcess