2022-10-18 22:59:32 +02:00

242 lines
5.9 KiB
Rust

use self::manager::{SessionCreateError, SessionManager};
use crate::{
database::{Database, DatabaseError},
ipc::{self, packet::IpcPacket},
};
use ipc_channel::ipc::IpcError;
use log::*;
use serenity::{
async_trait,
model::prelude::{ChannelId, GuildId, UserId},
prelude::Context,
};
use songbird::{
create_player,
error::JoinResult,
input::{children_to_reader, Input},
tracks::TrackHandle,
Call, Event, EventContext, EventHandler,
};
use std::{
process::{Command, Stdio},
sync::Arc,
};
use tokio::sync::Mutex;
pub mod manager;
#[derive(Clone)]
pub struct SpoticordSession {
owner: UserId,
guild_id: GuildId,
channel_id: ChannelId,
session_manager: SessionManager,
call: Arc<Mutex<Call>>,
track: TrackHandle,
}
impl SpoticordSession {
pub async fn new(
ctx: &Context,
guild_id: GuildId,
channel_id: ChannelId,
owner_id: UserId,
) -> Result<SpoticordSession, SessionCreateError> {
// Get the Spotify token of the owner
let data = ctx.data.read().await;
let database = data.get::<Database>().unwrap();
let 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);
}
};
// Create IPC oneshot server
let (server, tx_name, rx_name) = match ipc::Server::create() {
Ok(server) => server,
Err(why) => {
error!("Failed to create IPC server: {:?}", why);
return Err(SessionCreateError::ForkError);
}
};
// Join the voice channel
let songbird = songbird::get(ctx).await.unwrap().clone();
let (call, result) = songbird.join(guild_id, channel_id).await;
if let Err(why) = result {
error!("Error joining voice channel: {:?}", why);
return Err(SessionCreateError::JoinError(channel_id, guild_id));
}
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])
.args(["--player", &tx_name, &rx_name])
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
{
Ok(child) => child,
Err(why) => {
error!("Failed to start player process: {:?}", why);
return Err(SessionCreateError::ForkError);
}
};
// Establish bi-directional IPC channel
let client = match server.accept() {
Ok(client) => client,
Err(why) => {
error!("Failed to accept IPC connection: {:?}", why);
return Err(SessionCreateError::ForkError);
}
};
// Pipe player audio to the voice channel
let reader = children_to_reader::<f32>(vec![child]);
// Create track (paused, fixes audio glitches)
let (mut track, track_handle) = create_player(Input::float_pcm(true, reader));
track.pause();
// Set call audio to track
call_mut.play_only(track);
// Clone variables for use in the IPC handler
let ipc_track = track_handle.clone();
let ipc_client = client.clone();
// Handle IPC packets
// This will automatically quit once the IPC connection is closed
tokio::spawn(async move {
let check_result = |result| {
if let Err(why) = result {
error!("Failed to issue track command: {:?}", why);
}
};
loop {
let msg = match ipc_client.recv() {
Ok(msg) => msg,
Err(why) => {
if let IpcError::Disconnected = why {
break;
}
error!("Failed to receive IPC message: {:?}", why);
break;
}
};
match msg {
IpcPacket::StartPlayback => {
check_result(ipc_track.play());
}
IpcPacket::StopPlayback => {
check_result(ipc_track.pause());
}
_ => {}
}
}
});
// 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(),
);
call_mut.add_global_event(
songbird::Event::Core(songbird::CoreEvent::ClientDisconnect),
instance.clone(),
);
if let Err(why) = client.send(IpcPacket::Connect(token, user.device_name)) {
error!("Failed to send IpcPacket::Connect packet: {:?}", why);
}
Ok(instance)
}
pub async fn disconnect(&self) -> JoinResult<()> {
info!("Disconnecting from voice channel {}", self.channel_id);
self
.session_manager
.clone()
.remove_session(self.guild_id)
.await;
let mut call = self.call.lock().await;
self.track.stop().unwrap_or(());
call.remove_all_global_events();
call.leave().await
}
pub fn get_owner(&self) -> UserId {
self.owner
}
pub fn get_guild_id(&self) -> GuildId {
self.guild_id
}
pub fn get_channel_id(&self) -> ChannelId {
self.channel_id
}
}
#[async_trait]
impl EventHandler for SpoticordSession {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
match ctx {
EventContext::DriverDisconnect(_) => {
debug!("Driver disconnected, leaving voice channel");
self.disconnect().await.ok();
}
EventContext::ClientDisconnect(who) => {
debug!("Client disconnected, {}", who.user_id.to_string());
}
_ => {}
}
return None;
}
}