//! Example demonstrating how to make use of individual track audio events, //! and how to use the `TrackQueue` system. //! //! Requires the "cache", "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 = ["cache", "framework", "standard_framework", "voice"] //! ``` use std::{ env, sync::{ Arc, }, }; use reqwest::Client as HttpClient; use serenity::{ async_trait, client::{Client, Context, EventHandler}, framework::{ standard::{ macros::{command, group}, Args, CommandResult, Configuration, }, StandardFramework, }, http::Http, model::{channel::Message, gateway::Ready, prelude::ChannelId}, prelude::{GatewayIntents, Mentionable, TypeMapKey}, Result as SerenityResult, builder::{CreateEmbed, CreateMessage}, gateway::ShardManager, }; use serenity::all::standard::HelpOptions; use serenity::all::standard::CommandGroup; use serenity::all::GuildId; use serenity::all::UserId; use serenity::all::ClientBuilder; use serenity::all::Colour; use serenity::all::standard::help_commands; use serenity::all::standard::macros::help; use songbird::{ input::YoutubeDl, Event, EventContext, EventHandler as VoiceEventHandler, SerenityInit, TrackEvent, tracks::Track, }; struct HttpKey; impl TypeMapKey for HttpKey { type Value = HttpClient; } struct ShardManagerContainer; impl TypeMapKey for ShardManagerContainer { type Value = Arc; } struct Handler; use serde::{Deserialize, Serialize}; use tracing::error; use std::fs; use std::fs::File; use std::io::Write; use std::collections::HashSet; use std::collections::HashMap; use base64_stream::FromBase64Writer; use tokio::{sync::Mutex, process::Command}; use uuid::Uuid; #[async_trait] impl EventHandler for Handler { async fn ready(&self, _: Context, ready: Ready) { println!("{} is connected!", ready.user.name); } } #[group] #[commands( leave, mute, play, skip, stop, ping, unmute, volume, vox, chaos, restart, seek, tts, tts_list )] struct General; #[help] async fn my_help( context: &Context, msg: &Message, args: Args, help_options: &'static HelpOptions, groups: &[&'static CommandGroup], owners: HashSet, ) -> CommandResult { let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await; Ok(()) } ///////// #[derive(Serialize, Deserialize, Debug)] struct WinterGuildOptions { volume : f32, } impl Default for WinterGuildOptions { fn default() -> WinterGuildOptions { WinterGuildOptions { volume: 1.0, } } } #[derive(Serialize, Deserialize, Default, Debug)] struct WinterOptions { guild_options : HashMap, } impl WinterOptions { fn set_volume(&mut self, id: GuildId, volume: f32) { self.guild_options.entry(id).or_default().volume = volume; self.save_to_file(); } fn get_volume(&self, id: GuildId) -> f32 { if let Some(options) = self.guild_options.get(&id) { options.volume } else { WinterGuildOptions::default().volume } } fn load_from_file() -> WinterOptions { if let Ok(data) = fs::read_to_string("./data/winter_options.json") { let options : WinterOptions = serde_json::from_str(&data) .expect("winter_options is not valid JSON."); options } else { println!("Using default options."); WinterOptions::default() } } fn save_to_file(&self) { let data = serde_json::to_string(&self); if data.is_err() { println!("Failed to serialize options file."); return; } if fs::write("./data/winter_options.json", data.unwrap()).is_err() { println!("Failed to write options file."); } } } pub struct Winter { options : WinterOptions, } impl Winter { pub fn serenity() -> Arc> { Arc::new(Mutex::new(Self { options: WinterOptions::load_from_file() })) } } struct WinterKey; impl TypeMapKey for WinterKey { type Value = Arc>; } pub trait WinterSerenityInit { fn register_winter(self) -> Self; fn register_winter_with(self, voice: Arc>) -> Self; } impl WinterSerenityInit for ClientBuilder { fn register_winter(self) -> Self { register(self) } fn register_winter_with(self, voice: Arc>) -> Self { register_with(self, voice) } } pub fn register(client_builder: ClientBuilder) -> ClientBuilder { let voice = Winter::serenity(); register_with(client_builder, voice) } pub fn register_with(client_builder: ClientBuilder, voice: Arc>) -> ClientBuilder { client_builder .type_map_insert::(voice) } pub async fn winter_get(ctx: &Context) -> Option>> { let data = ctx.data.read().await; return data.get::().cloned(); } ///////// async fn get_http_client(ctx: &Context) -> HttpClient { let data = ctx.data.read().await; data.get::() .cloned() .expect("Guaranteed to exist in the typemap.") } #[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 framework = StandardFramework::new().group(&GENERAL_GROUP); framework.configure(Configuration::new().prefix("~")); let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT; let mut client = Client::builder(&token, intents) .event_handler(Handler) .framework(framework) .register_songbird() .register_winter() .type_map_insert::(HttpClient::new()) .await .expect("Err creating client"); { let mut data = client.data.write().await; data.insert::(client.shard_manager.clone()); } let shard_manager = client.shard_manager.clone(); tokio::spawn(async move { tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler"); shard_manager.shutdown_all().await; }); if let Err(why) = client.start().await { error!("Client error: {:?}", why); } } async fn ensure_joined(ctx: &Context, msg: &Message) -> bool { let (guild_id, channel_id) = { let guild = msg.guild(&ctx.cache).unwrap(); let channel_id = guild .voice_states .get(&msg.author.id) .and_then(|voice_state| voice_state.channel_id); (guild.id, channel_id) }; let connect_to = match channel_id { Some(channel) => channel, None => { reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await; return false; }, }; let manager = songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let winter = winter_get(ctx) .await .expect("Winter placed in at initialisation.") .clone(); if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; if handler.current_channel().is_some() { if handler.current_channel().unwrap() == songbird::id::ChannelId::from(connect_to) { return true; } else { reply(&ctx, &msg, "Wrong channel, silly!", &format!("You must to be in my channel, {}, to play music!", connect_to.mention()), None, true).await; return false; } } } if let Ok(handler_lock) = manager.join(guild_id, connect_to).await { check_msg( msg.channel_id .say(&ctx.http, &format!("Joined {}", connect_to.mention())) .await, ); let chan_id = msg.channel_id; let send_http = ctx.http.clone(); let mut handler = handler_lock.lock().await; if !handler.is_deaf() { if let Err(e) = handler.deafen(true).await { check_msg( msg.channel_id .say(&ctx.http, format!("Failed: {:?}", e)) .await, ); } } handler.add_global_event( Event::Track(TrackEvent::End), TrackEndNotifier { chan_id, http: send_http, }, ); handler.add_global_event( Event::Track(TrackEvent::End), TrackVolumeUpdater { winter, guild_id, }, ); return true; } else { reply(&ctx, &msg, "I'm not allowed here!", "Failed to join the channel.", None, true).await; return false; } } struct TrackVolumeUpdater { winter: Arc>, guild_id: GuildId, } #[async_trait] impl VoiceEventHandler for TrackVolumeUpdater { async fn act(&self, ctx: &EventContext<'_>) -> Option { // Update the volume for the track we are about to play. if let EventContext::Track(track_list) = ctx { if track_list.len() > 1 { let winter = self.winter.lock().await; let volume = winter.options.get_volume(self.guild_id); let _ = track_list[1].1.set_volume(volume); } } None } } struct TrackEndNotifier { chan_id: ChannelId, http: Arc, } #[async_trait] impl VoiceEventHandler for TrackEndNotifier { async fn act(&self, ctx: &EventContext<'_>) -> Option { if let EventContext::Track(track_list) = ctx { check_msg( self.chan_id .say(&self.http, &format!("Tracks ended: {}.", track_list.len())) .await, ); } None } } #[command] #[only_in(guilds)] 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 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, ); } reply(&ctx, &msg, "Left Voice Channel", "Goodbye! <3", None, false).await; } else { reply(&ctx, &msg, "Oops!", "I wasn't in a voice channel, so I did nothing", None, true).await; } Ok(()) } #[command] #[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 handler_lock = match manager.get(guild_id) { Some(handler) => handler, None => { reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await; return Ok(()); }, }; let mut handler = handler_lock.lock().await; if handler.is_mute() { reply(&ctx, &msg, "Aaaaaa!", "I am already being quiet! Why are you being mean to me :<", None, true).await; } else { if let Err(e) = handler.mute(true).await { check_msg( msg.channel_id .say(&ctx.http, format!("Failed: {:?}", e)) .await, ); } reply(&ctx, &msg, "Muted!", "I'll be quiet for now...", None, false).await; } Ok(()) } #[command] async fn ping(ctx: &Context, msg: &Message) -> CommandResult { reply(&ctx, &msg, "Ping!", "Hello there! :3", None, false).await; Ok(()) } struct SongEndNotifier { chan_id: ChannelId, http: Arc, } #[async_trait] impl VoiceEventHandler for SongEndNotifier { async fn act(&self, _ctx: &EventContext<'_>) -> Option { check_msg( self.chan_id .say(&self.http, "Song faded out completely!") .await, ); None } } #[derive(Deserialize, Serialize, Debug)] pub struct YtDlpOutput { pub artist: Option, pub album: Option, pub channel: Option, pub duration: Option, pub filesize: Option, pub http_headers: Option>, pub release_date: Option, pub thumbnail: Option, pub title: Option, pub track: Option, pub upload_date: Option, pub uploader: Option, pub url: String, pub webpage_url: Option, } #[command] #[only_in(guilds)] async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); let url = args.rest().to_string(); if url.is_empty() { reply(&ctx, &msg, "Tell me what you want!", "You must provide a URL or search term for me to play video or audio!", None, false).await; return Ok(()); } let http_client = get_http_client(ctx).await; let manager = songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let winter_lock = winter_get(ctx) .await .expect("Winter placed in at initialisation.") .clone(); let winter = winter_lock.lock().await; if !ensure_joined(ctx, msg).await { return Ok(()); } let do_search = !url.starts_with("http"); let sources = if do_search { let mut vec = Vec::new(); vec.push(YoutubeDl::new_search(http_client, url)); vec } else { let ytdl_args = [ "-j", url.as_str(), "--flat-playlist", ]; let mut output = Command::new("yt-dlp") .args(ytdl_args) .output() .await .unwrap(); if !output.status.success() { println!("Fuck! Couldn't run yt-dlp"); return Ok(()); } let out = output .stdout .split_mut(|&b| b == b'\n') .filter_map(|x| (!x.is_empty()).then(|| serde_json::from_slice(x))) .collect::, _>>() .unwrap(); let mut vec = Vec::new(); for playlist_src in out { vec.push(YoutubeDl::new(http_client.clone(), playlist_src.url)); } vec }; if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; for source in sources { let mut input = songbird::input::Input::from(source); let aux_metadata = input.aux_metadata().await.unwrap(); let title = aux_metadata.title.clone().unwrap_or("Unknown Title".to_string()); let artist = aux_metadata.artist.clone().unwrap_or("Unknown Artist".to_string()); let thumbnail = aux_metadata.thumbnail.clone(); let mut duration = "Unknown duration".to_string(); if aux_metadata.duration.is_some() { let meta_duration = aux_metadata.duration.unwrap(); let seconds = meta_duration.as_secs() % 60; let minutes = (meta_duration.as_secs() / 60) % 60; let hours = (meta_duration.as_secs() / 60) / 60; duration = format!("{:02}:{:02}:{:02}", hours, minutes, seconds).to_string(); } let track = songbird::tracks::Track::new(input) .volume(winter.options.get_volume(guild_id)); handler.enqueue(track).await; let mut embed = CreateEmbed::new() .title(title.clone()) .field("Arist", artist, true) .field("Duration", duration, true) .field("Queue Position", &format!("{:02}", handler.queue().len()), true) .color(Colour::from_rgb(202,255,239)); if thumbnail.is_some() { embed = embed.thumbnail(thumbnail.unwrap()); } let builder = CreateMessage::new().embed(embed); let msg = msg.channel_id.send_message(&ctx, builder).await; if let Err(why) = msg { println!("Error sending message: {:?}", why); } } } Ok(()) } #[command] #[only_in(guilds)] async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); let url = match args.single::() { Ok(url) => url, Err(_) => { reply(&ctx, &msg, "Tell me what you want!", "You must provide a URL or search term for me to play video or audio!", None, false).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 http_client = get_http_client(ctx).await; let manager = songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let winter_lock = winter_get(ctx) .await .expect("Winter placed in at initialisation.") .clone(); let winter = winter_lock.lock().await; if !ensure_joined(ctx, msg).await { return Ok(()); } let do_search = !url.starts_with("http"); if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; let src = if do_search { YoutubeDl::new_search(http_client, url) } else { YoutubeDl::new(http_client, url) }; check_msg(msg.reply(&ctx.http, "Added song to chaos mode.").await); let track = songbird::tracks::Track::new(src.into()) .volume(winter.options.get_volume(guild_id)); handler.play(track); } Ok(()) } #[command] #[only_in(guilds)] async fn vox(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); let line = args.rest(); if line.is_empty() { check_msg( msg.channel_id .say(&ctx.http, "Must provide a VOX line") .await, ); return Ok(()); } let words = line.split(" "); let manager = songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let winter_lock = winter_get(ctx) .await .expect("Winter placed in at initialisation.") .clone(); let winter = winter_lock.lock().await; if !ensure_joined(ctx, msg).await { return Ok(()); } if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; for (_, word) in words.enumerate() { // basic sanity check if word.chars().all(|x| x.is_alphanumeric()) { let vox_path = format!("./assets/vox/{}.wav", word); let source = songbird::input::File::new(vox_path); let track = Track::new(source.into()) .volume(winter.options.get_volume(guild_id)); handler.enqueue(track).await; } } check_msg( msg.channel_id .say( &ctx.http, format!("Added vox line: {} to queue! (position {})", line, handler.queue().len()), ) .await, ); } Ok(()) } #[derive(Serialize, Deserialize, Debug)] struct TikTokPOSTBody { text : String, voice : String, } #[derive(Serialize, Deserialize, Debug)] struct TikTokResponse { data : String, } const TIKTOK_VOICES: &'static [&'static str] = &[ // DISNEY VOICES "en_us_ghostface", // Ghost Face "en_us_chewbacca", // Chewbacca "en_us_c3po", // C3PO "en_us_stitch", // Stitch "en_us_stormtrooper", // Stormtrooper "en_us_rocket", // Rocket // ENGLISH VOICES "en_au_001", // English AU - Female "en_au_002", // English AU - Male "en_uk_001", // English UK - Male 1 "en_uk_003", // English UK - Male 2 "en_us_001", // English US - Female (Int. 1) "en_us_002", // English US - Female (Int. 2) "en_us_006", // English US - Male 1 "en_us_007", // English US - Male 2 "en_us_009", // English US - Male 3 "en_us_010", // English US - Male 4 // EUROPE VOICES "fr_001", // French - Male 1 "fr_002", // French - Male 2 "de_001", // German - Female "de_002", // German - Male "es_002", // Spanish - Male // AMERICA VOICES "es_mx_002", // Spanish MX - Male "br_001", // Portuguese BR - Female 1 "br_003", // Portuguese BR - Female 2 "br_004", // Portuguese BR - Female 3 "br_005", // Portuguese BR - Male // ASIA VOICES "id_001", // Indonesian - Female "jp_001", // Japanese - Female 1 "jp_003", // Japanese - Female 2 "jp_005", // Japanese - Female 3 "jp_006", // Japanese - Male "kr_002", // Korean - Male 1 "kr_003", // Korean - Female "kr_004", // Korean - Male 2 // SINGING VOICES "en_female_f08_salut_damour", // Alto "en_male_m03_lobby", // Tenor "en_female_f08_warmy_breeze", // Warmy Breeze "en_male_m03_sunshine_soon", // Sunshine Soon // OTHER "en_male_narration", // narrator "en_male_funny", // wacky "en_female_emotional" // peaceful ]; use rand::seq::SliceRandom; #[command] #[only_in(guilds)] async fn tts_list(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let mut voice_list : String = "".to_string(); for voice in TIKTOK_VOICES { voice_list += voice; voice_list += "\n"; } reply(&ctx, &msg, "TikTok TTS Voices", voice_list.as_str(), None, false).await; Ok(()) } #[command] #[only_in(guilds)] async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap(); let mut line = args.rest(); if line.is_empty() { check_msg( msg.channel_id .say(&ctx.http, "Must provide a TTS line") .await, ); return Ok(()); } line = line.trim(); let mut voice = TIKTOK_VOICES.choose(&mut rand::thread_rng()).unwrap().to_string(); if line.starts_with(".") { match line.split_once(' ') { Some((key, value)) => { voice = key[1..key.len()].to_string(); line = value; } None => { reply(&ctx, &msg, "Oh no!", "No text for TTS!", None, true).await; return Ok(()); } } } let manager = songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let winter_lock = winter_get(ctx) .await .expect("Winter placed in at initialisation.") .clone(); let winter = winter_lock.lock().await; if !ensure_joined(ctx, msg).await { return Ok(()); } if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; let args = TikTokPOSTBody { text: line.to_string(), voice: voice.clone(), }; let json = serde_json::to_string(&args).expect("Failed to serialize TikTokPOST Body"); let client = reqwest::Client::new(); let res = client .post("https://tiktok-tts.weilnet.workers.dev/api/generation") .header(reqwest::header::CONTENT_TYPE, "application/json") .body(json) .send() .await?; let response : TikTokResponse = match serde_json::from_str(res.text().await?.as_str()) { Ok(response) => response, Err(why) => { reply(&ctx, &msg, "Oh no!", &format!("Error querying tts. Reason: {:?}", why), None, true).await; return Ok(()); }, }; let file_path = format!("/tmp/tts{}.wav", Uuid::new_v4().to_string()); let tts_data = File::create(file_path.clone()).unwrap(); let mut writer = FromBase64Writer::new(tts_data); writer.write_all(response.data.as_bytes()).unwrap(); writer.flush().unwrap(); let source = songbird::input::File::new(file_path); let track = Track::new(source.into()) .volume(winter.options.get_volume(guild_id)); handler.play(track); check_msg( msg.channel_id .say( &ctx.http, format!("Playing TTS line: {} with voice: {}", line, voice), ) .await, ); } Ok(()) } #[command] #[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(); if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; let queue = handler.queue(); let _ = queue.skip(); check_msg( msg.channel_id .say( &ctx.http, format!("Song skipped: {} in queue.", queue.len()), ) .await, ); } else { reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await; } Ok(()) } #[command] #[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(); if let Some(handler_lock) = manager.get(guild_id) { let mut handler = handler_lock.lock().await; let queue = handler.queue(); let _ = queue.stop(); let _ = handler.stop(); reply(&ctx, &msg, "Empty inside", "Queue + chaos cleared.", None, false).await; } else { reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await; } Ok(()) } #[command] #[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(); 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, ); } reply(&ctx, &msg, "I am free!", "I am unmuted now! :3", None, false).await; } else { reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await; } Ok(()) } #[command] #[only_in(guilds)] async fn volume(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let volume_string = args.rest(); if volume_string.is_empty() { reply(&ctx, &msg, "Tell me what you want!", "You need to tell me a volume expression in percentage.", None, true).await; return Ok(()); } let volume_human_eval = meval::eval_str(volume_string); if !volume_human_eval.is_ok() { reply(&ctx, &msg, "/shrug", "Couldn't evaluate volume expression.", None, true).await; return Ok(()); } let volume_human = volume_human_eval.unwrap() as f32; let volume = volume_human / 100.0; let guild_id = msg.guild_id.unwrap(); let manager = songbird::get(ctx) .await .expect("Songbird Voice client placed in at initialisation.") .clone(); let winter_lock = winter_get(ctx) .await .expect("Winter placed in at initialisation.") .clone(); let mut winter = winter_lock.lock().await; if let Some(handler_lock) = manager.get(guild_id) { let handler = handler_lock.lock().await; for (_, track) in handler.queue().current_queue().iter().enumerate() { if !track.set_volume(volume).is_ok() { reply(&ctx, &msg, "IDK!", "Failed to set current volume.", None, true).await; return Ok(()); } } } winter.options.set_volume(guild_id, volume); reply(&ctx, &msg, "Am I being too loud?", &format!("Set volume to {}%.", volume_human), None, false).await; Ok(()) } #[command] #[only_in(guilds)] async fn seek(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let seek_string = args.rest(); if seek_string.is_empty() { reply(&ctx, &msg, "Tell me what you want!", "You need to tell me a position expression in seconds.", None, true).await; return Ok(()); } let seek_human_eval = meval::eval_str(seek_string); if !seek_human_eval.is_ok() { reply(&ctx, &msg, "/shrug", "Couldn't evaluate seek expression.", None, true).await; return Ok(()); } let seek_human = seek_human_eval.unwrap() as f32; let seek = std::time::Duration::from_secs_f32(seek_human); let guild_id = msg.guild_id.unwrap(); 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 handler = handler_lock.lock().await; for (_, track) in handler.queue().current_queue().iter().enumerate() { let _success = track.seek(seek); } } let seconds = seek.as_secs() % 60; let minutes = (seek.as_secs() / 60) % 60; let hours = (seek.as_secs() / 60) / 60; reply(&ctx, &msg, "I'm moving!", &format!("Seeked to to {:02}:{:02}:{:02}.", hours, minutes, seconds), None, false).await; Ok(()) } #[command] #[only_in(guilds)] async fn restart(ctx: &Context, msg: &Message) -> CommandResult { let data = ctx.data.read().await; if let Some(manager) = data.get::() { msg.reply(ctx, "Shutting down!").await?; manager.shutdown_all().await; } else { msg.reply(ctx, "There was a problem getting the shard manager").await?; return Ok(()); } 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); } } async fn reply>(ctx: &Context, context_msg: &Message, title: S, desc: S, image: Option, error: bool) { let mut embed = CreateEmbed::new() .title(title.into()) .description(desc.into()); if image.is_some() { embed = embed.image(image.unwrap().into()); } if error { embed = embed.color(Colour::from_rgb(255,218,218)); } else { embed = embed.color(Colour::from_rgb(223,255,198)); } let builder = CreateMessage::new().embed(embed); let msg = context_msg.channel_id.send_message(&ctx, builder).await; if let Err(why) = msg { println!("Error sending message: {:?}", why); } }