Updates, bugfixes. You name it.
parent
9798a178d7
commit
b7c85455ce
|
@ -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;
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum Status {
|
||||
Info = 0x0773D6,
|
||||
Success = 0x3BD65D,
|
||||
|
|
Loading…
Reference in New Issue