spoticord/src/bot/commands/music/join.rs

340 lines
9.7 KiB
Rust

use log::error;
use serenity::{
builder::CreateApplicationCommand,
model::prelude::{interaction::application_command::ApplicationCommandInteraction, Channel},
prelude::Context,
};
use crate::{
bot::commands::{defer_message, respond_message, update_message, CommandOutput},
consts::SPOTICORD_ACCOUNTS_URL,
session::manager::{SessionCreateError, SessionManager},
utils::embed::{EmbedBuilder, Status},
};
pub const NAME: &str = "join";
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
Box::pin(async move {
let guild = ctx
.cache
.guild(command.guild_id.expect("to contain a value"))
.expect("to be present");
// Get the voice channel id of the calling user
let channel_id = match guild
.voice_states
.get(&command.user.id)
.and_then(|state| state.channel_id)
{
Some(channel_id) => channel_id,
None => {
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("You need to connect to a voice channel")
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
};
// Check for Voice Channel permissions
{
let channel = match channel_id.to_channel(&ctx).await {
Ok(channel) => match channel {
Channel::Guild(channel) => channel,
_ => {
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("The voice channel you are in is not supported")
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
},
Err(why) => {
error!("Failed to get channel: {}", why);
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("The voice channel you are in is not available.\nI might not the permission to see this channel.")
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
};
if let Ok(permissions) = channel.permissions_for_user(&ctx.cache, ctx.cache.current_user_id())
{
if !permissions.view_channel() || !permissions.connect() || !permissions.speak() {
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("I do not have the permissions to connect to that voice channel")
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
}
}
// Check for Text Channel permissions
{
let channel = match command.channel_id.to_channel(&ctx).await {
Ok(channel) => match channel {
Channel::Guild(channel) => channel,
_ => {
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("The text channel you are in is not supported")
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
},
Err(why) => {
error!("Failed to get channel: {}", why);
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("The text channel you are in is not available.\nI might not have the permission to see this channel.")
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
};
if let Ok(permissions) = channel.permissions_for_user(&ctx.cache, ctx.cache.current_user_id())
{
if !permissions.view_channel() || !permissions.send_messages() || !permissions.embed_links()
{
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description(
"I do not have the permissions to send messages / links in this text channel",
)
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
}
}
let data = ctx.data.read().await;
let session_manager = data
.get::<SessionManager>()
.expect("to contain a value")
.clone();
// Check if another session is already active in this server
let mut session_opt = session_manager.get_session(guild.id).await;
if let Some(session) = &session_opt {
if let Some(owner) = session.owner().await {
let msg = if owner == command.user.id {
"You are already controlling the bot"
} else {
"The bot is currently being controlled by someone else"
};
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description(msg)
.status(Status::Error)
.build(),
true,
)
.await;
return;
}
};
// Prevent duplicate Spotify sessions
if let Some(session) = session_manager.find(command.user.id).await {
respond_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description(
format!(
"You are already playing music in another server ({}).\nStop playing in that server first before joining this one.",
ctx.cache.guild(session.guild_id().await).expect("to be present").name
)).status(Status::Error).build(),
true,
)
.await;
return;
}
defer_message(&ctx, &command, false).await;
if let Some(session) = &session_opt {
if session.channel_id().await != channel_id {
session.disconnect().await;
session_opt = None;
// Give serenity/songbird some time to register the disconnect
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
macro_rules! report_error {
($why:ident) => {
match $why {
// User has not linked their account
SessionCreateError::NoSpotify => {
update_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description(format!("You need to link your Spotify account. Use </link:1036714850367320136> or go to [the accounts website]({}) to get started.", SPOTICORD_ACCOUNTS_URL.as_str()))
.status(Status::Error)
.build(),
)
.await;
}
// Spotify credentials have expired or are invalid
SessionCreateError::SpotifyExpired => {
update_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description(format!("Spoticord no longer has access to your Spotify account. Use </link:1036714850367320136> or go to [the accounts website]({}) to relink your Spotify account.", SPOTICORD_ACCOUNTS_URL.as_str()))
.status(Status::Error)
.build(),
).await;
}
// Songbird error
SessionCreateError::JoinError(why) => {
update_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description(format!(
"An error occured while joining the channel. Please try running </join:1036714850367320142> again.\n\nError details: `{why}`"
))
.status(Status::Error)
.build(),
)
.await;
}
// Any other error
_ => {
update_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Cannot join voice channel")
.description("An error occured while joining the channel. Please try again later.")
.status(Status::Error)
.build(),
)
.await;
}
}
return;
};
}
if let Some(session) = session_opt.as_mut() {
if let Err(why) = session.update_owner(&ctx, command.user.id).await {
report_error!(why);
}
} else {
// Create the session, and handle potential errors
if let Err(why) = session_manager
.create_session(
&ctx,
guild.id,
channel_id,
command.channel_id,
command.user.id,
)
.await
{
report_error!(why);
};
}
update_message(
&ctx,
&command,
EmbedBuilder::new()
.title("Connected to voice channel")
.icon_url("https://spoticord.com/speaker.png")
.description(format!("Come listen along in <#{}>", channel_id))
.footer("You must manually go to Spotify and select your device")
.status(Status::Info)
.build(),
)
.await;
})
}
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command
.name(NAME)
.description("Request the bot to join the current voice channel")
}