Working on music

This commit is contained in:
oupson1er@gmail.com 2021-03-18 13:52:34 +01:00
parent 5ac371acb1
commit b9b631c892
8 changed files with 348 additions and 124 deletions

View File

@ -24,4 +24,4 @@ serde_json = "1.0"
log = "0.4" log = "0.4"
log4rs = "1.0" log4rs = "1.0"
ctrlc = "3.1" ctrlc = "3.1"
songbird = { version = "0.1.0", features = ["driver"], optional = true } songbird = { version = "0.1.4", features = ["driver", "builtin-queue"], optional = true }

View File

@ -1,4 +1,3 @@
use crate::commands::Result;
use serenity::{framework::standard::CommandResult, model::channel::Message, prelude::Context}; use serenity::{framework::standard::CommandResult, model::channel::Message, prelude::Context};
/// Send a reply to the channel the message was received on. /// Send a reply to the channel the message was received on.

View File

@ -4,9 +4,9 @@ use serenity::{
macros::{command, group}, macros::{command, group},
CommandResult, CommandResult,
}, },
http::CacheHttp,
model::prelude::*, model::prelude::*,
prelude::*, prelude::*,
http::CacheHttp,
}; };
use crate::data::GuildOptionsKey; use crate::data::GuildOptionsKey;
@ -120,14 +120,16 @@ async fn ghost_pings(ctx: &Context, msg: &Message) -> CommandResult {
.map(|u| u.name) .map(|u| u.name)
.unwrap_or_else(|_| String::from("Unkown")); .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"; message += "\x60\x60\x60";
crate::api::send_reply(ctx, msg, message).await?; crate::api::send_reply(ctx, msg, message).await?;
} }
Ok(()) Ok(())
} }

View File

