Updates, bugfixes. You name it.

main
DaXcess 2022-11-07 12:58:22 +01:00
parent 9798a178d7
commit b7c85455ce
9 changed files with 206 additions and 117 deletions

View File

@ -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;

View File

@ -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";

View File

@ -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<String> = 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::<tokio::signal::unix::Signal>().unwrap();

View File

@ -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();

View File

@ -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;

View File

@ -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<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
}
}
}
mod pbi;
#[derive(Clone)]
pub struct SpoticordSession {
owner: Arc<RwLock<Option<UserId>>>,
guild_id: GuildId,
channel_id: ChannelId,
text_channel_id: ChannelId,
http: Arc<Http>,
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<SpoticordSession, SessionCreateError> {
// 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<PlaybackInfo> {
self.playback_info.read().await.clone()

110
src/session/pbi.rs 100644
View 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
}
}
}

View File

@ -1,6 +1,5 @@
use serenity::builder::CreateEmbed;
#[allow(dead_code)]
pub enum Status {
Info = 0x0773D6,
Success = 0x3BD65D,