diff --git a/examples/serenity/voice/src/main-skip.rs b/examples/serenity/voice/src/main-skip.rs deleted file mode 100644 index 50718d4..0000000 --- a/examples/serenity/voice/src/main-skip.rs +++ /dev/null @@ -1,417 +0,0 @@ -//! Requires the "client", "standard_framework", and "voice" features be enabled in your -//! Cargo.toml, like so: -//! -//! ```toml -//! [dependencies.serenity] -//! git = "https://github.com/serenity-rs/serenity.git" -//! features = ["client", "standard_framework", "voice"] -//! ``` -use std::env; - -// This trait adds the `register_songbird` and `register_songbird_with` methods -// to the client builder below, making it easy to install this voice client. -// The voice client can be retrieved in any command using `songbird::get(ctx).await`. -use songbird::SerenityInit; - -// Event related imports to detect track creation failures. -use songbird::events::{Event, EventContext, EventHandler as VoiceEventHandler, TrackEvent}; - -use std::time::Duration; - -// Import the `Context` to handle commands. -use serenity::client::Context; - -use serenity::{ - async_trait, - client::{Client, EventHandler}, - framework::{ - standard::{ - macros::{command, group}, - Args, CommandResult, - }, - StandardFramework, - }, - model::{channel::Message, gateway::Ready}, - prelude::GatewayIntents, - Result as SerenityResult, -}; - -struct Handler; - -#[async_trait] -impl EventHandler for Handler { - async fn ready(&self, _: Context, ready: Ready) { - println!("{} is connected!", ready.user.name); - } -} - -#[group] -#[commands(deafen, join, leave, mute, play, ping, undeafen, unmute)] -struct General; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - - // Configure the client with your Discord bot token in the environment. - let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment"); - - let mut framework = StandardFramework::new() - .group(&GENERAL_GROUP); - - framework.configure(|c| c.prefix("~")); - - let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; - - let mut client = Client::builder(&token, intents) - .event_handler(Handler) - .framework(framework) - .register_songbird() - .await - .expect("Err creating client"); - - tokio::spawn(async move { - let _ = client - .start() - .await - .map_err(|why| println!("Client ended: {:?}", why)); - }); - - tokio::signal::ctrl_c().await; - println!("Received Ctrl-C, shutting down."); -} - -#[command] -#[only_in(guilds)] -async fn deafen(ctx: &Context, msg: &Message) -> CommandResult { - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - let handler_lock = match manager.get(guild_id) { - Some(handler) => handler, - None => { - check_msg(msg.reply(ctx, "Not in a voice channel").await); - - return Ok(()); - }, - }; - - let mut handler = handler_lock.lock().await; - - if handler.is_deaf() { - check_msg(msg.channel_id.say(&ctx.http, "Already deafened").await); - } else { - if let Err(e) = handler.deafen(true).await { - check_msg( - msg.channel_id - .say(&ctx.http, format!("Failed: {:?}", e)) - .await, - ); - } - - check_msg(msg.channel_id.say(&ctx.http, "Deafened").await); - } - - Ok(()) -} - -#[command] -#[only_in(guilds)] -async fn join(ctx: &Context, msg: &Message) -> CommandResult { - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let channel_id = guild - .voice_states - .get(&msg.author.id) - .and_then(|voice_state| voice_state.channel_id); - - let connect_to = match channel_id { - Some(channel) => channel, - None => { - check_msg(msg.reply(ctx, "Not in a voice channel").await); - - return Ok(()); - }, - }; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - let (handler_lock, _success) = manager.join(guild_id, connect_to).await; - - // Attach an event handler to see notifications of all track errors. - let mut handler = handler_lock.lock().await; - handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier); - - Ok(()) -} - -struct TrackErrorNotifier; - -#[async_trait] -impl VoiceEventHandler for TrackErrorNotifier { - async fn act(&self, ctx: &EventContext<'_>) -> Option { - if let EventContext::Track(track_list) = ctx { - for (state, handle) in *track_list { - println!( - "Track {:?} encountered an error: {:?}", - handle.uuid(), - state.playing - ); - } - } - - None - } -} - -#[command] -#[only_in(guilds)] -async fn leave(ctx: &Context, msg: &Message) -> CommandResult { - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - let has_handler = manager.get(guild_id).is_some(); - - if has_handler { - if let Err(e) = manager.remove(guild_id).await { - check_msg( - msg.channel_id - .say(&ctx.http, format!("Failed: {:?}", e)) - .await, - ); - } - - check_msg(msg.channel_id.say(&ctx.http, "Left voice channel").await); - } else { - check_msg(msg.reply(ctx, "Not in a voice channel").await); - } - - Ok(()) -} - -#[command] -#[only_in(guilds)] -async fn mute(ctx: &Context, msg: &Message) -> CommandResult { - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - let handler_lock = match manager.get(guild_id) { - Some(handler) => handler, - None => { - check_msg(msg.reply(ctx, "Not in a voice channel").await); - - return Ok(()); - }, - }; - - let mut handler = handler_lock.lock().await; - - if handler.is_mute() { - check_msg(msg.channel_id.say(&ctx.http, "Already muted").await); - } else { - if let Err(e) = handler.mute(true).await { - check_msg( - msg.channel_id - .say(&ctx.http, format!("Failed: {:?}", e)) - .await, - ); - } - - check_msg(msg.channel_id.say(&ctx.http, "Now muted").await); - } - - Ok(()) -} - -#[command] -async fn ping(ctx: &Context, msg: &Message) -> CommandResult { - check_msg(msg.channel_id.say(&ctx.http, "Pong!").await); - Ok(()) -} - -#[command] -#[only_in(guilds)] -async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { - let url = match args.single::() { - Ok(url) => url, - Err(_) => { - check_msg( - msg.channel_id - .say(&ctx.http, "Must provide a URL to a video or audio") - .await, - ); - - return Ok(()); - }, - }; - - let time = match args.single::() { - Ok(t) => t, - Err(_) => { - check_msg( - msg.channel_id - .say(&ctx.http, "Must provide a valid f64 timestamp to jump to.") - .await, - ); - - return Ok(()); - }, - }; - - if !url.starts_with("http") { - check_msg( - msg.channel_id - .say(&ctx.http, "Must provide a valid URL") - .await, - ); - - return Ok(()); - } - - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Some(handler_lock) = manager.get(guild_id) { - let mut handler = handler_lock.lock().await; - - let jar = reqwest::cookie::Jar::default(); - jar.add_cookie_str("CONSENT=YES+; Path=/; Domain=youtube.com; Secure; Expires=Fri, 01 Jan 2038 00:00:00 GMT;", &"https://youtube.com".parse().unwrap()); - - let src = songbird::input::YoutubeDl::new_ytdl_like("yt-dlp", reqwest::Client::builder().cookie_provider(std::sync::Arc::new(jar)).build().unwrap(), url); - - let h = handler.play_input(src.into()); - - h.add_event( - Event::Delayed(Duration::from_secs(10)), - SkipHandler { - skip_to: Duration::from_secs_f64(time), - }, - ); - - check_msg(msg.channel_id.say(&ctx.http, "Playing song").await); - } else { - check_msg( - msg.channel_id - .say(&ctx.http, "Not in a voice channel to play in") - .await, - ); - } - - Ok(()) -} - -struct SkipHandler { - skip_to: Duration, -} - -#[async_trait] -impl VoiceEventHandler for SkipHandler { - async fn act(&self, ctx: &EventContext<'_>) -> Option { - if let EventContext::Track(&[(state, handle)]) = ctx { - println!("Current position is {:?}", state.position); - println!("Seeking to {:?}", self.skip_to); - let resp = handle.seek_time(self.skip_to); - println!("Seek response {:?}", resp); - return None; - } - - println!("This wasn't supposed to happen."); - None - } -} - -#[command] -#[only_in(guilds)] -async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Some(handler_lock) = manager.get(guild_id) { - let mut handler = handler_lock.lock().await; - if let Err(e) = handler.deafen(false).await { - check_msg( - msg.channel_id - .say(&ctx.http, format!("Failed: {:?}", e)) - .await, - ); - } - - check_msg(msg.channel_id.say(&ctx.http, "Undeafened").await); - } else { - check_msg( - msg.channel_id - .say(&ctx.http, "Not in a voice channel to undeafen in") - .await, - ); - } - - Ok(()) -} - -#[command] -#[only_in(guilds)] -async fn unmute(ctx: &Context, msg: &Message) -> CommandResult { - let guild = msg.guild(&ctx.cache).unwrap(); - let guild_id = guild.id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Some(handler_lock) = manager.get(guild_id) { - let mut handler = handler_lock.lock().await; - if let Err(e) = handler.mute(false).await { - check_msg( - msg.channel_id - .say(&ctx.http, format!("Failed: {:?}", e)) - .await, - ); - } - - check_msg(msg.channel_id.say(&ctx.http, "Unmuted").await); - } else { - check_msg( - msg.channel_id - .say(&ctx.http, "Not in a voice channel to unmute in") - .await, - ); - } - - Ok(()) -} - -/// Checks that a message successfully sent; if not, then logs why to stdout. -fn check_msg(result: SerenityResult) { - if let Err(why) = result { - println!("Error sending message: {:?}", why); - } -} diff --git a/examples/serenity/voice/src/main.rs b/examples/serenity/voice/src/main.rs index 256f7ab..e66ed21 100644 --- a/examples/serenity/voice/src/main.rs +++ b/examples/serenity/voice/src/main.rs @@ -161,11 +161,11 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { .expect("Songbird Voice client placed in at initialisation.") .clone(); - let (handler_lock, _success) = manager.join(guild_id, connect_to).await; - - // Attach an event handler to see notifications of all track errors. - let mut handler = handler_lock.lock().await; - handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier); + if let Ok(handler_lock) = manager.join(guild_id, connect_to).await { + // Attach an event handler to see notifications of all track errors. + let mut handler = handler_lock.lock().await; + handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier); + } Ok(()) } diff --git a/examples/serenity/voice_cached_audio/src/main.rs b/examples/serenity/voice_cached_audio/src/main.rs index 2b4e217..092b1f9 100644 --- a/examples/serenity/voice_cached_audio/src/main.rs +++ b/examples/serenity/voice_cached_audio/src/main.rs @@ -233,11 +233,9 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { .expect("Songbird Voice client placed in at initialisation.") .clone(); - let (handler_lock, success_reader) = manager.join(guild_id, connect_to).await; + if let Ok(handler_lock) = manager.join(guild_id, connect_to).await { + let call_lock_for_evt = Arc::downgrade(&handler_lock); - let call_lock_for_evt = Arc::downgrade(&handler_lock); - - if let Ok(_reader) = success_reader { let mut handler = handler_lock.lock().await; check_msg( msg.channel_id diff --git a/examples/serenity/voice_events_queue/src/main.rs b/examples/serenity/voice_events_queue/src/main.rs index 076fdb0..b8ccb14 100644 --- a/examples/serenity/voice_events_queue/src/main.rs +++ b/examples/serenity/voice_events_queue/src/main.rs @@ -174,9 +174,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { .expect("Songbird Voice client placed in at initialisation.") .clone(); - let (handle_lock, success) = manager.join(guild_id, connect_to).await; - - if let Ok(_channel) = success { + if let Ok(handle_lock) = manager.join(guild_id, connect_to).await { check_msg( msg.channel_id .say(&ctx.http, &format!("Joined {}", connect_to.mention())) diff --git a/examples/serenity/voice_receive/src/main.rs b/examples/serenity/voice_receive/src/main.rs index 6ff4850..0c4d03b 100644 --- a/examples/serenity/voice_receive/src/main.rs +++ b/examples/serenity/voice_receive/src/main.rs @@ -253,9 +253,7 @@ async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { .expect("Songbird Voice client placed in at initialisation.") .clone(); - let (handler_lock, conn_result) = manager.join(guild_id, connect_to).await; - - if let Ok(_) = conn_result { + if let Ok(handler_lock) = manager.join(guild_id, connect_to).await { // NOTE: this skips listening for the actual connection result. let mut handler = handler_lock.lock().await; diff --git a/examples/twilight/src/main.rs b/examples/twilight/src/main.rs index 308932c..067aea0 100644 --- a/examples/twilight/src/main.rs +++ b/examples/twilight/src/main.rs @@ -131,10 +131,8 @@ async fn join(msg: Message, state: State) -> Result<(), Box format!("Joined <#{}>!", channel_id), + let content = match state.songbird.join(guild_id, channel_id).await { + Ok(_handle) => format!("Joined <#{}>!", channel_id), Err(e) => format!("Failed to join <#{}>! Why: {:?}", channel_id, e), }; diff --git a/src/input/adapters/cached/error.rs b/src/input/adapters/cached/error.rs index ac9ddc5..6c48cc0 100644 --- a/src/input/adapters/cached/error.rs +++ b/src/input/adapters/cached/error.rs @@ -91,8 +91,7 @@ impl Display for CodecCacheError { Self::Parse(p) => f.write_fmt(format_args!("failed to parse audio format: {p}")), Self::Opus(o) => f.write_fmt(format_args!("failed to create Opus encoder: {o}")), Self::MetadataEncoding(m) => f.write_fmt(format_args!( - "failed to convert track metadata to JSON: {}", - m + "failed to convert track metadata to JSON: {m}" )), Self::MetadataTooLarge => f.write_str("track metadata was too large, >= 32kiB"), Self::CreatePanicked => f.write_str("sync thread panicked while creating stream"), diff --git a/src/manager.rs b/src/manager.rs index 34782af..8cd31b2 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -216,11 +216,13 @@ impl Songbird { /// /// Twilight users should read the caveats mentioned in [`process`]. /// + /// NOTE: an `Err(..)` value will still create a [`Call`] accessible via [`get`]. + /// /// [`Call`]: Call /// [`get`]: Songbird::get /// [`process`]: #method.process #[inline] - pub async fn join(&self, guild_id: G, channel_id: C) -> (Arc>, JoinResult<()>) + pub async fn join(&self, guild_id: G, channel_id: C) -> JoinResult>> where C: Into, G: Into, @@ -233,7 +235,7 @@ impl Songbird { &self, guild_id: GuildId, channel_id: ChannelId, - ) -> (Arc>, JoinResult<()>) { + ) -> JoinResult>> { let call = self.get_or_insert(guild_id); let stage_1 = { @@ -241,12 +243,10 @@ impl Songbird { handler.join(channel_id).await }; - let result = match stage_1 { - Ok(chan) => chan.await, + match stage_1 { + Ok(chan) => chan.await.map(|_| call), Err(e) => Err(e), - }; - - (call, result) + } } /// Partially connects to a target by retrieving its relevant [`Call`] and @@ -255,13 +255,16 @@ impl Songbird { /// This method returns the handle and the connection info needed for other libraries /// or drivers, such as lavalink, and does not actually start or run a voice call. /// + /// NOTE: an `Err(..)` value will still create a [`Call`] accessible via [`get`]. + /// /// [`Call`]: Call + /// [`get`]: Songbird::get #[inline] pub async fn join_gateway( &self, guild_id: G, channel_id: C, - ) -> (Arc>, JoinResult) + ) -> JoinResult<(ConnectionInfo, Arc>)> where C: Into, G: Into, @@ -273,7 +276,7 @@ impl Songbird { &self, guild_id: GuildId, channel_id: ChannelId, - ) -> (Arc>, JoinResult) { + ) -> JoinResult<(ConnectionInfo, Arc>)> { let call = self.get_or_insert(guild_id); let stage_1 = { @@ -281,12 +284,13 @@ impl Songbird { handler.join_gateway(channel_id).await }; - let result = match stage_1 { - Ok(chan) => chan.await.map_err(|_| JoinError::Dropped), + match stage_1 { + Ok(chan) => chan + .await + .map_err(|_| JoinError::Dropped) + .map(|info| (info, call)), Err(e) => Err(e), - }; - - (call, result) + } } /// Retrieves the [handler][`Call`] for the given target and leaves the