Bring back the buttons
parent
22dde966b5
commit
da1d0d1fec
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BRED='\033[1;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
diff=$(cargo clippy --all -- -D warnings -D clippy::unwrap_used)
|
||||||
|
result=$?
|
||||||
|
|
||||||
|
if [[ ${result} -ne 0 ]] ; then
|
||||||
|
echo -e "\n${BRED}Cannot commit:${NC} There are some clippy issues in your code, check the above output for any errors."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
|
@ -916,9 +916,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "0.2.8"
|
version = "0.2.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
@ -2459,9 +2459,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.7"
|
version = "0.4.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
|
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
@ -2770,9 +2770,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.11"
|
version = "0.1.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce"
|
checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "help";
|
pub const NAME: &str = "help";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
respond_message(
|
respond_message(
|
||||||
&ctx,
|
&ctx,
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "link";
|
pub const NAME: &str = "link";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let database = data.get::<Database>().expect("to contain a value");
|
let database = data.get::<Database>().expect("to contain a value");
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "rename";
|
pub const NAME: &str = "rename";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let database = data.get::<Database>().expect("to contain a value");
|
let database = data.get::<Database>().expect("to contain a value");
|
||||||
|
|
|
@ -14,7 +14,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "unlink";
|
pub const NAME: &str = "unlink";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let database = data.get::<Database>().expect("to contain a value");
|
let database = data.get::<Database>().expect("to contain a value");
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::{bot::commands::CommandOutput, consts::VERSION, utils::embed::Status}
|
||||||
|
|
||||||
pub const NAME: &str = "version";
|
pub const NAME: &str = "version";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
if let Err(why) = command
|
if let Err(why) = command
|
||||||
.create_interaction_response(&ctx.http, |response| {
|
.create_interaction_response(&ctx.http, |response| {
|
||||||
|
|
|
@ -5,7 +5,10 @@ use serenity::{
|
||||||
builder::{CreateApplicationCommand, CreateApplicationCommands},
|
builder::{CreateApplicationCommand, CreateApplicationCommands},
|
||||||
model::application::command::Command,
|
model::application::command::Command,
|
||||||
model::prelude::{
|
model::prelude::{
|
||||||
interaction::{application_command::ApplicationCommandInteraction, InteractionResponseType},
|
interaction::{
|
||||||
|
application_command::ApplicationCommandInteraction,
|
||||||
|
message_component::MessageComponentInteraction, InteractionResponseType,
|
||||||
|
},
|
||||||
GuildId,
|
GuildId,
|
||||||
},
|
},
|
||||||
prelude::{Context, TypeMapKey},
|
prelude::{Context, TypeMapKey},
|
||||||
|
@ -44,6 +47,28 @@ pub async fn respond_message(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn respond_component_message(
|
||||||
|
ctx: &Context,
|
||||||
|
component: &MessageComponentInteraction,
|
||||||
|
options: EmbedMessageOptions,
|
||||||
|
ephemeral: bool,
|
||||||
|
) {
|
||||||
|
if let Err(why) = component
|
||||||
|
.create_interaction_response(&ctx.http, |response| {
|
||||||
|
response
|
||||||
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
.interaction_response_data(|message| {
|
||||||
|
message
|
||||||
|
.embed(|embed| make_embed_message(embed, options))
|
||||||
|
.ephemeral(ephemeral)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Error sending message: {:?}", why);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update_message(
|
pub async fn update_message(
|
||||||
ctx: &Context,
|
ctx: &Context,
|
||||||
command: &ApplicationCommandInteraction,
|
command: &ApplicationCommandInteraction,
|
||||||
|
@ -78,6 +103,7 @@ pub async fn defer_message(
|
||||||
|
|
||||||
pub type CommandOutput = Pin<Box<dyn Future<Output = ()> + Send>>;
|
pub type CommandOutput = Pin<Box<dyn Future<Output = ()> + Send>>;
|
||||||
pub type CommandExecutor = fn(Context, ApplicationCommandInteraction) -> CommandOutput;
|
pub type CommandExecutor = fn(Context, ApplicationCommandInteraction) -> CommandOutput;
|
||||||
|
pub type ComponentExecutor = fn(Context, MessageComponentInteraction) -> CommandOutput;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CommandManager {
|
pub struct CommandManager {
|
||||||
|
@ -87,7 +113,8 @@ pub struct CommandManager {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct CommandInfo {
|
pub struct CommandInfo {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub executor: CommandExecutor,
|
pub command_executor: CommandExecutor,
|
||||||
|
pub component_executor: Option<ComponentExecutor>,
|
||||||
pub register: fn(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand,
|
pub register: fn(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,50 +127,71 @@ impl CommandManager {
|
||||||
// Debug-only commands
|
// Debug-only commands
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
instance.insert_command(ping::NAME, ping::register, ping::run);
|
instance.insert(ping::NAME, ping::register, ping::command, None);
|
||||||
instance.insert_command(token::NAME, token::register, token::run);
|
instance.insert(token::NAME, token::register, token::command, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Core commands
|
// Core commands
|
||||||
instance.insert_command(core::help::NAME, core::help::register, core::help::run);
|
instance.insert(
|
||||||
instance.insert_command(
|
core::help::NAME,
|
||||||
|
core::help::register,
|
||||||
|
core::help::command,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
instance.insert(
|
||||||
core::version::NAME,
|
core::version::NAME,
|
||||||
core::version::register,
|
core::version::register,
|
||||||
core::version::run,
|
core::version::command,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
instance.insert_command(core::link::NAME, core::link::register, core::link::run);
|
instance.insert(
|
||||||
instance.insert_command(
|
core::link::NAME,
|
||||||
|
core::link::register,
|
||||||
|
core::link::command,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
instance.insert(
|
||||||
core::unlink::NAME,
|
core::unlink::NAME,
|
||||||
core::unlink::register,
|
core::unlink::register,
|
||||||
core::unlink::run,
|
core::unlink::command,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
instance.insert_command(
|
instance.insert(
|
||||||
core::rename::NAME,
|
core::rename::NAME,
|
||||||
core::rename::register,
|
core::rename::register,
|
||||||
core::rename::run,
|
core::rename::command,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Music commands
|
// Music commands
|
||||||
instance.insert_command(music::join::NAME, music::join::register, music::join::run);
|
instance.insert(
|
||||||
instance.insert_command(
|
music::join::NAME,
|
||||||
|
music::join::register,
|
||||||
|
music::join::command,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
instance.insert(
|
||||||
music::leave::NAME,
|
music::leave::NAME,
|
||||||
music::leave::register,
|
music::leave::register,
|
||||||
music::leave::run,
|
music::leave::command,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
instance.insert_command(
|
instance.insert(
|
||||||
music::playing::NAME,
|
music::playing::NAME,
|
||||||
music::playing::register,
|
music::playing::register,
|
||||||
music::playing::run,
|
music::playing::command,
|
||||||
|
Some(music::playing::component),
|
||||||
);
|
);
|
||||||
|
|
||||||
instance
|
instance
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_command(
|
pub fn insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: impl Into<String>,
|
name: impl Into<String>,
|
||||||
register: fn(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand,
|
register: fn(&mut CreateApplicationCommand) -> &mut CreateApplicationCommand,
|
||||||
executor: CommandExecutor,
|
command_executor: CommandExecutor,
|
||||||
|
component_executor: Option<ComponentExecutor>,
|
||||||
) {
|
) {
|
||||||
let name = name.into();
|
let name = name.into();
|
||||||
|
|
||||||
|
@ -152,12 +200,13 @@ impl CommandManager {
|
||||||
CommandInfo {
|
CommandInfo {
|
||||||
name,
|
name,
|
||||||
register,
|
register,
|
||||||
executor,
|
command_executor,
|
||||||
|
component_executor,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register_commands(&self, ctx: &Context) {
|
pub async fn register(&self, ctx: &Context) {
|
||||||
let cmds = &self.commands;
|
let cmds = &self.commands;
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
|
@ -196,11 +245,12 @@ impl CommandManager {
|
||||||
.expect("Failed to create global commands");
|
.expect("Failed to create global commands");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On slash command interaction
|
||||||
pub async fn execute_command(&self, ctx: &Context, interaction: ApplicationCommandInteraction) {
|
pub async fn execute_command(&self, ctx: &Context, interaction: ApplicationCommandInteraction) {
|
||||||
let command = self.commands.get(&interaction.data.name);
|
let command = self.commands.get(&interaction.data.name);
|
||||||
|
|
||||||
if let Some(command) = command {
|
if let Some(command) = command {
|
||||||
(command.executor)(ctx.clone(), interaction.clone()).await;
|
(command.command_executor)(ctx.clone(), interaction.clone()).await;
|
||||||
} else {
|
} else {
|
||||||
// Command does not exist
|
// Command does not exist
|
||||||
if let Err(why) = interaction
|
if let Err(why) = interaction
|
||||||
|
@ -219,6 +269,39 @@ impl CommandManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On message component interaction (e.g. button)
|
||||||
|
pub async fn execute_component(&self, ctx: &Context, interaction: MessageComponentInteraction) {
|
||||||
|
let command = match interaction.data.custom_id.split("::").next() {
|
||||||
|
Some(command) => command,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let command = self.commands.get(command);
|
||||||
|
|
||||||
|
if let Some(command) = command {
|
||||||
|
if let Some(executor) = command.component_executor {
|
||||||
|
executor(ctx.clone(), interaction.clone()).await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(why) = interaction
|
||||||
|
.create_interaction_response(&ctx.http, |response| {
|
||||||
|
response
|
||||||
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
.interaction_response_data(|message| {
|
||||||
|
message
|
||||||
|
.content("Woops, that interaction doesn't exist")
|
||||||
|
.ephemeral(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to respond to interaction: {}", why);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypeMapKey for CommandManager {
|
impl TypeMapKey for CommandManager {
|
||||||
|
|
|
@ -13,7 +13,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "join";
|
pub const NAME: &str = "join";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let guild = ctx
|
let guild = ctx
|
||||||
.cache
|
.cache
|
||||||
|
|
|
@ -12,7 +12,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "leave";
|
pub const NAME: &str = "leave";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let session_manager = data
|
let session_manager = data
|
||||||
|
|
|
@ -1,16 +1,25 @@
|
||||||
use librespot::core::spotify_id::SpotifyAudioType;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use librespot::core::spotify_id::{SpotifyAudioType, SpotifyId};
|
||||||
use log::error;
|
use log::error;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
builder::CreateApplicationCommand,
|
builder::{CreateApplicationCommand, CreateButton, CreateComponents, CreateEmbed},
|
||||||
model::prelude::interaction::{
|
model::{
|
||||||
application_command::ApplicationCommandInteraction, InteractionResponseType,
|
prelude::{
|
||||||
|
component::ButtonStyle,
|
||||||
|
interaction::{
|
||||||
|
application_command::ApplicationCommandInteraction,
|
||||||
|
message_component::MessageComponentInteraction, InteractionResponseType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user::User,
|
||||||
},
|
},
|
||||||
prelude::Context,
|
prelude::Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
bot::commands::{respond_message, CommandOutput},
|
bot::commands::{respond_component_message, respond_message, CommandOutput},
|
||||||
session::manager::SessionManager,
|
session::{manager::SessionManager, pbi::PlaybackInfo},
|
||||||
utils::{
|
utils::{
|
||||||
self,
|
self,
|
||||||
embed::{EmbedBuilder, Status},
|
embed::{EmbedBuilder, Status},
|
||||||
|
@ -19,7 +28,7 @@ use crate::{
|
||||||
|
|
||||||
pub const NAME: &str = "playing";
|
pub const NAME: &str = "playing";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let not_playing = async {
|
let not_playing = async {
|
||||||
respond_message(
|
respond_message(
|
||||||
|
@ -27,7 +36,7 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu
|
||||||
&command,
|
&command,
|
||||||
EmbedBuilder::new()
|
EmbedBuilder::new()
|
||||||
.title("Cannot get track info")
|
.title("Cannot get track info")
|
||||||
.icon_url("https://tabler-icons.io/static/tabler-icons/icons/ban.svg")
|
.icon_url("https://spoticord.com/forbidden.png")
|
||||||
.description("I'm currently not playing any music in this server")
|
.description("I'm currently not playing any music in this server")
|
||||||
.status(Status::Error)
|
.status(Status::Error)
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -82,6 +91,404 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get owner of session
|
||||||
|
let owner = match utils::discord::get_user(&ctx, owner).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => {
|
||||||
|
// This shouldn't happen
|
||||||
|
|
||||||
|
error!("Could not find user with ID: {owner}");
|
||||||
|
|
||||||
|
respond_message(
|
||||||
|
&ctx,
|
||||||
|
&command,
|
||||||
|
EmbedBuilder::new()
|
||||||
|
.title("[INTERNAL ERROR] Cannot get track info")
|
||||||
|
.description(format!(
|
||||||
|
"Could not find user with ID `{}`\nThis is an issue with the bot!",
|
||||||
|
owner
|
||||||
|
))
|
||||||
|
.status(Status::Error)
|
||||||
|
.build(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get metadata
|
||||||
|
let (title, description, audio_type, thumbnail) = get_metadata(spotify_id, &pbi);
|
||||||
|
|
||||||
|
if let Err(why) = command
|
||||||
|
.create_interaction_response(&ctx.http, |response| {
|
||||||
|
response
|
||||||
|
.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
.interaction_response_data(|message| {
|
||||||
|
message
|
||||||
|
.set_embed(build_playing_embed(
|
||||||
|
title,
|
||||||
|
audio_type,
|
||||||
|
spotify_id,
|
||||||
|
description,
|
||||||
|
owner,
|
||||||
|
thumbnail,
|
||||||
|
))
|
||||||
|
.components(|components| create_button(components, pbi.is_playing))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Error sending message: {why:?}");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn component(ctx: Context, mut interaction: MessageComponentInteraction) -> CommandOutput {
|
||||||
|
Box::pin(async move {
|
||||||
|
let error_message = |title: &'static str, description: &'static str| async {
|
||||||
|
respond_component_message(
|
||||||
|
&ctx,
|
||||||
|
&interaction,
|
||||||
|
EmbedBuilder::new()
|
||||||
|
.title(title.to_string())
|
||||||
|
.icon_url("https://spoticord.com/forbidden.png")
|
||||||
|
.description(description.to_string())
|
||||||
|
.status(Status::Error)
|
||||||
|
.build(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
|
||||||
|
let error_edit = |title: &'static str, description: &'static str| {
|
||||||
|
let mut interaction = interaction.clone();
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
interaction.defer(&ctx.http).await.ok();
|
||||||
|
|
||||||
|
if let Err(why) = interaction
|
||||||
|
.message
|
||||||
|
.edit(&ctx, |message| {
|
||||||
|
message.embed(|embed| {
|
||||||
|
embed
|
||||||
|
.description(description)
|
||||||
|
.author(|author| {
|
||||||
|
author
|
||||||
|
.name(title)
|
||||||
|
.icon_url("https://spoticord.com/forbidden.png")
|
||||||
|
})
|
||||||
|
.color(Status::Error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to update playing message: {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
let session_manager = data
|
||||||
|
.get::<SessionManager>()
|
||||||
|
.expect("to contain a value")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// Check if session still exists
|
||||||
|
let mut session = match session_manager
|
||||||
|
.get_session(interaction.guild_id.expect("to contain a value"))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Some(session) => session,
|
||||||
|
None => {
|
||||||
|
error_edit(
|
||||||
|
"Cannot perform action",
|
||||||
|
"I'm currently not playing any music in this server",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the session contains an owner
|
||||||
|
let owner = match session.owner().await {
|
||||||
|
Some(owner) => owner,
|
||||||
|
None => {
|
||||||
|
error_edit(
|
||||||
|
"Cannot change playback state",
|
||||||
|
"I'm currently not playing any music in this server",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get Playback Info from session
|
||||||
|
let pbi = match session.playback_info().await {
|
||||||
|
Some(pbi) => pbi,
|
||||||
|
None => {
|
||||||
|
error_edit(
|
||||||
|
"Cannot change playback state",
|
||||||
|
"I'm currently not playing any music in this server",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check if the user is the owner of the session
|
||||||
|
if owner != interaction.user.id {
|
||||||
|
error_message(
|
||||||
|
"Cannot change playback state",
|
||||||
|
"You must be the host to use the media buttons",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get owner of session
|
||||||
|
let owner = match utils::discord::get_user(&ctx, owner).await {
|
||||||
|
Some(user) => user,
|
||||||
|
None => {
|
||||||
|
// This shouldn't happen
|
||||||
|
|
||||||
|
error!("Could not find user with ID: {owner}");
|
||||||
|
|
||||||
|
respond_component_message(
|
||||||
|
&ctx,
|
||||||
|
&interaction,
|
||||||
|
EmbedBuilder::new()
|
||||||
|
.title("[INTERNAL ERROR] Cannot get track info")
|
||||||
|
.description(format!(
|
||||||
|
"Could not find user with ID `{}`\nThis is an issue with the bot!",
|
||||||
|
owner
|
||||||
|
))
|
||||||
|
.status(Status::Error)
|
||||||
|
.build(),
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the desired command to the session
|
||||||
|
let success = match interaction.data.custom_id.as_str() {
|
||||||
|
"playing::btn_pause_play" => {
|
||||||
|
if pbi.is_playing {
|
||||||
|
session.pause().await.is_ok()
|
||||||
|
} else {
|
||||||
|
session.resume().await.is_ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"playing::btn_previous_track" => session.previous().await.is_ok(),
|
||||||
|
|
||||||
|
"playing::btn_next_track" => session.next().await.is_ok(),
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
error!("Unknown custom_id: {}", interaction.data.custom_id);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !success {
|
||||||
|
error_message(
|
||||||
|
"Cannot change playback state",
|
||||||
|
"An error occurred while trying to change the playback state",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
interaction.defer(&ctx.http).await.ok();
|
||||||
|
tokio::time::sleep(Duration::from_millis(
|
||||||
|
if interaction.data.custom_id == "playing::btn_pause_play" {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
2500
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
update_embed(&mut interaction, &ctx, owner).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
|
command
|
||||||
|
.name(NAME)
|
||||||
|
.description("Display which song is currently being played")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_button(components: &mut CreateComponents, playing: bool) -> &mut CreateComponents {
|
||||||
|
let mut prev_btn = CreateButton::default();
|
||||||
|
prev_btn
|
||||||
|
.style(ButtonStyle::Primary)
|
||||||
|
.label("<<")
|
||||||
|
.custom_id("playing::btn_previous_track");
|
||||||
|
|
||||||
|
let mut toggle_btn = CreateButton::default();
|
||||||
|
toggle_btn
|
||||||
|
.style(ButtonStyle::Secondary)
|
||||||
|
.label(if playing { "Pause" } else { "Play" })
|
||||||
|
.custom_id("playing::btn_pause_play");
|
||||||
|
|
||||||
|
let mut next_btn = CreateButton::default();
|
||||||
|
next_btn
|
||||||
|
.style(ButtonStyle::Primary)
|
||||||
|
.label(">>")
|
||||||
|
.custom_id("playing::btn_next_track");
|
||||||
|
|
||||||
|
components.create_action_row(|ar| {
|
||||||
|
ar.add_button(prev_btn)
|
||||||
|
.add_button(toggle_btn)
|
||||||
|
.add_button(next_btn)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_embed(interaction: &mut MessageComponentInteraction, ctx: &Context, owner: User) {
|
||||||
|
let error_edit = |title: &'static str, description: &'static str| {
|
||||||
|
let mut interaction = interaction.clone();
|
||||||
|
let ctx = ctx.clone();
|
||||||
|
|
||||||
|
async move {
|
||||||
|
interaction.defer(&ctx.http).await.ok();
|
||||||
|
|
||||||
|
if let Err(why) = interaction
|
||||||
|
.message
|
||||||
|
.edit(&ctx, |message| {
|
||||||
|
message.embed(|embed| {
|
||||||
|
embed
|
||||||
|
.description(description)
|
||||||
|
.author(|author| {
|
||||||
|
author
|
||||||
|
.name(title)
|
||||||
|
.icon_url("https://spoticord.com/forbidden.png")
|
||||||
|
})
|
||||||
|
.color(Status::Error)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to update playing message: {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
let session_manager = data
|
||||||
|
.get::<SessionManager>()
|
||||||
|
.expect("to contain a value")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// Check if session still exists
|
||||||
|
let session = match session_manager
|
||||||
|
.get_session(interaction.guild_id.expect("to contain a value"))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Some(session) => session,
|
||||||
|
None => {
|
||||||
|
error_edit(
|
||||||
|
"Cannot perform action",
|
||||||
|
"I'm currently not playing any music in this server",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get Playback Info from session
|
||||||
|
let pbi = match session.playback_info().await {
|
||||||
|
Some(pbi) => pbi,
|
||||||
|
None => {
|
||||||
|
error_edit(
|
||||||
|
"Cannot change playback state",
|
||||||
|
"I'm currently not playing any music in this server",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let spotify_id = match pbi.spotify_id {
|
||||||
|
Some(spotify_id) => spotify_id,
|
||||||
|
None => {
|
||||||
|
error_edit(
|
||||||
|
"Cannot change playback state",
|
||||||
|
"I'm currently not playing any music in this server",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (title, description, audio_type, thumbnail) = get_metadata(spotify_id, &pbi);
|
||||||
|
|
||||||
|
if let Err(why) = interaction
|
||||||
|
.message
|
||||||
|
.edit(&ctx, |message| {
|
||||||
|
message
|
||||||
|
.set_embed(build_playing_embed(
|
||||||
|
title,
|
||||||
|
audio_type,
|
||||||
|
spotify_id,
|
||||||
|
description,
|
||||||
|
owner,
|
||||||
|
thumbnail,
|
||||||
|
))
|
||||||
|
.components(|components| create_button(components, pbi.is_playing));
|
||||||
|
|
||||||
|
message
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
error!("Failed to update playing message: {why}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_playing_embed(
|
||||||
|
title: impl Into<String>,
|
||||||
|
audio_type: impl Into<String>,
|
||||||
|
spotify_id: SpotifyId,
|
||||||
|
description: impl Into<String>,
|
||||||
|
owner: User,
|
||||||
|
thumbnail: impl Into<String>,
|
||||||
|
) -> CreateEmbed {
|
||||||
|
let mut embed = CreateEmbed::default();
|
||||||
|
embed
|
||||||
|
.author(|author| {
|
||||||
|
author
|
||||||
|
.name("Currently Playing")
|
||||||
|
.icon_url("https://spoticord.com/spotify-logo.png")
|
||||||
|
})
|
||||||
|
.title(title.into())
|
||||||
|
.url(format!(
|
||||||
|
"https://open.spotify.com/{}/{}",
|
||||||
|
audio_type.into(),
|
||||||
|
spotify_id
|
||||||
|
.to_base62()
|
||||||
|
.expect("to be able to convert to base62")
|
||||||
|
))
|
||||||
|
.description(description.into())
|
||||||
|
.footer(|footer| footer.text(&owner.name).icon_url(owner.face()))
|
||||||
|
.thumbnail(thumbnail.into())
|
||||||
|
.color(Status::Info);
|
||||||
|
|
||||||
|
embed
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_metadata(spotify_id: SpotifyId, pbi: &PlaybackInfo) -> (String, String, String, String) {
|
||||||
// Get audio type
|
// Get audio type
|
||||||
let audio_type = if spotify_id.audio_type == SpotifyAudioType::Track {
|
let audio_type = if spotify_id.audio_type == SpotifyAudioType::Track {
|
||||||
"track"
|
"track"
|
||||||
|
@ -92,8 +499,8 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu
|
||||||
// Create title
|
// Create title
|
||||||
let title = format!(
|
let title = format!(
|
||||||
"{} - {}",
|
"{} - {}",
|
||||||
pbi.get_artists().expect("to contain a value"),
|
pbi.get_artists().as_deref().unwrap_or("ID"),
|
||||||
pbi.get_name().expect("to contain a value")
|
pbi.get_name().as_deref().unwrap_or("ID")
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create description
|
// Create description
|
||||||
|
@ -119,72 +526,8 @@ pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutpu
|
||||||
utils::time_to_str(pbi.duration_ms / 1000)
|
utils::time_to_str(pbi.duration_ms / 1000)
|
||||||
));
|
));
|
||||||
|
|
||||||
// Get owner of session
|
|
||||||
let owner = match utils::discord::get_user(&ctx, owner).await {
|
|
||||||
Some(user) => user,
|
|
||||||
None => {
|
|
||||||
// This shouldn't happen
|
|
||||||
|
|
||||||
error!("Could not find user with id {}", owner);
|
|
||||||
|
|
||||||
respond_message(
|
|
||||||
&ctx,
|
|
||||||
&command,
|
|
||||||
EmbedBuilder::new()
|
|
||||||
.title("[INTERNAL ERROR] Cannot get track info")
|
|
||||||
.description(format!(
|
|
||||||
"Could not find user with id {}\nThis is an issue with the bot!",
|
|
||||||
owner
|
|
||||||
))
|
|
||||||
.status(Status::Error)
|
|
||||||
.build(),
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the thumbnail image
|
// Get the thumbnail image
|
||||||
let thumbnail = pbi.get_thumbnail_url().expect("to contain a value");
|
let thumbnail = pbi.get_thumbnail_url().expect("to contain a value");
|
||||||
|
|
||||||
if let Err(why) = command
|
(title, description, audio_type.to_string(), thumbnail)
|
||||||
.create_interaction_response(&ctx.http, |response| {
|
|
||||||
response
|
|
||||||
.kind(InteractionResponseType::ChannelMessageWithSource)
|
|
||||||
.interaction_response_data(|message| {
|
|
||||||
message.embed(|embed| {
|
|
||||||
embed
|
|
||||||
.author(|author| {
|
|
||||||
author
|
|
||||||
.name("Currently Playing")
|
|
||||||
.icon_url("https://spoticord.com/spotify-logo.png")
|
|
||||||
})
|
|
||||||
.title(title)
|
|
||||||
.url(format!(
|
|
||||||
"https://open.spotify.com/{}/{}",
|
|
||||||
audio_type,
|
|
||||||
spotify_id
|
|
||||||
.to_base62()
|
|
||||||
.expect("to be able to convert to base62")
|
|
||||||
))
|
|
||||||
.description(description)
|
|
||||||
.footer(|footer| footer.text(&owner.name).icon_url(owner.face()))
|
|
||||||
.thumbnail(&thumbnail)
|
|
||||||
.color(Status::Info as u64)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
error!("Error sending message: {:?}", why);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name(NAME)
|
|
||||||
.description("Display which song is currently being played")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use super::CommandOutput;
|
||||||
|
|
||||||
pub const NAME: &str = "ping";
|
pub const NAME: &str = "ping";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
info!("Pong!");
|
info!("Pong!");
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use super::CommandOutput;
|
||||||
|
|
||||||
pub const NAME: &str = "token";
|
pub const NAME: &str = "token";
|
||||||
|
|
||||||
pub fn run(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
pub fn command(ctx: Context, command: ApplicationCommandInteraction) -> CommandOutput {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let data = ctx.data.read().await;
|
let data = ctx.data.read().await;
|
||||||
let db = data.get::<Database>().expect("to contain a value");
|
let db = data.get::<Database>().expect("to contain a value");
|
||||||
|
|
|
@ -3,7 +3,13 @@
|
||||||
use log::*;
|
use log::*;
|
||||||
use serenity::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
model::prelude::{interaction::Interaction, Activity, GuildId, Ready},
|
model::prelude::{
|
||||||
|
interaction::{
|
||||||
|
application_command::ApplicationCommandInteraction,
|
||||||
|
message_component::MessageComponentInteraction, Interaction,
|
||||||
|
},
|
||||||
|
Activity, GuildId, Ready,
|
||||||
|
},
|
||||||
prelude::{Context, EventHandler},
|
prelude::{Context, EventHandler},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -11,6 +17,23 @@ use crate::consts::MOTD;
|
||||||
|
|
||||||
use super::commands::CommandManager;
|
use super::commands::CommandManager;
|
||||||
|
|
||||||
|
// If the GUILD_ID environment variable is set, only allow commands from that guild
|
||||||
|
macro_rules! enforce_guild {
|
||||||
|
($interaction:ident) => {
|
||||||
|
if let Ok(guild_id) = std::env::var("GUILD_ID") {
|
||||||
|
if let Ok(guild_id) = guild_id.parse::<u64>() {
|
||||||
|
let guild_id = GuildId(guild_id);
|
||||||
|
|
||||||
|
if let Some(interaction_guild_id) = $interaction.guild_id {
|
||||||
|
if guild_id != interaction_guild_id {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Handler struct with a command parameter, an array of dictionary which takes a string and function
|
// Handler struct with a command parameter, an array of dictionary which takes a string and function
|
||||||
pub struct Handler;
|
pub struct Handler;
|
||||||
|
|
||||||
|
@ -25,7 +48,7 @@ impl EventHandler for Handler {
|
||||||
|
|
||||||
// Set this to true only when a command is removed/updated/created
|
// Set this to true only when a command is removed/updated/created
|
||||||
if false {
|
if false {
|
||||||
command_manager.register_commands(&ctx).await;
|
command_manager.register(&ctx).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.set_activity(Activity::listening(MOTD)).await;
|
ctx.set_activity(Activity::listening(MOTD)).await;
|
||||||
|
@ -35,18 +58,17 @@ impl EventHandler for Handler {
|
||||||
|
|
||||||
// INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.)
|
// INTERACTION_CREATE event, emitted when the bot receives an interaction (slash command, button, etc.)
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
if let Interaction::ApplicationCommand(command) = interaction {
|
match interaction {
|
||||||
if let Ok(guild_id) = std::env::var("GUILD_ID") {
|
Interaction::ApplicationCommand(command) => self.handle_command(ctx, command).await,
|
||||||
if let Ok(guild_id) = guild_id.parse::<u64>() {
|
Interaction::MessageComponent(component) => self.handle_component(ctx, component).await,
|
||||||
let guild_id = GuildId(guild_id);
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(interaction_guild_id) = command.guild_id {
|
impl Handler {
|
||||||
if guild_id != interaction_guild_id {
|
async fn handle_command(&self, ctx: Context, command: ApplicationCommandInteraction) {
|
||||||
return;
|
enforce_guild!(command);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands must only be executed inside of guilds
|
// Commands must only be executed inside of guilds
|
||||||
|
|
||||||
|
@ -65,7 +87,6 @@ impl EventHandler for Handler {
|
||||||
error!("Failed to send run-in-guild-only error message: {}", why);
|
error!("Failed to send run-in-guild-only error message: {}", why);
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("interaction_create END2");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -82,5 +103,41 @@ impl EventHandler for Handler {
|
||||||
|
|
||||||
command_manager.execute_command(&ctx, command).await;
|
command_manager.execute_command(&ctx, command).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_component(&self, ctx: Context, component: MessageComponentInteraction) {
|
||||||
|
enforce_guild!(component);
|
||||||
|
|
||||||
|
// Components can only be interacted with inside of guilds
|
||||||
|
|
||||||
|
let guild_id = match component.guild_id {
|
||||||
|
Some(guild_id) => guild_id,
|
||||||
|
None => {
|
||||||
|
if let Err(why) = component
|
||||||
|
.create_interaction_response(&ctx.http, |response| {
|
||||||
|
response
|
||||||
|
.kind(serenity::model::prelude::interaction::InteractionResponseType::ChannelMessageWithSource)
|
||||||
|
.interaction_response_data(|message| {
|
||||||
|
message.content("You can only interact with components inside of a server")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await {
|
||||||
|
error!("Failed to send run-in-guild-only error message: {}", why);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"Received component interaction: command={} user={} guild={}",
|
||||||
|
component.data.custom_id,
|
||||||
|
component.user.id,
|
||||||
|
guild_id
|
||||||
|
);
|
||||||
|
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
let command_manager = data.get::<CommandManager>().expect("to contain a value");
|
||||||
|
|
||||||
|
command_manager.execute_component(&ctx, component).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,4 +31,16 @@ pub enum IpcPacket {
|
||||||
|
|
||||||
/// Sent when the user has switched their Spotify device away from Spoticord
|
/// Sent when the user has switched their Spotify device away from Spoticord
|
||||||
Stopped,
|
Stopped,
|
||||||
|
|
||||||
|
/// Request the player to advance to the next track
|
||||||
|
Next,
|
||||||
|
|
||||||
|
/// Request the player to go back to the previous track
|
||||||
|
Previous,
|
||||||
|
|
||||||
|
/// Request the player to pause playback
|
||||||
|
Pause,
|
||||||
|
|
||||||
|
/// Request the player to resume playback
|
||||||
|
Resume,
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ pub struct SpoticordPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpoticordPlayer {
|
impl SpoticordPlayer {
|
||||||
pub fn create(client: ipc::Client) -> Self {
|
pub fn new(client: ipc::Client) -> Self {
|
||||||
Self {
|
Self {
|
||||||
client,
|
client,
|
||||||
session: None,
|
session: None,
|
||||||
|
@ -223,6 +223,30 @@ impl SpoticordPlayer {
|
||||||
tokio::spawn(spirc_task);
|
tokio::spawn(spirc_task);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
if let Some(spirc) = &self.spirc {
|
||||||
|
spirc.next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn previous(&mut self) {
|
||||||
|
if let Some(spirc) = &self.spirc {
|
||||||
|
spirc.prev();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pause(&mut self) {
|
||||||
|
if let Some(spirc) = &self.spirc {
|
||||||
|
spirc.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resume(&mut self) {
|
||||||
|
if let Some(spirc) = &self.spirc {
|
||||||
|
spirc.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn stop(&mut self) {
|
pub fn stop(&mut self) {
|
||||||
if let Some(spirc) = self.spirc.take() {
|
if let Some(spirc) = self.spirc.take() {
|
||||||
spirc.shutdown();
|
spirc.shutdown();
|
||||||
|
@ -240,7 +264,7 @@ pub async fn main() {
|
||||||
let client = ipc::Client::connect(tx_name, rx_name).expect("Failed to connect to IPC");
|
let client = ipc::Client::connect(tx_name, rx_name).expect("Failed to connect to IPC");
|
||||||
|
|
||||||
// Create the player
|
// Create the player
|
||||||
let mut player = SpoticordPlayer::create(client.clone());
|
let mut player = SpoticordPlayer::new(client.clone());
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let message = match client.try_recv() {
|
let message = match client.try_recv() {
|
||||||
|
@ -274,6 +298,22 @@ pub async fn main() {
|
||||||
player.stop();
|
player.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IpcPacket::Next => {
|
||||||
|
player.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcPacket::Previous => {
|
||||||
|
player.previous();
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcPacket::Pause => {
|
||||||
|
player.pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcPacket::Resume => {
|
||||||
|
player.resume();
|
||||||
|
}
|
||||||
|
|
||||||
IpcPacket::Quit => {
|
IpcPacket::Quit => {
|
||||||
debug!("Received quit packet, exiting");
|
debug!("Received quit packet, exiting");
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
pub mod manager;
|
||||||
|
pub mod pbi;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
manager::{SessionCreateError, SessionManager},
|
manager::{SessionCreateError, SessionManager},
|
||||||
pbi::PlaybackInfo,
|
pbi::PlaybackInfo,
|
||||||
|
@ -30,9 +33,6 @@ use std::{
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
pub mod manager;
|
|
||||||
mod pbi;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>);
|
pub struct SpoticordSession(Arc<RwLock<InnerSpoticordSession>>);
|
||||||
|
|
||||||
|
@ -149,6 +149,42 @@ impl SpoticordSession {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Advance to the next track
|
||||||
|
pub async fn next(&mut self) -> Result<(), IpcError> {
|
||||||
|
if let Some(ref client) = self.0.read().await.client {
|
||||||
|
return client.send(IpcPacket::Next);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rewind to the previous track
|
||||||
|
pub async fn previous(&mut self) -> Result<(), IpcError> {
|
||||||
|
if let Some(ref client) = self.0.read().await.client {
|
||||||
|
return client.send(IpcPacket::Previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pause the current track
|
||||||
|
pub async fn pause(&mut self) -> Result<(), IpcError> {
|
||||||
|
if let Some(ref client) = self.0.read().await.client {
|
||||||
|
return client.send(IpcPacket::Pause);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume the current track
|
||||||
|
pub async fn resume(&mut self) -> Result<(), IpcError> {
|
||||||
|
if let Some(ref client) = self.0.read().await.client {
|
||||||
|
return client.send(IpcPacket::Resume);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn create_player(&mut self, ctx: &Context) -> Result<(), SessionCreateError> {
|
async fn create_player(&mut self, ctx: &Context) -> Result<(), SessionCreateError> {
|
||||||
let owner_id = match self.owner().await {
|
let owner_id = match self.owner().await {
|
||||||
Some(owner_id) => owner_id,
|
Some(owner_id) => owner_id,
|
||||||
|
|
|
@ -8,6 +8,12 @@ pub enum Status {
|
||||||
None = 0,
|
None = 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Status> for serenity::utils::Colour {
|
||||||
|
fn from(value: Status) -> Self {
|
||||||
|
Self(value as u32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct EmbedMessageOptions {
|
pub struct EmbedMessageOptions {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
|
Loading…
Reference in New Issue