Uhm so yeah here's some c o d e

This commit is contained in:
DaXcess committed 2022-10-31 22:46:34 +01:00
1 parent 6a77189343
commit d530b182b3
33 files changed
+1934 -298

No files matched your search

+38 -1
View File
@@ -60,12 +60,34 @@ impl SessionManager {
Ok(())
}
/// Remove (and destroy) a session
/// Remove a session
pub async fn remove_session(&mut self, guild_id: GuildId) {
let mut sessions = self.sessions.write().await;
if let Some(session) = sessions.get(&guild_id) {
if let Some(owner) = session.get_owner().await {
let mut owner_map = self.owner_map.write().await;
owner_map.remove(&owner);
}
}
sessions.remove(&guild_id);
}
/// Remove owner from owner map.
/// Used whenever a user stops playing music without leaving the bot.
pub async fn remove_owner(&mut self, owner_id: UserId) {
let mut owner_map = self.owner_map.write().await;
owner_map.remove(&owner_id);
}
/// Set the owner of a session
/// Used when a user joins a session that is already active
pub async fn set_owner(&mut self, owner_id: UserId, guild_id: GuildId) {
let mut owner_map = self.owner_map.write().await;
owner_map.insert(owner_id, guild_id);
}
/// Get a session by its guild ID
pub async fn get_session(&self, guild_id: GuildId) -> Option<Arc<SpoticordSession>> {
let sessions = self.sessions.read().await;
@@ -82,4 +104,19 @@ impl SessionManager {
sessions.get(&guild_id).cloned()
}
/// Get the amount of sessions with an owner
pub async fn get_active_session_count(&self) -> usize {
let sessions = self.sessions.read().await;
let mut count: usize = 0;
for session in sessions.values() {
if session.owner.read().await.is_some() {
count += 1;
}
}
count
}
}
+390 -21
View File
@@ -1,18 +1,19 @@
use self::manager::{SessionCreateError, SessionManager};
use crate::{
database::{Database, DatabaseError},
ipc::{self, packet::IpcPacket},
ipc::{self, packet::IpcPacket, Client},
utils::{self, spotify},
};
use ipc_channel::ipc::IpcError;
use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId};
use log::*;
use serenity::{
async_trait,
model::prelude::{ChannelId, GuildId, UserId},
prelude::Context,
prelude::{Context, RwLock},
};
use songbird::{
create_player,
error::JoinResult,
input::{children_to_reader, Input},
tracks::TrackHandle,
Call, Event, EventContext, EventHandler,
@@ -25,9 +26,111 @@ use tokio::sync::Mutex;
pub mod manager;
#[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)]
pub struct SpoticordSession {
owner: UserId,
owner: Arc<RwLock<Option<UserId>>>,
guild_id: GuildId,
channel_id: ChannelId,
@@ -35,6 +138,10 @@ pub struct SpoticordSession {
call: Arc<Mutex<Call>>,
track: TrackHandle,
playback_info: Arc<RwLock<Option<PlaybackInfo>>>,
client: Client,
}
impl SpoticordSession {
@@ -92,8 +199,7 @@ impl SpoticordSession {
let mut call_mut = call.lock().await;
// Spawn player process
let args: Vec<String> = std::env::args().collect();
let child = match Command::new(&args[0])
let child = match Command::new(std::env::current_exe().unwrap())
.args(["--player", &tx_name, &rx_name])
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
@@ -126,9 +232,22 @@ impl SpoticordSession {
// Set call audio to track
call_mut.play_only(track);
let instance = Self {
owner: Arc::new(RwLock::new(Some(owner_id.clone()))),
guild_id,
channel_id,
session_manager: session_manager.clone(),
call: call.clone(),
track: track_handle.clone(),
playback_info: Arc::new(RwLock::new(None)),
client: client.clone(),
};
// Clone variables for use in the IPC handler
let ipc_track = track_handle.clone();
let ipc_client = client.clone();
let ipc_context = ctx.clone();
let mut ipc_instance = instance.clone();
// Handle IPC packets
// This will automatically quit once the IPC connection is closed
@@ -140,6 +259,9 @@ impl SpoticordSession {
};
loop {
// Required for IpcPacket::TrackChange to work
tokio::task::yield_now().await;
let msg = match ipc_client.recv() {
Ok(msg) => msg,
Err(why) => {
@@ -153,29 +275,89 @@ impl SpoticordSession {
};
match msg {
// Sink requests playback to start/resume
IpcPacket::StartPlayback => {
check_result(ipc_track.play());
}
// Sink requests playback to pause
IpcPacket::StopPlayback => {
check_result(ipc_track.pause());
}
// A new track has been set by the player
IpcPacket::TrackChange(track) => {
// Convert to SpotifyId
let track_id = SpotifyId::from_uri(&track).unwrap();
let mut instance = ipc_instance.clone();
let context = ipc_context.clone();
tokio::spawn(async move {
if let Err(why) = instance.update_track(&context, &owner_id, track_id).await {
error!("Failed to update track: {:?}", why);
instance.player_stopped().await;
}
});
}
// The player has started playing a track
IpcPacket::Playing(track, position_ms, duration_ms) => {
// Convert to SpotifyId
let track_id = SpotifyId::from_uri(&track).unwrap();
let was_none = ipc_instance
.update_playback(duration_ms, position_ms, true)
.await;
if was_none {
// Stop player if update track fails
if let Err(why) = ipc_instance
.update_track(&ipc_context, &owner_id, track_id)
.await
{
error!("Failed to update track: {:?}", why);
ipc_instance.player_stopped().await;
return;
}
}
}
IpcPacket::Paused(track, position_ms, duration_ms) => {
// Convert to SpotifyId
let track_id = SpotifyId::from_uri(&track).unwrap();
let was_none = ipc_instance
.update_playback(duration_ms, position_ms, true)
.await;
if was_none {
// Stop player if update track fails
if let Err(why) = ipc_instance
.update_track(&ipc_context, &owner_id, track_id)
.await
{
error!("Failed to update track: {:?}", why);
ipc_instance.player_stopped().await;
return;
}
}
}
IpcPacket::Stopped => {
ipc_instance.player_stopped().await;
}
// Ignore other packets
_ => {}
}
}
});
// Set up events
let instance = Self {
owner: owner_id,
guild_id,
channel_id,
session_manager,
call: call.clone(),
track: track_handle,
};
call_mut.add_global_event(
songbird::Event::Core(songbird::CoreEvent::DriverDisconnect),
instance.clone(),
@@ -186,6 +368,7 @@ impl SpoticordSession {
instance.clone(),
);
// Inform the player process to connect to Spotify
if let Err(why) = client.send(IpcPacket::Connect(token, user.device_name)) {
error!("Failed to send IpcPacket::Connect packet: {:?}", why);
}
@@ -193,7 +376,147 @@ impl SpoticordSession {
Ok(instance)
}
pub async fn disconnect(&self) -> JoinResult<()> {
pub async fn update_owner(
&self,
ctx: &Context,
owner_id: UserId,
) -> Result<(), SessionCreateError> {
// Get the Spotify token of the owner
let data = ctx.data.read().await;
let database = data.get::<Database>().unwrap();
let mut session_manager = data.get::<SessionManager>().unwrap().clone();
let token = match database.get_access_token(owner_id.to_string()).await {
Ok(token) => token,
Err(why) => {
if let DatabaseError::InvalidStatusCode(code) = why {
if code == 404 {
return Err(SessionCreateError::NoSpotifyError);
}
}
return Err(SessionCreateError::DatabaseError);
}
};
let user = match database.get_user(owner_id.to_string()).await {
Ok(user) => user,
Err(why) => {
error!("Failed to get user: {:?}", why);
return Err(SessionCreateError::DatabaseError);
}
};
let mut owner = self.owner.write().await;
*owner = Some(owner_id);
session_manager.set_owner(owner_id, self.guild_id).await;
// Inform the player process to connect to Spotify
if let Err(why) = self
.client
.send(IpcPacket::Connect(token, user.device_name))
{
error!("Failed to send IpcPacket::Connect packet: {:?}", why);
}
Ok(())
}
// Update current track
async fn update_track(
&self,
ctx: &Context,
owner_id: &UserId,
spotify_id: SpotifyId,
) -> Result<(), String> {
let should_update = {
let pbi = self.playback_info.read().await;
if let Some(pbi) = &*pbi {
pbi.spotify_id.is_none() || pbi.spotify_id.unwrap() != spotify_id
} else {
false
}
};
if !should_update {
return Ok(());
}
let data = ctx.data.read().await;
let database = data.get::<Database>().unwrap();
let token = match database.get_access_token(&owner_id.to_string()).await {
Ok(token) => token,
Err(why) => {
error!("Failed to get access token: {:?}", why);
return Err("Failed to get access token".to_string());
}
};
let mut track: Option<spotify::Track> = None;
let mut episode: Option<spotify::Episode> = None;
if spotify_id.audio_type == SpotifyAudioType::Track {
let track_info = match spotify::get_track_info(&token, spotify_id).await {
Ok(track) => track,
Err(why) => {
error!("Failed to get track info: {:?}", why);
return Err("Failed to get track info".to_string());
}
};
trace!("Received track info: {:?}", track_info);
track = Some(track_info);
} else if spotify_id.audio_type == SpotifyAudioType::Podcast {
let episode_info = match spotify::get_episode_info(&token, spotify_id).await {
Ok(episode) => episode,
Err(why) => {
error!("Failed to get episode info: {:?}", why);
return Err("Failed to get episode info".to_string());
}
};
trace!("Received episode info: {:?}", episode_info);
episode = Some(episode_info);
}
let mut pbi = self.playback_info.write().await;
if let Some(pbi) = &mut *pbi {
pbi.update_track_episode(spotify_id, track, episode);
}
Ok(())
}
/// Called when the player must stop, but not leave the call
async fn player_stopped(&mut self) {
if let Err(why) = self.track.pause() {
error!("Failed to pause track: {:?}", why);
}
// Disconnect from Spotify
if let Err(why) = self.client.send(IpcPacket::Disconnect) {
error!("Failed to send disconnect packet: {:?}", why);
}
// Clear owner
let mut owner = self.owner.write().await;
if let Some(owner_id) = owner.take() {
self.session_manager.remove_owner(owner_id).await;
}
// Clear playback info
let mut playback_info = self.playback_info.write().await;
*playback_info = None;
}
// Disconnect from voice channel and remove session from manager
pub async fn disconnect(&self) {
info!("Disconnecting from voice channel {}", self.channel_id);
self
@@ -206,17 +529,55 @@ impl SpoticordSession {
self.track.stop().unwrap_or(());
call.remove_all_global_events();
call.leave().await
if let Err(why) = call.leave().await {
error!("Failed to leave voice channel: {:?}", why);
}
}
pub fn get_owner(&self) -> UserId {
self.owner
// Update playback info (duration, position, playing state)
async fn update_playback(&self, duration_ms: u32, position_ms: u32, playing: bool) -> bool {
let is_none = {
let pbi = self.playback_info.read().await;
pbi.is_none()
};
if is_none {
let mut pbi = self.playback_info.write().await;
*pbi = Some(PlaybackInfo::new(duration_ms, position_ms, playing));
} else {
let mut pbi = self.playback_info.write().await;
// Update position, duration and playback state
pbi
.as_mut()
.unwrap()
.update_pos_dur(position_ms, duration_ms, playing)
.await;
};
is_none
}
// Get the playback info for the current track
pub async fn get_playback_info(&self) -> Option<PlaybackInfo> {
self.playback_info.read().await.clone()
}
// Get the current owner of this session
pub async fn get_owner(&self) -> Option<UserId> {
let owner = self.owner.read().await;
*owner
}
// Get the server id this session is playing in
pub fn get_guild_id(&self) -> GuildId {
self.guild_id
}
// Get the channel id this session is playing in
pub fn get_channel_id(&self) -> ChannelId {
self.channel_id
}
@@ -228,10 +589,18 @@ impl EventHandler for SpoticordSession {
match ctx {
EventContext::DriverDisconnect(_) => {
debug!("Driver disconnected, leaving voice channel");
self.disconnect().await.ok();
self.disconnect().await;
}
EventContext::ClientDisconnect(who) => {
debug!("Client disconnected, {}", who.user_id.to_string());
trace!("Client disconnected, {}", who.user_id.to_string());
if let Some(session) = self.session_manager.find(UserId(who.user_id.0)).await {
if session.get_guild_id() == self.guild_id && session.get_channel_id() == self.channel_id
{
// Clone because haha immutable references
self.clone().player_stopped().await;
}
}
}
_ => {}
}