@ -1,7 +1,6 @@
use crate::{api, data::ShardManagerContainer}; use crate::{api, data::ShardManagerContainer};
use futures::StreamExt; use futures::StreamExt;
use log::error; use log::error;
use rand::prelude::IteratorRandom;
use serenity::{ use serenity::{
client::bridge::gateway::ShardId, client::bridge::gateway::ShardId,
framework::standard::{ framework::standard::{
@ -306,7 +305,15 @@ async fn random_mute(ctx: &Context, msg: &Message) -> CommandResult {
members[m].add_role(&ctx.http, role).await?; 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(()) Ok(())
} }

View File

@ -1,20 +1,136 @@
use std::sync::Arc;
use tokio::sync::Mutex as TokioMutex;
use crate::data::{GuildOptions, GuildOptionsKey}; use crate::data::{GuildOptions, GuildOptionsKey};
use log::{error, info}; use log::error;
use serenity::{ use serenity::{
client::{Context}, builder::CreateMessage,
client::Context,
framework::standard::{ framework::standard::{
macros::{command, group}, macros::{command, group},
Args, CommandResult, Args, CommandResult,
}, },
model::{channel::Message, guild::PartialMember, id::GuildId, misc::Mentionable}, http::Http,
prelude::*, model::{
Result as SerenityResult, 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<Http>,
handler_lock: Arc<TokioMutex<Call>>,
}
#[async_trait::async_trait]
impl songbird::EventHandler for TrackStartNotifier {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
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] #[group]
#[commands(join, leave, play, stop)] #[commands(join, leave, play, stop, next)]
struct Music; struct Music;
#[command] #[command]
@ -23,7 +139,7 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult {
let guild = match msg.guild(&ctx.cache).await { let guild = match msg.guild(&ctx.cache).await {
Some(guild) => guild, Some(guild) => guild,
None => { None => {
msg.channel_id.say(&ctx.http, "DMs not supported").await; msg.channel_id.say(&ctx.http, "DMs not supported").await?;
return Ok(()); return Ok(());
} }
@ -34,9 +150,9 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult {
} else { } else {
false false
} { } {
msg.channel_id msg.channel_id
.say(&ctx.http, "Error, you cant play music") .say(&ctx.http, "Error, you cant play music")
.await; .await?;
return Ok(()); return Ok(());
} }
@ -50,25 +166,47 @@ async fn join(ctx: &Context, msg: &Message) -> CommandResult {
let connect_to = match channel_id { let connect_to = match channel_id {
Some(channel) => channel, Some(channel) => channel,
None => { None => {
msg.reply(ctx, "Not in a voice channel").await; msg.reply(ctx, "Not in a voice channel").await?;
return Ok(()); return Ok(());
} }
}; };
let manager = songbird::get(ctx).await let manager = songbird::get(ctx)
.expect("Songbird Voice client placed in at initialisation.").clone(); .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 msg.channel_id
.say(&ctx.http, &format!("Joined {}", connect_to.mention())) .say(
.await; &ctx.http,
} else { &format!("Joined channel {}", connect_to.mention()),
)
.await?;
} else {
msg.channel_id msg.channel_id
.say(&ctx.http, "Error joining the channel") .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(()) Ok(())
@ -84,7 +222,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
{ {
Some(id) => id, Some(id) => id,
None => { None => {
msg.channel_id.say(&ctx.http, "DMs not supported").await; msg.channel_id.say(&ctx.http, "DMs not supported").await?;
return Ok(()); return Ok(());
} }
@ -95,26 +233,28 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
} else { } else {
false false
} { } {
msg.channel_id msg.channel_id
.say(&ctx.http, "Error, you cant play music") .say(&ctx.http, "Error, you cant play music")
.await; .await?;
return Ok(()); return Ok(());
} }
let manager = songbird::get(ctx).await let manager = songbird::get(ctx)
.expect("Songbird Voice client placed in at initialisation.").clone(); .await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let has_handler = manager.get(guild_id).is_some(); if let Some(_handler) = manager.get(guild_id) {
if let Err(e) = manager.remove(guild_id).await {
if has_handler { msg.channel_id
if let Err(e) = manager.remove(guild_id).await { .say(&ctx.http, format!("Failed: {:?}", e))
msg.channel_id.say(&ctx.http, format!("Failed: {:?}", e)).await; .await?;
}
msg.channel_id.say(&ctx.http, "Left voice channel").await;
} else {
msg.reply(ctx, "Not in a voice channel").await;
} }
msg.channel_id.say(&ctx.http, "Left voice channel").await?;
} else {
msg.reply(ctx, "Not in a voice channel").await?;
}
Ok(()) Ok(())
} }
@ -124,31 +264,20 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let url = match args.single::<String>() { let url = match args.single::<String>() {
Ok(url) => url, Ok(url) => url,
Err(_) => { Err(_) => {
msg.channel_id
msg.channel_id .say(&ctx.http, "Must provide a URL to a video or audio")
.say(&ctx.http, "Must provide a URL to a video or audio") .await?;
.await;
return Ok(()); 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 { let guild_id = match ctx.cache.guild_channel(msg.channel_id).await {
Some(channel) => channel.guild_id, Some(channel) => channel.guild_id,
None => { None => {
msg.channel_id
msg.channel_id .say(&ctx.http, "Error finding channel info")
.say(&ctx.http, "Error finding channel info") .await?;
.await;
return Ok(()); return Ok(());
} }
@ -159,36 +288,128 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
} else { } else {
false false
} { } {
msg.channel_id msg.channel_id
.say(&ctx.http, "Error, you cant play music") .say(&ctx.http, "Error, you cant play music")
.await; .await?;
return Ok(()); return Ok(());
} }
let manager = songbird::get(ctx).await let manager = songbird::get(ctx)
.expect("Songbird Voice client placed in at initialisation.").clone(); .await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) { if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await; let mut handler = handler_lock.lock().await;
let source = match songbird::ytdl(&url).await { let source = if url.starts_with("http") {
match songbird::ytdl(&url).await {
Ok(source) => source, Ok(source) => source,
Err(why) => { Err(why) => {
error!("Err starting source: {:?}", 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(()); return Ok(());
}, }
}; }
handler.play_source(source);
msg.channel_id.say(&ctx.http, "Playing song").await;
} else { } 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(()) 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 { let guild_id = match ctx.cache.guild_channel(msg.channel_id).await {
Some(channel) => channel.guild_id, Some(channel) => channel.guild_id,
None => { None => {
msg.channel_id
msg.channel_id .say(&ctx.http, "Error finding channel info")
.say(&ctx.http, "Error finding channel info") .await?;
.await;
return Ok(()); return Ok(());
} }
@ -213,26 +433,24 @@ async fn stop(ctx: &Context, msg: &Message) -> CommandResult {
} else { } else {
false false
} { } {
msg.channel_id
msg.channel_id .say(&ctx.http, "Error, you cant play music")
.say(&ctx.http, "Error, you cant play music") .await?;
.await;
return Ok(()); 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 msg.channel_id.say(&ctx.http, "Stopping").await?;
.expect("Songbird Voice client placed in at initialisation.").clone(); } else {
if let Some(handler) = manager.get(guild_id) { msg.reply(ctx, "Not in a voice channel").await?;
}
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;
}
Ok(()) Ok(())
} }

View File

@ -1,3 +1,4 @@
use log::debug;
use serenity::{ use serenity::{
framework::standard::{ framework::standard::{
macros::{command, group}, macros::{command, group},
@ -6,7 +7,6 @@ use serenity::{
model::prelude::*, model::prelude::*,
prelude::*, prelude::*,
}; };
use log::debug;
use std::convert::TryFrom; use std::convert::TryFrom;
#[group] #[group]

View File

@ -2,7 +2,7 @@ use crate::{
api, api,
data::{GuildOptions, GuildOptionsKey}, data::{GuildOptions, GuildOptionsKey},
}; };
use log::{debug, error}; use log::debug;
use rand::Rng; use rand::Rng;
use serenity::{ use serenity::{
framework::standard::{ framework::standard::{
@ -24,7 +24,7 @@ struct Roulette;
#[bucket = "roulette"] #[bucket = "roulette"]
async fn shot(ctx: &Context, msg: &Message, args: Args) -> CommandResult { async fn shot(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let _message = args.message().trim_end(); 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 { if rand::thread_rng().gen_range(0..6) == 0 {
api::send_reply(ctx, &msg, "💥").await?; api::send_reply(ctx, &msg, "💥").await?;
} else { } 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", "Error : You cannot play to the REAL RUSSIAN ROULETTE in this guild",
) )
.await?; .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 { } else {
if rand::thread_rng().gen_range(0..6) == 0 { api::send_reply(&ctx, &msg, format!("Click ! Reloading")).await?;
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?;
}
} }
} }
Ok(()) Ok(())

View File

@ -26,7 +26,7 @@ pub(crate) struct GuildOptions {
pub(crate) mute_id: Option<RoleId>, pub(crate) mute_id: Option<RoleId>,
pub(crate) roulette_options: RouletteOptions, pub(crate) roulette_options: RouletteOptions,
pub(crate) last_ghost_pings: Vec<GhostPing>, pub(crate) last_ghost_pings: Vec<GhostPing>,
pub(crate) mutes : Vec<(UserId, u64)>, pub(crate) mutes: Vec<(UserId, u64)>,
} }
impl GuildOptions { impl GuildOptions {
@ -155,7 +155,7 @@ impl Default for GuildOptions {
guild_id: None, guild_id: None,
mute_id: None, mute_id: None,
last_ghost_pings: Vec::new(), last_ghost_pings: Vec::new(),
mutes: Vec::new() mutes: Vec::new(),
} }
} }
} }