From 7f89c7a4be35efff3ba0eeeb4ec33680915ba6b0 Mon Sep 17 00:00:00 2001 From: Benjamin Sherriff Date: Wed, 5 Jul 2023 19:23:26 -0400 Subject: [PATCH] InactiveHandler on track end --- README.md | 24 +++++++++++++---------- src/commands/audio/mod.rs | 17 ++++++++++++++--- src/commands/audio/play.rs | 39 +++++++++++++++++++++++++++++++++----- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3f4b127..2414fea 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ -drawing +
+ drawing +

Siren

+
-# Siren -A D&D Bot built for Discord. Includes music support and assistant DM tools. +Siren is a D&D Bot built for Discord, written in Rust. Features include: +- Play tracks from Youtube or locally hosted files +- Assistant DM tools to be defined later ## Running Visit the [Discord Developer Portal](https://discord.com/developers/applications) and create a new application. Click [here](https://discord.com/developers/docs/intro) for guides and more information. @@ -16,11 +20,12 @@ https://discord.com/api/oauth2/authorize?client_id=&permissions=40671 ``` - The CLIENT_ID can be found in the General Information tab on the Discord Developer Portal for your application, under `Application ID` -Start the application with `docker compose up -d` -- Requires [Docker](https://www.docker.com/) +1. Copy `.env.TEMPLATE` to `.env` and fill out the fields +2. Start the application with `docker compose up -d` + - Requires [Docker](https://www.docker.com/) ## Contributing -[Rust](https://www.rust-lang.org/) must be installed to run locally. +[Rust](https://www.rust-lang.org/) must be installed to run locally. See [serenity-rs/serenity](https://github.com/serenity-rs/serenity) for more information about Rust Discord API Library. Furthermore, the following packages must be installed for [serenity-rs/songbird](https://github.com/serenity-rs/songbird). View the repository for additional installation and setup information on other operating systems. ``` @@ -29,12 +34,11 @@ sudo apt install ffmpeg sudo apt apt install youtube-dl ``` -Requires [yt-dlp](https://github.com/yt-dlp/yt-dlp#installation) and potentially [yt-dlp FFmpeg Static Auto-Builds](https://github.com/yt-dlp/FFmpeg-Builds) +Potentially requires [yt-dlp](https://github.com/yt-dlp/yt-dlp#installation) and [yt-dlp FFmpeg Static Auto-Builds](https://github.com/yt-dlp/FFmpeg-Builds). -Copy `.env.TEMPLATE` to `.env` and fill out the fields -Run `cargo run` to begin the application +Begin the application with `cargo run` -The application can be tested from within a Docker container: +The application can also be tested from within a Docker container: ``` docker build -t siren . docker run --env-file .env -it --rm --name siren siren:latest diff --git a/src/commands/audio/mod.rs b/src/commands/audio/mod.rs index 7ea6d14..6d78d1d 100644 --- a/src/commands/audio/mod.rs +++ b/src/commands/audio/mod.rs @@ -6,7 +6,7 @@ use serenity::model::application::interaction::{InteractionResponseType, applica use serenity::model::prelude::{GuildId, ChannelId}; use serenity::model::user::User; use serenity::prelude::*; -use songbird::Call; +use songbird::{Call, Songbird}; use songbird::input::{Restartable, Input, Metadata, error::Error as SongbirdError}; pub mod pause; @@ -39,7 +39,7 @@ pub async fn join(ctx: &Context, guild_id_option: &Option, user: &User) }; debug!("<{}> Joining channel {}", guild_id.0, channel_id); - let manager = songbird::get(ctx).await.expect("Songbird Voice client placed in at initialization").clone(); + let manager = get_songbird(ctx).await; let (_handle_lock, success) = manager.join(guild_id.to_owned(), channel_id.to_owned()).await; match success { Ok(s) => Ok(s), @@ -63,7 +63,7 @@ pub async fn leave(ctx: &Context, guild_id_option: &Option) -> Result<( } }; - let manager = songbird::get(ctx).await.expect("Songbird Voice client placed in at initialization").clone(); + let manager = get_songbird(ctx).await; if manager.get(*guild_id).is_some() { debug!("<{}> Disconnecting from channel", guild_id.0); if let Err(e) = manager.remove(*guild_id).await { @@ -161,3 +161,14 @@ fn is_valid_url(url: &str) -> bool { Err(_) => return false } } + +/// Gets the Songbird voice client. +/// +/// # Arguments +/// - ctx - The context of the command. +/// +/// # Returns +/// Arc - The Songbird voice client. +pub async fn get_songbird(ctx: &Context) -> Arc { + songbird::get(ctx).await.expect("Songbird Voice client placed in at initialization") +} diff --git a/src/commands/audio/play.rs b/src/commands/audio/play.rs index ad144f7..0bef488 100644 --- a/src/commands/audio/play.rs +++ b/src/commands/audio/play.rs @@ -1,10 +1,11 @@ use log::{debug, warn, error}; -use serenity::prelude::*; +use serenity::{prelude::*, async_trait}; use serenity::builder::CreateApplicationCommand; use serenity::model::application::interaction::application_command::ApplicationCommandInteraction; +use songbird::EventHandler; -use crate::commands::audio::{join, leave, add_song}; +use crate::commands::audio::{join, leave, add_song, get_songbird}; use super::{create_response, edit_response}; @@ -58,15 +59,22 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { }; debug!("Play command executed with track: {:?}", track_url); - let manager = songbird::get(ctx).await.expect("Songbird Voice client placed in at initialization").clone(); + let manager = get_songbird(ctx).await; if let Some(handler_lock) = manager.get(guild_id) { - match add_song(handler_lock, &track_url, true).await { + let is_queue_empty = { + let call_handler = handler_lock.lock().await; + call_handler.queue().is_empty() + }; + match add_song(handler_lock.clone(), &track_url, is_queue_empty).await { Ok(added_song) => { let track_title = added_song.title.unwrap(); debug!("Added song: {}", track_title); if let Err(why) = edit_response(&ctx, &command, format!("Added song to queue: {}", track_title)).await { error!("Failed to edit response message: {}", why); } + let mut handler = handler_lock.lock().await; + handler.remove_all_global_events(); + handler.add_global_event(songbird::Event::Track(songbird::TrackEvent::End), InactiveHandler { guild_id, call: manager }) } Err(why) => { warn!("Failed to add song: {}", why); @@ -83,7 +91,7 @@ pub async fn run(ctx: &Context, command: &ApplicationCommandInteraction) { }, Err(err) => { warn!("{}", err); - if let Err(why) = edit_response(&ctx, &command, "Unable to join voice channel".to_string()).await { + if let Err(why) = edit_response(&ctx, &command, format!("{}", err)).await { error!("Failed to edit response message: {}", why); } } @@ -98,3 +106,24 @@ pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicatio .required(true) }) } + +struct InactiveHandler { + pub call: std::sync::Arc, + pub guild_id: serenity::model::id::GuildId +} + +#[async_trait] +impl EventHandler for InactiveHandler { + async fn act(&self, ctx: &songbird::events::EventContext<'_>) -> Option { + if let songbird::EventContext::Track(track_list) = ctx { + if track_list.is_empty() { + if let Some(call) = self.call.get(self.guild_id) { + debug!("Track list is empty; leaving voice channel"); + let mut handler = call.lock().await; + handler.leave().await.unwrap(); + } + } + } + None + } +}