diff --git a/examples/Cargo.toml b/examples/Cargo.toml index f914ac2fa..e449fa276 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -10,10 +10,22 @@ resolver = "2" [workspace.dependencies] reqwest = "0.11" -serenity = { features = ["cache", "framework", "standard_framework", "voice", "http", "rustls_backend"], git = "https://github.com/serenity-rs/serenity", branch = "next" } +serenity = { features = [ + "cache", + "framework", + "standard_framework", + "voice", + "http", + "rustls_backend", +], git = "https://github.com/serenity-rs/serenity", branch = "next" } songbird = { path = "../", version = "0.4" } symphonia = { features = ["aac", "mp3", "isomp4", "alac"], version = "0.5.2" } -tokio = { features = ["macros", "rt-multi-thread", "signal", "sync"], version = "1" } +tokio = { features = [ + "macros", + "rt-multi-thread", + "signal", + "sync", +], version = "1" } tracing = "0.1" tracing-subscriber = "0.2" tracing-futures = "0.2" diff --git a/examples/serenity/voice/src/main.rs b/examples/serenity/voice/src/main.rs index 8059b7f2c..4b79b0a88 100644 --- a/examples/serenity/voice/src/main.rs +++ b/examples/serenity/voice/src/main.rs @@ -7,11 +7,7 @@ //! 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; +use std::sync::Arc; // Event related imports to detect track creation failures. use songbird::events::{Event, EventContext, EventHandler as VoiceEventHandler, TrackEvent}; @@ -38,14 +34,13 @@ use serenity::{ StandardFramework, }, model::{channel::Message, gateway::Ready}, - prelude::{GatewayIntents, TypeMapKey}, + prelude::GatewayIntents, Result as SerenityResult, }; -struct HttpKey; - -impl TypeMapKey for HttpKey { - type Value = HttpClient; +struct UserData { + http: HttpClient, + songbird: Arc, } struct Handler; @@ -73,16 +68,20 @@ async fn main() { let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; + // We create a global HTTP client here to make use of in + // `~play`. If we wanted, we could supply cookies and auth + // details ahead of time. + let manager = songbird::Songbird::serenity(); + let user_data = UserData { + http: HttpClient::new(), + songbird: Arc::clone(&manager), + }; + let mut client = Client::builder(&token, intents) + .voice_manager::(manager) + .data(Arc::new(user_data) as _) .event_handler(Handler) .framework(framework) - .register_songbird() - // We insert our own HTTP client here to make use of in - // `~play`. If we wanted, we could supply cookies and auth - // details ahead of time. - // - // Generally, we don't want to make a new Client for every request! - .type_map_insert::(HttpClient::new()) .await .expect("Err creating client"); @@ -101,11 +100,7 @@ async fn main() { #[only_in(guilds)] async fn deafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let handler_lock = match manager.get(guild_id) { Some(handler) => handler, @@ -157,11 +152,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { }, }; - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - + let manager = &ctx.data::().songbird; 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; @@ -195,10 +186,7 @@ impl VoiceEventHandler for TrackErrorNotifier { async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let has_handler = manager.get(guild_id).is_some(); if has_handler { @@ -222,11 +210,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn mute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let handler_lock = match manager.get(guild_id) { Some(handler) => handler, @@ -282,27 +266,16 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let http_client = { - let data = ctx.data.read().await; - data.get::() - .cloned() - .expect("Guaranteed to exist in the typemap.") - }; - - 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 data = ctx.data::(); + if let Some(handler_lock) = data.songbird.get(guild_id) { let mut handler = handler_lock.lock().await; - let mut src = if do_search { - YoutubeDl::new_search(http_client, url) + let src = if do_search { + YoutubeDl::new_search(data.http.clone(), url) } else { - YoutubeDl::new(http_client, url) + YoutubeDl::new(data.http.clone(), url) }; - let _ = handler.play_input(src.clone().into()); + let _ = handler.play_input(src.into()); check_msg(msg.channel_id.say(&ctx.http, "Playing song").await); } else { @@ -320,11 +293,7 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { #[only_in(guilds)] async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; @@ -352,11 +321,7 @@ async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn unmute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; diff --git a/examples/serenity/voice_cached_audio/Cargo.toml b/examples/serenity/voice_cached_audio/Cargo.toml index 67a8117f5..433ccec52 100644 --- a/examples/serenity/voice_cached_audio/Cargo.toml +++ b/examples/serenity/voice_cached_audio/Cargo.toml @@ -13,3 +13,4 @@ tokio = { workspace = true } tracing = { workspace = true } tracing-subscriber = { workspace = true } tracing-futures = { workspace = true } +dashmap = "5.5.3" diff --git a/examples/serenity/voice_cached_audio/src/main.rs b/examples/serenity/voice_cached_audio/src/main.rs index dec21ccef..386956a32 100644 --- a/examples/serenity/voice_cached_audio/src/main.rs +++ b/examples/serenity/voice_cached_audio/src/main.rs @@ -10,8 +10,6 @@ //! features = ["cache", "framework", "standard_framework", "voice"] //! ``` use std::{ - collections::HashMap, - convert::TryInto, env, sync::{Arc, Weak}, }; @@ -44,13 +42,9 @@ use songbird::{ Event, EventContext, EventHandler as VoiceEventHandler, - SerenityInit, TrackEvent, }; -// This imports `typemap`'s `Key` as `TypeMapKey`. -use serenity::prelude::*; - struct Handler; #[async_trait] @@ -70,24 +64,70 @@ impl From<&CachedSound> for Input { use CachedSound::*; match obj { Compressed(c) => c.new_handle().into(), - Uncompressed(u) => u - .new_handle() - .try_into() - .expect("Failed to create decoder for Memory source."), + Uncompressed(u) => u.new_handle().into(), } } } -struct SoundStore; - -impl TypeMapKey for SoundStore { - type Value = Arc>>; +struct UserData { + songbird: Arc, + sound_store: dashmap::DashMap, } #[group] #[commands(deafen, join, leave, mute, ting, undeafen, unmute)] struct General; +async fn setup_cached_audio() -> dashmap::DashMap { + // Loading the audio ahead of time. + let audio_map = dashmap::DashMap::new(); + + // Creation of an in-memory source. + // + // This is a small sound effect, so storing the whole thing is relatively cheap. + // + // `spawn_loader` creates a new thread which works to copy all the audio into memory + // ahead of time. We do this in both cases to ensure optimal performance for the audio + // core. + let ting_src = Memory::new(File::new("../../../resources/ting.wav").into()) + .await + .expect("These parameters are well-defined."); + let _ = ting_src.raw.spawn_loader(); + audio_map.insert("ting".into(), CachedSound::Uncompressed(ting_src)); + + // Another short sting, to show where each loop occurs. + let loop_src = Memory::new(File::new("../../../resources/loop.wav").into()) + .await + .expect("These parameters are well-defined."); + let _ = loop_src.raw.spawn_loader(); + audio_map.insert("loop".into(), CachedSound::Uncompressed(loop_src)); + + // Creation of a compressed source. + // + // This is a full song, making this a much less memory-heavy choice. + // + // Music by Cloudkicker, used under CC BY-SC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/). + let song_src = Compressed::new( + File::new("../../../resources/Cloudkicker - 2011 07.mp3").into(), + Bitrate::BitsPerSecond(128_000), + ) + .await + .expect("These parameters are well-defined."); + let _ = song_src.raw.spawn_loader(); + + // Compressed sources are internally stored as DCA1 format files. + // Because `Compressed` implements `std::io::Read`, we can save these + // to disk and use them again later if we want! + let mut creator = song_src.new_handle(); + std::thread::spawn(move || { + let mut out_file = std::fs::File::create("ckick-dca1.dca").unwrap(); + std::io::copy(&mut creator, &mut out_file).expect("Error writing out song!"); + }); + + audio_map.insert("song".into(), CachedSound::Compressed(song_src)); + audio_map +} + #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); @@ -100,69 +140,20 @@ async fn main() { let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; + let manager = songbird::Songbird::serenity(); + let user_data = UserData { + sound_store: setup_cached_audio().await, + songbird: Arc::clone(&manager), + }; + let mut client = Client::builder(&token, intents) + .voice_manager::(manager) + .data(Arc::new(user_data) as _) .event_handler(Handler) .framework(framework) - .register_songbird() .await .expect("Err creating client"); - // Obtain a lock to the data owned by the client, and insert the client's - // voice manager into it. This allows the voice manager to be accessible by - // event handlers and framework commands. - { - let mut data = client.data.write().await; - - // Loading the audio ahead of time. - let mut audio_map = HashMap::new(); - - // Creation of an in-memory source. - // - // This is a small sound effect, so storing the whole thing is relatively cheap. - // - // `spawn_loader` creates a new thread which works to copy all the audio into memory - // ahead of time. We do this in both cases to ensure optimal performance for the audio - // core. - let ting_src = Memory::new(File::new("../../../resources/ting.wav").into()) - .await - .expect("These parameters are well-defined."); - let _ = ting_src.raw.spawn_loader(); - audio_map.insert("ting".into(), CachedSound::Uncompressed(ting_src)); - - // Another short sting, to show where each loop occurs. - let loop_src = Memory::new(File::new("../../../resources/loop.wav").into()) - .await - .expect("These parameters are well-defined."); - let _ = loop_src.raw.spawn_loader(); - audio_map.insert("loop".into(), CachedSound::Uncompressed(loop_src)); - - // Creation of a compressed source. - // - // This is a full song, making this a much less memory-heavy choice. - // - // Music by Cloudkicker, used under CC BY-SC-SA 3.0 (https://creativecommons.org/licenses/by-nc-sa/3.0/). - let song_src = Compressed::new( - File::new("../../../resources/Cloudkicker - 2011 07.mp3").into(), - Bitrate::BitsPerSecond(128_000), - ) - .await - .expect("These parameters are well-defined."); - let _ = song_src.raw.spawn_loader(); - - // Compressed sources are internally stored as DCA1 format files. - // Because `Compressed` implements `std::io::Read`, we can save these - // to disk and use them again later if we want! - let mut creator = song_src.new_handle(); - std::thread::spawn(move || { - let mut out_file = std::fs::File::create("ckick-dca1.dca").unwrap(); - std::io::copy(&mut creator, &mut out_file).expect("Error writing out song!"); - }); - - audio_map.insert("song".into(), CachedSound::Compressed(song_src)); - - data.insert::(Arc::new(Mutex::new(audio_map))); - } - let _ = client .start() .await @@ -173,11 +164,7 @@ async fn main() { #[only_in(guilds)] async fn deafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let handler_lock = match manager.get(guild_id) { Some(handler) => handler, @@ -229,12 +216,8 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { }, }; - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Ok(handler_lock) = manager.join(guild_id, connect_to).await { + let data = ctx.data::(); + if let Ok(handler_lock) = data.songbird.join(guild_id, connect_to).await { let call_lock_for_evt = Arc::downgrade(&handler_lock); let mut handler = handler_lock.lock().await; @@ -244,20 +227,13 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { .await, ); - let sources_lock = ctx - .data - .read() - .await - .get::() - .cloned() - .expect("Sound cache was installed at startup."); - let sources_lock_for_evt = sources_lock.clone(); - let sources = sources_lock.lock().await; - let source = sources + let source = data + .sound_store .get("song") + .map(|s| s.value().into()) .expect("Handle placed into cache at startup."); - let song = handler.play_input(source.into()); + let song = handler.play_input(source); let _ = song.set_volume(1.0); let _ = song.enable_loop(); @@ -266,7 +242,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { Event::Track(TrackEvent::Loop), LoopPlaySound { call_lock: call_lock_for_evt, - sources: sources_lock_for_evt, + data, }, ); } else { @@ -282,7 +258,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { struct LoopPlaySound { call_lock: Weak>, - sources: Arc>>, + data: Arc, } #[async_trait] @@ -290,11 +266,11 @@ impl VoiceEventHandler for LoopPlaySound { async fn act(&self, _ctx: &EventContext<'_>) -> Option { if let Some(call_lock) = self.call_lock.upgrade() { let src = { - let sources = self.sources.lock().await; - sources + self.data + .sound_store .get("loop") + .map(|c| c.value().into()) .expect("Handle placed into cache at startup.") - .into() }; let mut handler = call_lock.lock().await; @@ -311,10 +287,7 @@ impl VoiceEventHandler for LoopPlaySound { async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let has_handler = manager.get(guild_id).is_some(); if has_handler { @@ -338,11 +311,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn mute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let handler_lock = match manager.get(guild_id) { Some(handler) => handler, @@ -376,28 +345,18 @@ async fn mute(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn ting(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); + let data = ctx.data::(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Some(handler_lock) = manager.get(guild_id) { + if let Some(handler_lock) = data.songbird.get(guild_id) { let mut handler = handler_lock.lock().await; - let sources_lock = ctx - .data - .read() - .await - .get::() - .cloned() - .expect("Sound cache was installed at startup."); - let sources = sources_lock.lock().await; - let source = sources + let source = data + .sound_store .get("ting") + .map(|c| c.value().into()) .expect("Handle placed into cache at startup."); - let _sound = handler.play_input(source.into()); + let _sound = handler.play_input(source); check_msg(msg.channel_id.say(&ctx.http, "Ting!").await); } else { @@ -416,11 +375,7 @@ async fn ting(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; @@ -448,10 +403,7 @@ async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn unmute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; diff --git a/examples/serenity/voice_events_queue/src/main.rs b/examples/serenity/voice_events_queue/src/main.rs index 6f51977d4..5004fe80c 100644 --- a/examples/serenity/voice_events_queue/src/main.rs +++ b/examples/serenity/voice_events_queue/src/main.rs @@ -34,7 +34,7 @@ use serenity::{ }, http::Http, model::{channel::Message, gateway::Ready, prelude::ChannelId}, - prelude::{GatewayIntents, Mentionable, TypeMapKey}, + prelude::{GatewayIntents, Mentionable}, Result as SerenityResult, }; @@ -43,14 +43,12 @@ use songbird::{ Event, EventContext, EventHandler as VoiceEventHandler, - SerenityInit, TrackEvent, }; -struct HttpKey; - -impl TypeMapKey for HttpKey { - type Value = HttpClient; +struct UserData { + songbird: Arc, + http: HttpClient, } struct Handler; @@ -80,11 +78,17 @@ async fn main() { let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; + let manager = songbird::Songbird::serenity(); + let data = UserData { + http: HttpClient::new(), + songbird: Arc::clone(&manager), + }; + let mut client = Client::builder(&token, intents) + .voice_manager::(manager) + .data(Arc::new(data) as _) .event_handler(Handler) .framework(framework) - .register_songbird() - .type_map_insert::(HttpClient::new()) .await .expect("Err creating client"); @@ -104,21 +108,10 @@ async fn main() { println!("Received Ctrl-C, shutting down."); } -async fn get_http_client(ctx: &Context) -> HttpClient { - let data = ctx.data.read().await; - data.get::() - .cloned() - .expect("Guaranteed to exist in the typemap.") -} - #[command] async fn deafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild(&ctx.cache).unwrap().id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let handler_lock = match manager.get(guild_id) { Some(handler) => handler, @@ -170,11 +163,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { }, }; - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - + let manager = &ctx.data::().songbird; if let Ok(handle_lock) = manager.join(guild_id, connect_to).await { check_msg( msg.channel_id @@ -268,10 +257,7 @@ impl VoiceEventHandler for ChannelDurationNotifier { async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild(&ctx.cache).unwrap().id; - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let has_handler = manager.get(guild_id).is_some(); if has_handler { @@ -295,11 +281,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn mute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild(&ctx.cache).unwrap().id; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let handler_lock = match manager.get(guild_id) { Some(handler) => handler, @@ -363,18 +345,12 @@ async fn play_fade(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul } let guild_id = msg.guild_id.unwrap(); + let data = ctx.data::(); - let http_client = get_http_client(ctx).await; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Some(handler_lock) = manager.get(guild_id) { + if let Some(handler_lock) = data.songbird.get(guild_id) { let mut handler = handler_lock.lock().await; - let src = YoutubeDl::new(http_client, url); + let src = YoutubeDl::new(data.http.clone(), url); // This handler object will allow you to, as needed, // control the audio track via events and further commands. @@ -486,20 +462,14 @@ async fn queue(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { } let guild_id = msg.guild_id.unwrap(); + let data = ctx.data::(); - let http_client = get_http_client(ctx).await; - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); - - if let Some(handler_lock) = manager.get(guild_id) { + if let Some(handler_lock) = data.songbird.get(guild_id) { let mut handler = handler_lock.lock().await; // Here, we use lazy restartable sources to make sure that we don't pay // for decoding, playback on tracks which aren't actually live yet. - let src = YoutubeDl::new(http_client, url); + let src = YoutubeDl::new(data.http.clone(), url); handler.enqueue_input(src.into()).await; @@ -526,11 +496,7 @@ async fn queue(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { #[only_in(guilds)] async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; @@ -560,11 +526,7 @@ async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { #[only_in(guilds)] async fn stop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; @@ -587,11 +549,7 @@ async fn stop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { #[only_in(guilds)] async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; @@ -619,10 +577,7 @@ async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { #[only_in(guilds)] async fn unmute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; diff --git a/examples/serenity/voice_receive/src/main.rs b/examples/serenity/voice_receive/src/main.rs index f3fa04eeb..d81725c5f 100644 --- a/examples/serenity/voice_receive/src/main.rs +++ b/examples/serenity/voice_receive/src/main.rs @@ -45,9 +45,13 @@ use songbird::{ Event, EventContext, EventHandler as VoiceEventHandler, - SerenityInit, + Songbird, }; +struct UserData { + songbird: Arc, +} + struct Handler; #[async_trait] @@ -218,11 +222,17 @@ async fn main() { // If you want, you can do this on a per-call basis---here, we need it to // read the audio data that other people are sending us! let songbird_config = Config::default().decode_mode(DecodeMode::Decode); + let manager = songbird::Songbird::serenity_from_config(songbird_config); + + let data = UserData { + songbird: Arc::clone(&manager), + }; let mut client = Client::builder(&token, intents) + .voice_manager::(manager) + .data(Arc::new(data) as _) .event_handler(Handler) .framework(framework) - .register_songbird_from_config(songbird_config) .await .expect("Err creating client"); @@ -245,11 +255,7 @@ async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { }; let guild_id = msg.guild_id.unwrap(); - - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; if let Ok(handler_lock) = manager.join(guild_id, connect_to).await { // NOTE: this skips listening for the actual connection result. @@ -284,10 +290,7 @@ async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap(); - let manager = songbird::get(ctx) - .await - .expect("Songbird Voice client placed in at initialisation.") - .clone(); + let manager = &ctx.data::().songbird; let has_handler = manager.get(guild_id).is_some(); if has_handler { diff --git a/examples/twilight/src/main.rs b/examples/twilight/src/main.rs index 82b4ad7c6..27337cf67 100644 --- a/examples/twilight/src/main.rs +++ b/examples/twilight/src/main.rs @@ -27,7 +27,7 @@ use songbird::{ tracks::{PlayMode, TrackHandle}, Songbird, }; -use std::{collections::HashMap, env, error::Error, future::Future, num::NonZeroU64, sync::Arc}; +use std::{collections::HashMap, env, error::Error, future::Future, sync::Arc}; use tokio::sync::RwLock; use twilight_gateway::{ stream::{self, ShardEventStream}, @@ -39,7 +39,10 @@ use twilight_http::Client as HttpClient; use twilight_model::{ channel::Message, gateway::payload::incoming::MessageCreate, - id::{marker::GuildMarker, Id}, + id::{ + marker::{ChannelMarker, GuildMarker}, + Id, + }, }; use twilight_standby::Standby; @@ -157,11 +160,9 @@ async fn join(msg: Message, state: State) -> Result<(), Box()?; + let channel_id = msg.content.parse::>()?; let guild_id = msg.guild_id.ok_or("Can't join a non-guild channel.")?; - let channel_id = - NonZeroU64::new(channel_id).ok_or("Joined voice channel must have nonzero ID.")?; let content = match state.songbird.join(guild_id, channel_id).await { Ok(_handle) => format!("Joined <#{}>!", channel_id), diff --git a/src/lib.rs b/src/lib.rs index aace20479..05229e237 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,8 +96,6 @@ pub mod input; pub mod join; #[cfg(feature = "gateway")] mod manager; -#[cfg(feature = "serenity")] -pub mod serenity; #[cfg(feature = "gateway")] pub mod shards; #[cfg(any(test, feature = "internals"))] @@ -129,8 +127,5 @@ pub use crate::{ #[cfg(feature = "gateway")] pub use crate::{handler::*, manager::*}; -#[cfg(feature = "serenity")] -pub use crate::serenity::*; - pub use config::Config; pub use info::ConnectionInfo; diff --git a/src/manager.rs b/src/manager.rs index f08adcbc4..d36935cf6 100644 --- a/src/manager.rs +++ b/src/manager.rs @@ -57,7 +57,7 @@ impl Songbird { /// /// This must be [registered] after creation. /// - /// [registered]: crate::serenity::register_with + /// [registered]: serenity::client::ClientBuilder::voice_manager #[must_use] pub fn serenity() -> Arc { Self::serenity_from_config(Config::default()) @@ -68,7 +68,7 @@ impl Songbird { /// /// This must be [registered] after creation. /// - /// [registered]: crate::serenity::register_with + /// [registered]: serenity::client::ClientBuilder::voice_manager #[must_use] pub fn serenity_from_config(config: Config) -> Arc { Arc::new(Self { diff --git a/src/serenity.rs b/src/serenity.rs deleted file mode 100644 index 23c18369a..000000000 --- a/src/serenity.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Compatibility and convenience methods for working with [serenity]. -//! Requires the `"serenity"` feature. -//! -//! [serenity]: https://crates.io/crates/serenity - -use crate::{Config, Songbird}; -use serenity::{ - client::{ClientBuilder, Context}, - prelude::TypeMapKey, -}; -use std::sync::Arc; - -/// Zero-size type used to retrieve the registered [`Songbird`] instance -/// from serenity's inner [`TypeMap`]. -/// -/// [`Songbird`]: Songbird -/// [`TypeMap`]: serenity::prelude::TypeMap -pub struct SongbirdKey; - -impl TypeMapKey for SongbirdKey { - type Value = Arc; -} - -/// Installs a new songbird instance into the serenity client. -/// -/// This should be called after any uses of `ClientBuilder::type_map`. -pub fn register(client_builder: ClientBuilder) -> ClientBuilder { - let voice = Songbird::serenity(); - register_with(client_builder, voice) -} - -/// Installs a given songbird instance into the serenity client. -/// -/// This should be called after any uses of `ClientBuilder::type_map`. -pub fn register_with(client_builder: ClientBuilder, voice: Arc) -> ClientBuilder { - client_builder - .voice_manager::(voice.clone()) - .type_map_insert::(voice) -} - -/// Installs a given songbird instance into the serenity client. -/// -/// This should be called after any uses of `ClientBuilder::type_map`. -pub fn register_from_config(client_builder: ClientBuilder, config: Config) -> ClientBuilder { - let voice = Songbird::serenity_from_config(config); - register_with(client_builder, voice) -} - -/// Retrieve the Songbird voice client from a serenity context's -/// shared key-value store. -pub async fn get(ctx: &Context) -> Option> { - let data = ctx.data.read().await; - - data.get::().cloned() -} - -/// Helper trait to add installation/creation methods to serenity's -/// `ClientBuilder`. -/// -/// These install the client to receive gateway voice events, and -/// store an easily accessible reference to Songbird's managers. -pub trait SerenityInit { - /// Registers a new Songbird voice system with serenity, storing it for easy - /// access via [`get`]. - /// - /// [`get`]: get - #[must_use] - fn register_songbird(self) -> Self; - /// Registers a given Songbird voice system with serenity, as above. - #[must_use] - fn register_songbird_with(self, voice: Arc) -> Self; - /// Registers a Songbird voice system serenity, based on the given configuration. - #[must_use] - fn register_songbird_from_config(self, config: Config) -> Self; -} - -impl SerenityInit for ClientBuilder { - fn register_songbird(self) -> Self { - register(self) - } - - fn register_songbird_with(self, voice: Arc) -> Self { - register_with(self, voice) - } - - fn register_songbird_from_config(self, config: Config) -> Self { - register_from_config(self, config) - } -}