From b9b631c892fd211f2d91052bc4496645913e1868 Mon Sep 17 00:00:00 2001 From: "oupson1er@gmail.com" Date: Thu, 18 Mar 2021 13:52:34 +0100 Subject: [PATCH] Working on music --- Cargo.toml | 2 +- src/api.rs | 1 - src/commands/admin.rs | 10 +- src/commands/general.rs | 11 +- src/commands/music.rs | 406 ++++++++++++++++++++++++++++--------- src/commands/owner.rs | 2 +- src/commands/roulette.rs | 36 ++-- src/data/guilds_options.rs | 4 +- 8 files changed, 348 insertions(+), 124 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 00ff139..91947bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,4 @@ serde_json = "1.0" log = "0.4" log4rs = "1.0" ctrlc = "3.1" -songbird = { version = "0.1.0", features = ["driver"], optional = true } \ No newline at end of file +songbird = { version = "0.1.4", features = ["driver", "builtin-queue"], optional = true } \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index 4748b52..06c9541 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,4 +1,3 @@ -use crate::commands::Result; use serenity::{framework::standard::CommandResult, model::channel::Message, prelude::Context}; /// Send a reply to the channel the message was received on. diff --git a/src/commands/admin.rs b/src/commands/admin.rs index cd45733..c707e80 100644 --- a/src/commands/admin.rs +++ b/src/commands/admin.rs @@ -4,9 +4,9 @@ use serenity::{ macros::{command, group}, CommandResult, }, + http::CacheHttp, model::prelude::*, prelude::*, - http::CacheHttp, }; use crate::data::GuildOptionsKey; @@ -120,14 +120,16 @@ async fn ghost_pings(ctx: &Context, msg: &Message) -> CommandResult { .map(|u| u.name) .unwrap_or_else(|_| String::from("Unkown")); - message += &format!("{} : {:?}\n", sender, ping.roles.iter().map(|r| format!("", r))) + message += &format!( + "{} : {:?}\n", + sender, + ping.roles.iter().map(|r| format!("", r)) + ) } message += "\x60\x60\x60"; crate::api::send_reply(ctx, msg, message).await?; } - - Ok(()) } diff --git a/src/commands/general.rs b/src/commands/general.rs index cb73370..c3cc6b5 100644 --- a/src/commands/general.rs +++ b/src/commands/general.rs @@ -1,7 +1,6 @@ use crate::{api, data::ShardManagerContainer}; use futures::StreamExt; use log::error; -use rand::prelude::IteratorRandom; use serenity::{ client::bridge::gateway::ShardId, framework::standard::{ @@ -306,7 +305,15 @@ async fn random_mute(ctx: &Context, msg: &Message) -> CommandResult { members[m].add_role(&ctx.http, role).await?; - api::send_reply(ctx, msg, format!("{} was choosen by the roulette of the mute !", members[m].mention())).await?; + api::send_reply( + ctx, + msg, + format!( + "{} was choosen by the roulette of the mute !", + members[m].mention() + ), + ) + .await?; } Ok(()) } diff --git a/src/commands/music.rs b/src/commands/music.rs index 78496b8..39f26a1 100644 --- a/src/commands/music.rs +++ b/src/commands/music.rs @@ -1,20 +1,136 @@ +use std::sync::Arc; + +use tokio::sync::Mutex as TokioMutex; + use crate::data::{GuildOptions, GuildOptionsKey}; -use log::{error, info}; +use log::error; use serenity::{ - client::{Context}, + builder::CreateMessage, + client::Context, framework::standard::{ macros::{command, group}, Args, CommandResult, }, - model::{channel::Message, guild::PartialMember, id::GuildId, misc::Mentionable}, - prelude::*, - Result as SerenityResult, + http::Http, + model::{ + channel::Message, + guild::PartialMember, + id::{ChannelId, GuildId}, + misc::Mentionable, + }, }; -use std::sync::Arc; +use songbird::{input::Metadata, Call, Event, EventContext, TrackEvent}; +struct TrackStartNotifier { + chan_id: ChannelId, + http: Arc, + handler_lock: Arc>, +} + +#[async_trait::async_trait] +impl songbird::EventHandler for TrackStartNotifier { + async fn act(&self, ctx: &EventContext<'_>) -> Option { + if let EventContext::Track(_track_list) = ctx { + let handler = self.handler_lock.lock().await; + if let Some(np) = handler.queue().current() { + let metadata = np.metadata(); + if let Err(why) = self + .chan_id + .send_message(&self.http, |m| { + embed_song(m, metadata); + m + }) + .await + { + error!("Error sending message: {:?}", why); + } + } else { + if let Err(why) = self.chan_id.say(&self.http, "Queue finished").await { + error!("Error sending message: {:?}", why); + } + } + } + + None + } +} + +fn embed_song(msg: &mut CreateMessage, metadata: &Metadata) { + msg.embed(|e| { + e.title("Now playing"); + + if let Some(title) = &metadata.title { + e.field("title", title, true); + } + + if let Some(url) = &metadata.source_url { + e.url(url); + } + + if let Some(duration) = &metadata.duration { + let seconds = duration.as_secs(); + + let hours = seconds / (60 * 60); + let min = (seconds - (hours * 60 * 60)) / 60; + let seconds = seconds - (min * 60); + + e.field( + "duration", + if hours > 0 { + format!("{}:{:02}:{:02}", hours, min, seconds) + } else { + format!("{}:{:02}", min, seconds) + }, + false, + ); + } + + if let Some(img) = &metadata.thumbnail { + e.image(img); + } + e + }); +} + +fn embed_queued(msg: &mut CreateMessage, metadata: &Metadata) { + msg.embed(|e| { + e.title("Queued"); + + if let Some(title) = &metadata.title { + e.field("title", title, true); + } + + if let Some(url) = &metadata.source_url { + e.url(url); + } + + if let Some(duration) = &metadata.duration { + let seconds = duration.as_secs(); + + let hours = seconds / (60 * 60); + let min = (seconds - (hours * 60 * 60)) / 60; + let seconds = seconds - (min * 60); + + e.field( + "duration", + if hours > 0 { + format!("{}:{:02}:{:02}", hours, min, seconds) + } else { + format!("{}:{:02}", min, seconds) + }, + false, + ); + } + + if let Some(img) = &metadata.thumbnail { + e.image(img); + } + e + }); +} #[group] -#[commands(join, leave, play, stop)] +#[commands(join, leave, play, stop, next)] struct Music; #[command] @@ -23,7 +139,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { let guild = match msg.guild(&ctx.cache).await { Some(guild) => guild, None => { - msg.channel_id.say(&ctx.http, "DMs not supported").await; + msg.channel_id.say(&ctx.http, "DMs not supported").await?; return Ok(()); } @@ -34,9 +150,9 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { } else { false } { - msg.channel_id - .say(&ctx.http, "Error, you cant play music") - .await; + msg.channel_id + .say(&ctx.http, "Error, you cant play music") + .await?; return Ok(()); } @@ -50,25 +166,47 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult { let connect_to = match channel_id { Some(channel) => channel, None => { - msg.reply(ctx, "Not in a voice channel").await; + 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 manager = songbird::get(ctx) + .await + .expect("Songbird Voice client placed in at initialisation.") + .clone(); - if manager.join(guild_id, connect_to).await.1.is_ok() { - + // TODO ADMIN PERMISSION + if manager.get(guild_id).is_none() { + let (handler_lock, success) = manager.join(guild_id, connect_to).await; + + if success.is_ok() { + let mut handler = handler_lock.lock().await; + handler.add_global_event( + Event::Track(TrackEvent::Play), + TrackStartNotifier { + chan_id: msg.channel_id, + http: ctx.http.clone(), + handler_lock: handler_lock.clone(), + }, + ); msg.channel_id - .say(&ctx.http, &format!("Joined {}", connect_to.mention())) - .await; - } else { - + .say( + &ctx.http, + &format!("Joined channel {}", connect_to.mention()), + ) + .await?; + } else { msg.channel_id .say(&ctx.http, "Error joining the channel") - .await; + .await?; + } + } else { + // TODO WARN + msg.channel_id + .say(&ctx.http, "Error : Already connected on another channel") + .await?; } Ok(()) @@ -84,7 +222,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { { Some(id) => id, None => { - msg.channel_id.say(&ctx.http, "DMs not supported").await; + msg.channel_id.say(&ctx.http, "DMs not supported").await?; return Ok(()); } @@ -95,26 +233,28 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult { } else { false } { - msg.channel_id - .say(&ctx.http, "Error, you cant play music") - .await; + msg.channel_id + .say(&ctx.http, "Error, you cant play music") + .await?; return Ok(()); } - let manager = songbird::get(ctx).await - .expect("Songbird Voice client placed in at initialisation.").clone(); + 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 { - msg.channel_id.say(&ctx.http, format!("Failed: {:?}", e)).await; - } - - msg.channel_id.say(&ctx.http, "Left voice channel").await; - } else { - msg.reply(ctx, "Not in a voice channel").await; + if let Some(_handler) = manager.get(guild_id) { + if let Err(e) = manager.remove(guild_id).await { + msg.channel_id + .say(&ctx.http, format!("Failed: {:?}", e)) + .await?; } + + msg.channel_id.say(&ctx.http, "Left voice channel").await?; + } else { + msg.reply(ctx, "Not in a voice channel").await?; + } Ok(()) } @@ -124,31 +264,20 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let url = match args.single::() { Ok(url) => url, Err(_) => { - - msg.channel_id - .say(&ctx.http, "Must provide a URL to a video or audio") - .await; + msg.channel_id + .say(&ctx.http, "Must provide a URL to a video or audio") + .await?; return Ok(()); } }; - if !url.starts_with("http") { - - msg.channel_id - .say(&ctx.http, "Must provide a valid URL") - .await; - - return Ok(()); - } - let guild_id = match ctx.cache.guild_channel(msg.channel_id).await { Some(channel) => channel.guild_id, None => { - - msg.channel_id - .say(&ctx.http, "Error finding channel info") - .await; + msg.channel_id + .say(&ctx.http, "Error finding channel info") + .await?; return Ok(()); } @@ -159,36 +288,128 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { } else { false } { - msg.channel_id - .say(&ctx.http, "Error, you cant play music") - .await; + msg.channel_id + .say(&ctx.http, "Error, you cant play music") + .await?; return Ok(()); } - let manager = songbird::get(ctx).await - .expect("Songbird Voice client placed in at initialisation.").clone(); + 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 source = match songbird::ytdl(&url).await { + if let Some(handler_lock) = manager.get(guild_id) { + let mut handler = handler_lock.lock().await; + + let source = if url.starts_with("http") { + match songbird::ytdl(&url).await { Ok(source) => source, Err(why) => { error!("Err starting source: {:?}", why); - - msg.channel_id.say(&ctx.http, "Error sourcing ffmpeg").await; - + + msg.channel_id + .say(&ctx.http, "Error sourcing ffmpeg") + .await?; + return Ok(()); - }, - }; - - handler.play_source(source); - - msg.channel_id.say(&ctx.http, "Playing song").await; + } + } } else { - msg.channel_id.say(&ctx.http, "Not in a voice channel to play in").await; + match songbird::input::ytdl_search(&url).await { + Ok(source) => source, + Err(why) => { + error!("Err starting source: {:?}", why); + + msg.channel_id + .say(&ctx.http, "Error sourcing ffmpeg") + .await?; + + return Ok(()); + } + } + }; + + let meta = &source.metadata.clone(); + + handler.enqueue_source(source); + + msg.channel_id + .send_message(&ctx.http, |m| { + if handler.queue().len() == 1 { + embed_song(m, meta); + } else { + embed_queued(m, meta); + } + m + }) + .await?; + } else { + msg.channel_id + .say(&ctx.http, "Not in a voice channel to play in") + .await?; + } + + Ok(()) +} + +#[command] +#[description("Next music")] +async fn next(ctx: &Context, msg: &Message) -> CommandResult { + let guild_id = match ctx.cache.guild_channel(msg.channel_id).await { + Some(channel) => channel.guild_id, + None => { + msg.channel_id + .say(&ctx.http, "Error finding channel info") + .await?; + + return Ok(()); } - + }; + + if if let Some(member) = &msg.member { + is_mute(ctx, member, guild_id).await.unwrap_or(false) + } else { + false + } { + msg.channel_id + .say(&ctx.http, "Error, you cant play music") + .await?; + return Ok(()); + } + + let manager = songbird::get(ctx) + .await + .expect("Songbird Voice client placed in at initialisation.") + .clone(); + + if let Some(handler) = manager.get(guild_id) { + let handler = handler.lock().await; + + let queue = handler.queue(); + + if let Some(current) = queue.current() { + let metadata = current.metadata(); + log::debug!("Metadata : {:?}", metadata); + } + + let current_queue = queue.current_queue(); + let next = current_queue.get(1); + queue.skip()?; + + if let Some(current) = next { + let metadata = current.metadata(); + log::debug!("{:?}", metadata); + + if let Some(title) = &metadata.title { + msg.channel_id + .say(&ctx.http, format!("Playing {}", title)) + .await?; + } + } + } else { + msg.reply(ctx, "Not in a voice channel").await?; + } Ok(()) } @@ -199,10 +420,9 @@ async fn stop(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = match ctx.cache.guild_channel(msg.channel_id).await { Some(channel) => channel.guild_id, None => { - - msg.channel_id - .say(&ctx.http, "Error finding channel info") - .await; + msg.channel_id + .say(&ctx.http, "Error finding channel info") + .await?; return Ok(()); } @@ -213,26 +433,24 @@ async fn stop(ctx: &Context, msg: &Message) -> CommandResult { } else { false } { - - msg.channel_id - .say(&ctx.http, "Error, you cant play music") - .await; + msg.channel_id + .say(&ctx.http, "Error, you cant play music") + .await?; return Ok(()); } - + let manager = songbird::get(ctx) + .await + .expect("Songbird Voice client placed in at initialisation.") + .clone(); + if let Some(handler) = manager.get(guild_id) { + let mut handler = handler.lock().await; + handler.stop(); - let manager = songbird::get(ctx).await - .expect("Songbird Voice client placed in at initialisation.").clone(); - if let Some(handler) = manager.get(guild_id) { - - let mut handler = handler.lock().await; - handler.stop(); - - msg.channel_id.say(&ctx.http, "Stopping").await; - } else { - msg.reply(ctx, "Not in a voice channel").await; - } + msg.channel_id.say(&ctx.http, "Stopping").await?; + } else { + msg.reply(ctx, "Not in a voice channel").await?; + } Ok(()) } diff --git a/src/commands/owner.rs b/src/commands/owner.rs index 9a70447..a3a050b 100644 --- a/src/commands/owner.rs +++ b/src/commands/owner.rs @@ -1,3 +1,4 @@ +use log::debug; use serenity::{ framework::standard::{ macros::{command, group}, @@ -6,7 +7,6 @@ use serenity::{ model::prelude::*, prelude::*, }; -use log::debug; use std::convert::TryFrom; #[group] diff --git a/src/commands/roulette.rs b/src/commands/roulette.rs index c31ccea..03c0059 100644 --- a/src/commands/roulette.rs +++ b/src/commands/roulette.rs @@ -2,7 +2,7 @@ use crate::{ api, data::{GuildOptions, GuildOptionsKey}, }; -use log::{debug, error}; +use log::debug; use rand::Rng; use serenity::{ framework::standard::{ @@ -24,7 +24,7 @@ struct Roulette; #[bucket = "roulette"] async fn shot(ctx: &Context, msg: &Message, args: Args) -> CommandResult { let _message = args.message().trim_end(); - if _message == "shot" || _message == "" { + if _message == "shot" || _message.is_empty() { if rand::thread_rng().gen_range(0..6) == 0 { api::send_reply(ctx, &msg, "💥").await?; } else { @@ -59,24 +59,22 @@ async fn kick(ctx: &Context, msg: &Message) -> CommandResult { "Error : You cannot play to the REAL RUSSIAN ROULETTE in this guild", ) .await?; + } else if rand::thread_rng().gen_range(0..6) == 0 { + api::send_reply(ctx, &msg, "💥").await?; + + msg.author + .create_dm_channel(&ctx) + .await? + .send_message(&ctx.http, |m| m.content("<:cheh:780736245675982909>")) + .await?; + + guild_id + .member(&ctx.http, &msg.author) + .await? + .kick_with_reason(&ctx.http, "You loose at the roulette") + .await?; } else { - if rand::thread_rng().gen_range(0..6) == 0 { - api::send_reply(ctx, &msg, "💥").await?; - - msg.author - .create_dm_channel(&ctx) - .await? - .send_message(&ctx.http, |m| m.content("<:cheh:780736245675982909>")) - .await?; - - guild_id - .member(&ctx.http, &msg.author) - .await? - .kick_with_reason(&ctx.http, "You loose at the roulette") - .await?; - } else { - api::send_reply(&ctx, &msg, format!("Click ! Reloading")).await?; - } + api::send_reply(&ctx, &msg, format!("Click ! Reloading")).await?; } } Ok(()) diff --git a/src/data/guilds_options.rs b/src/data/guilds_options.rs index da0fb8f..53eefdb 100644 --- a/src/data/guilds_options.rs +++ b/src/data/guilds_options.rs @@ -26,7 +26,7 @@ pub(crate) struct GuildOptions { pub(crate) mute_id: Option, pub(crate) roulette_options: RouletteOptions, pub(crate) last_ghost_pings: Vec, - pub(crate) mutes : Vec<(UserId, u64)>, + pub(crate) mutes: Vec<(UserId, u64)>, } impl GuildOptions { @@ -155,7 +155,7 @@ impl Default for GuildOptions { guild_id: None, mute_id: None, last_ghost_pings: Vec::new(), - mutes: Vec::new() + mutes: Vec::new(), } } }