Added youtube playlist support
This commit is contained in:
parent
1abb492dff
commit
e6f60efbe7
|
@ -7,7 +7,7 @@ use serenity::{
|
|||
model::{channel::Embed, guild::Member, Permissions},
|
||||
};
|
||||
|
||||
use crate::data::GuildOptionsKey;
|
||||
use crate::{data::GuildOptionsKey, utils::message::embed_author};
|
||||
|
||||
pub(crate) async fn handle_interaction(ctx: &Context, object: Value) -> CommandResult {
|
||||
let data = object.get("data").ok_or("Failed to get data")?;
|
||||
|
@ -64,7 +64,7 @@ pub(crate) async fn handle_interaction(ctx: &Context, object: Value) -> CommandR
|
|||
goulag(ctx, data, guild_id, &author_member, id, token).await?
|
||||
} else {
|
||||
Response::new_with_embed(4, |e| {
|
||||
super::embed_author(e, Some(&author_member))
|
||||
embed_author(e, Some(&author_member))
|
||||
.title("Error")
|
||||
.description("You don't have the right to do that")
|
||||
.colour((247, 76, 0))
|
||||
|
@ -129,7 +129,7 @@ async fn goulag(
|
|||
member.add_role(&ctx.http, mute_role).await?;
|
||||
|
||||
Response::new_with_embed(4, |e| {
|
||||
super::embed_author(e, Some(author))
|
||||
embed_author(e, Some(author))
|
||||
.title("Mute")
|
||||
.description(format!("{} was muted", member.display_name()))
|
||||
.colour((247, 76, 0))
|
||||
|
@ -142,7 +142,7 @@ async fn goulag(
|
|||
}
|
||||
|
||||
Response::new_with_embed(4, |e| {
|
||||
super::embed_author(e, Some(author))
|
||||
embed_author(e, Some(author))
|
||||
.title("Error")
|
||||
.description("Unknown mute role")
|
||||
.colour((247, 76, 0))
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
use serenity::{builder::CreateEmbed, model::guild::Member};
|
||||
|
||||
pub(crate) mod admin;
|
||||
pub(crate) mod general;
|
||||
pub(crate) mod interaction;
|
||||
|
@ -11,22 +9,3 @@ pub(crate) mod settings;
|
|||
pub(crate) mod music;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
fn embed_author<'a>(e: &'a mut CreateEmbed, author: Option<&Member>) -> &'a mut CreateEmbed {
|
||||
if let Some(author) = author {
|
||||
e.footer(|f| {
|
||||
f.text(if let Some(nick) = &author.nick {
|
||||
nick
|
||||
} else {
|
||||
&author.user.name
|
||||
});
|
||||
|
||||
if let Some(url) = &author.user.avatar_url() {
|
||||
f.icon_url(url);
|
||||
}
|
||||
f
|
||||
})
|
||||
} else {
|
||||
e
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
use serenity::{
|
||||
client::Context,
|
||||
framework::standard::{CommandError, CommandResult},
|
||||
model::{guild::Member, id::ChannelId},
|
||||
};
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::utils::message::embed_response;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum UseVoiceError {
|
||||
NotInGuild,
|
||||
NotInVoiceChannel,
|
||||
NotInSameVoiceChannel,
|
||||
NotEnoughPermission,
|
||||
CommandError(CommandError),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<CommandError> for UseVoiceError {
|
||||
fn from(err: CommandError) -> Self {
|
||||
Self::CommandError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UseVoiceError {}
|
||||
|
||||
impl Display for UseVoiceError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::NotInGuild => write!(f, "DM are not supported"),
|
||||
Self::NotInVoiceChannel => write!(f, "Not in a voice channel"),
|
||||
Self::NotEnoughPermission => write!(f, "You don't have the right to do that"),
|
||||
Self::NotInSameVoiceChannel => {
|
||||
write!(f, "You must on the same voice channel than the bot")
|
||||
}
|
||||
Self::CommandError(e) => write!(f, "Something went wrong : {}", e),
|
||||
Self::Unknown => write!(f, "Something went wrong"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UseVoiceError {
|
||||
pub(crate) async fn send_error_message(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
channel_id: &ChannelId,
|
||||
member: Option<&Member>,
|
||||
) -> CommandResult<()> {
|
||||
channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(m, "Error", &format!("{}", self), member)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -2,27 +2,26 @@ use std::{sync::Arc, usize};
|
|||
|
||||
use tokio::sync::Mutex as TokioMutex;
|
||||
|
||||
use crate::data::GuildOptionsKey;
|
||||
use log::error;
|
||||
use serenity::{
|
||||
builder::CreateMessage,
|
||||
client::Context,
|
||||
framework::standard::{
|
||||
macros::{command, group},
|
||||
Args, CommandError, CommandResult,
|
||||
Args, CommandResult,
|
||||
},
|
||||
http::Http,
|
||||
model::{
|
||||
channel::Message,
|
||||
guild::{Member, PartialMember},
|
||||
id::{ChannelId, GuildId},
|
||||
misc::Mentionable,
|
||||
permissions::Permissions,
|
||||
},
|
||||
model::{channel::Message, id::ChannelId, misc::Mentionable, permissions::Permissions},
|
||||
};
|
||||
use songbird::{input::Metadata, Call, Event, EventContext, TrackEvent};
|
||||
use songbird::{Call, Event, EventContext, TrackEvent};
|
||||
|
||||
use super::embed_author;
|
||||
mod error;
|
||||
mod utils;
|
||||
mod youtube;
|
||||
|
||||
use crate::utils::{message::embed_response, permissions::has_permission};
|
||||
use utils::embed_song;
|
||||
|
||||
use self::utils::{can_use_voice_command, is_mute};
|
||||
|
||||
struct TrackStartNotifier {
|
||||
chan_id: ChannelId,
|
||||
|
@ -62,94 +61,6 @@ impl songbird::EventHandler for TrackStartNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
fn embed_song(msg: &mut CreateMessage, title: &str, metadata: &Metadata, author: Option<&Member>) {
|
||||
msg.embed(|e| {
|
||||
e.title(title);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
embed_author(e, author).colour((247, 76, 0))
|
||||
});
|
||||
}
|
||||
|
||||
fn embed_queued(msg: &mut CreateMessage, metadata: &Metadata, author: Option<&Member>) {
|
||||
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);
|
||||
}
|
||||
|
||||
embed_author(e, author).colour((247, 76, 0))
|
||||
});
|
||||
}
|
||||
|
||||
fn embed_response<'a, 'b>(
|
||||
msg: &'a mut CreateMessage<'b>,
|
||||
title: &str,
|
||||
content: &str,
|
||||
author: Option<&Member>,
|
||||
) -> &'a mut CreateMessage<'b> {
|
||||
msg.embed(|e| {
|
||||
e.title(title).description(content);
|
||||
embed_author(e, author).colour((247, 76, 0))
|
||||
})
|
||||
}
|
||||
|
||||
#[group]
|
||||
#[commands(join, leave, play, stop, next, pause, resume, remove, queue)]
|
||||
struct Music;
|
||||
|
@ -285,7 +196,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
|
|||
let handler = handler.lock().await;
|
||||
match can_use_voice_command(ctx, &handler, &msg, &member).await {
|
||||
Ok(()) => {
|
||||
std::mem::drop(handler);
|
||||
std::mem::drop(handler); // Drop mutex
|
||||
if let Err(e) = manager.remove(guild_id).await {
|
||||
log::error!("Failed to leave : {}", e);
|
||||
msg.channel_id
|
||||
|
@ -371,23 +282,106 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
match can_use_voice_command(ctx, &handler, &msg, &member).await {
|
||||
Ok(()) => {
|
||||
let source = if url.starts_with("http") {
|
||||
match songbird::ytdl(&url).await {
|
||||
Ok(source) => source,
|
||||
Err(why) => {
|
||||
error!("Err starting source: {:?}", why);
|
||||
if url.contains("youtube.com") && url.contains("list=") {
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(
|
||||
m,
|
||||
"Loading the playlist",
|
||||
"This could take some time",
|
||||
Some(&member),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
let list = match youtube::get_list_of_urls(&url).await {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
error!("Failed to load playlist : {}", e);
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(
|
||||
m,
|
||||
"Error",
|
||||
"Failed to load the playlist",
|
||||
Some(&member),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if clean {
|
||||
handler.queue().stop();
|
||||
}
|
||||
|
||||
for music in list {
|
||||
let s = match songbird::ytdl(&music).await {
|
||||
Ok(source) => source,
|
||||
Err(why) => {
|
||||
error!("Err starting source: {:?}", why);
|
||||
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(
|
||||
m,
|
||||
"Error",
|
||||
"Failed to load the song",
|
||||
Some(&member),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
handler.enqueue_source(s);
|
||||
}
|
||||
|
||||
let track = handler.queue().current();
|
||||
|
||||
if let Some(track) = &track {
|
||||
let author = msg.member(&ctx).await?;
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_song(m, "Now playing", track.metadata(), Some(&author))
|
||||
})
|
||||
.await?;
|
||||
} else {
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(
|
||||
m,
|
||||
"Error",
|
||||
"Failed to load the song",
|
||||
"Failed to load the playlist",
|
||||
Some(&member),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
match songbird::ytdl(&url).await {
|
||||
Ok(source) => source,
|
||||
Err(why) => {
|
||||
error!("Err starting source: {:?}", why);
|
||||
|
||||
return Ok(());
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_response(
|
||||
m,
|
||||
"Error",
|
||||
"Failed to load the song",
|
||||
Some(&member),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -422,12 +416,16 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
let author = msg.member(&ctx).await?;
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
if handler.queue().len() == 1 {
|
||||
embed_song(m, "Now playing", meta, Some(&author));
|
||||
} else {
|
||||
embed_queued(m, meta, Some(&author));
|
||||
}
|
||||
m
|
||||
embed_song(
|
||||
m,
|
||||
if handler.queue().len() == 1 {
|
||||
"Now playing"
|
||||
} else {
|
||||
"Queued"
|
||||
},
|
||||
meta,
|
||||
Some(&author),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
@ -692,8 +690,7 @@ async fn remove(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
handler.queue().skip()?;
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_song(m, "Removed", track.metadata(), Some(&member));
|
||||
m
|
||||
embed_song(m, "Removed", track.metadata(), Some(&member))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
@ -710,8 +707,7 @@ async fn remove(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
|||
Some(q) => {
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
embed_song(m, "Removed", q.metadata(), Some(&member));
|
||||
m
|
||||
embed_song(m, "Removed", q.metadata(), Some(&member))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
@ -811,124 +807,3 @@ async fn queue(ctx: &Context, msg: &Message) -> CommandResult<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn is_mute(ctx: &Context, member: &PartialMember, guild_id: GuildId) -> CommandResult<bool> {
|
||||
let data = ctx.data.read().await;
|
||||
|
||||
let data = data
|
||||
.get::<GuildOptionsKey>()
|
||||
.expect("Failed to get guild cache");
|
||||
|
||||
if let Some(mute_role) = data.get(&guild_id).and_then(|o| o.get_mute_role()) {
|
||||
Ok(member.roles.contains(&mute_role))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum UseVoiceError {
|
||||
NotInGuild,
|
||||
NotInVoiceChannel,
|
||||
NotInSameVoiceChannel,
|
||||
NotEnoughPermission,
|
||||
CommandError(CommandError),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl From<CommandError> for UseVoiceError {
|
||||
fn from(err: CommandError) -> Self {
|
||||
Self::CommandError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl UseVoiceError {
|
||||
async fn send_error_message(
|
||||
&self,
|
||||
ctx: &Context,
|
||||
channel_id: &ChannelId,
|
||||
member: Option<&Member>,
|
||||
) -> CommandResult<()> {
|
||||
let msg = match self {
|
||||
Self::NotInGuild => "DM are not supported",
|
||||
Self::NotInVoiceChannel => "Not in a voice channel",
|
||||
Self::NotEnoughPermission => "You don't have the right to do that",
|
||||
Self::NotInSameVoiceChannel => "You must on the same voice channel than the bot",
|
||||
Self::CommandError(_) => "Something went wrong",
|
||||
Self::Unknown => "Something went wrong",
|
||||
};
|
||||
|
||||
channel_id
|
||||
.send_message(&ctx.http, |m| embed_response(m, "Error", msg, member))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn can_use_voice_command(
|
||||
ctx: &Context,
|
||||
call: &Call,
|
||||
msg: &Message,
|
||||
member: &Member,
|
||||
) -> Result<(), UseVoiceError> {
|
||||
if has_permission(ctx, member, &[Permissions::MANAGE_GUILD]).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let guild = match msg.guild(&ctx.cache).await {
|
||||
Some(guild) => guild,
|
||||
None => return Err(UseVoiceError::NotInGuild),
|
||||
};
|
||||
|
||||
let channel_id = match guild.voice_states.get(&msg.author.id) {
|
||||
Some(voice_state) => match voice_state.channel_id {
|
||||
Some(c) => c,
|
||||
None => return Err(UseVoiceError::Unknown),
|
||||
},
|
||||
None => {
|
||||
return Err(UseVoiceError::NotInVoiceChannel);
|
||||
}
|
||||
};
|
||||
|
||||
let bot_channel_id = match call.current_channel() {
|
||||
Some(c) => c,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
if bot_channel_id.0 != channel_id.0 {
|
||||
Err(UseVoiceError::NotInSameVoiceChannel)
|
||||
} else if let Some(member) = &msg.member {
|
||||
if is_mute(ctx, member, guild.id).await? {
|
||||
Err(UseVoiceError::NotEnoughPermission)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(UseVoiceError::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
async fn has_permission(
|
||||
ctx: &Context,
|
||||
member: &Member,
|
||||
permissions: &[Permissions],
|
||||
) -> CommandResult<bool> {
|
||||
let p = member.permissions(ctx).await?;
|
||||
for perm in permissions {
|
||||
if p.contains(*perm) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
let roles = member.roles(ctx).await.unwrap();
|
||||
for role in roles {
|
||||
for perm in permissions {
|
||||
if role.has_permissions(*perm, false) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
use serenity::{
|
||||
builder::CreateMessage,
|
||||
client::Context,
|
||||
framework::standard::CommandResult,
|
||||
model::{
|
||||
channel::Message,
|
||||
guild::{Member, PartialMember},
|
||||
id::GuildId,
|
||||
Permissions,
|
||||
},
|
||||
};
|
||||
use songbird::{input::Metadata, Call};
|
||||
|
||||
use crate::{
|
||||
data::GuildOptionsKey,
|
||||
utils::{message::embed_author, permissions::has_permission},
|
||||
};
|
||||
|
||||
use super::error::UseVoiceError;
|
||||
|
||||
pub(crate) fn embed_song<'a, 'b>(
|
||||
msg: &'a mut CreateMessage<'b>,
|
||||
title: &str,
|
||||
metadata: &Metadata,
|
||||
author: Option<&Member>,
|
||||
) -> &'a mut CreateMessage<'b> {
|
||||
msg.embed(|e| {
|
||||
e.title(title);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
embed_author(e, author).colour((247, 76, 0))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn is_mute(
|
||||
ctx: &Context,
|
||||
member: &PartialMember,
|
||||
guild_id: GuildId,
|
||||
) -> CommandResult<bool> {
|
||||
let data = ctx.data.read().await;
|
||||
|
||||
let data = data
|
||||
.get::<GuildOptionsKey>()
|
||||
.expect("Failed to get guild cache");
|
||||
|
||||
if let Some(mute_role) = data.get(&guild_id).and_then(|o| o.get_mute_role()) {
|
||||
Ok(member.roles.contains(&mute_role))
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn can_use_voice_command(
|
||||
ctx: &Context,
|
||||
call: &Call,
|
||||
msg: &Message,
|
||||
member: &Member,
|
||||
) -> Result<(), UseVoiceError> {
|
||||
if has_permission(ctx, member, &[Permissions::MANAGE_GUILD]).await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let guild = match msg.guild(&ctx.cache).await {
|
||||
Some(guild) => guild,
|
||||
None => return Err(UseVoiceError::NotInGuild),
|
||||
};
|
||||
|
||||
let channel_id = match guild.voice_states.get(&msg.author.id) {
|
||||
Some(voice_state) => match voice_state.channel_id {
|
||||
Some(c) => c,
|
||||
None => return Err(UseVoiceError::Unknown),
|
||||
},
|
||||
None => {
|
||||
return Err(UseVoiceError::NotInVoiceChannel);
|
||||
}
|
||||
};
|
||||
|
||||
let bot_channel_id = match call.current_channel() {
|
||||
Some(c) => c,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
if bot_channel_id.0 != channel_id.0 {
|
||||
Err(UseVoiceError::NotInSameVoiceChannel)
|
||||
} else if let Some(member) = &msg.member {
|
||||
if is_mute(ctx, member, guild.id).await? {
|
||||
Err(UseVoiceError::NotEnoughPermission)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
Err(UseVoiceError::Unknown)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
use serde::Deserialize;
|
||||
use tokio::{
|
||||
io::{Error, ErrorKind, Result},
|
||||
process::Command,
|
||||
};
|
||||
pub(crate) async fn get_list_of_urls(url: &str) -> Result<Vec<String>> {
|
||||
let output = Command::new("youtube-dl")
|
||||
.args(&["-j", "--flat-playlist", &url])
|
||||
.output()
|
||||
.await?;
|
||||
|
||||
if !output.status.success() {
|
||||
Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
String::from_utf8_lossy(&output.stderr),
|
||||
))
|
||||
} else {
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
let mut json_output = vec![];
|
||||
for line in output.lines() {
|
||||
let json: YtdlResponse = serde_json::from_str(line)?;
|
||||
json_output.push(String::from("https://youtube.com/watch?v=") + &json.url);
|
||||
}
|
||||
|
||||
Ok(json_output)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct YtdlResponse {
|
||||
url: String,
|
||||
}
|
|
@ -41,6 +41,7 @@ mod commands;
|
|||
mod config;
|
||||
mod data;
|
||||
mod presence;
|
||||
mod utils;
|
||||
|
||||
const MINIMUM_MENTIONS: usize = 10;
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
use serenity::{
|
||||
builder::{CreateEmbed, CreateMessage},
|
||||
model::guild::Member,
|
||||
};
|
||||
|
||||
pub(crate) fn embed_response<'a, 'b>(
|
||||
msg: &'a mut CreateMessage<'b>,
|
||||
title: &str,
|
||||
content: &str,
|
||||
author: Option<&Member>,
|
||||
) -> &'a mut CreateMessage<'b> {
|
||||
msg.embed(|e| {
|
||||
e.title(title).description(content);
|
||||
embed_author(e, author).colour((247, 76, 0))
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn embed_author<'a>(
|
||||
e: &'a mut CreateEmbed,
|
||||
author: Option<&Member>,
|
||||
) -> &'a mut CreateEmbed {
|
||||
if let Some(author) = author {
|
||||
e.footer(|f| {
|
||||
f.text(if let Some(nick) = &author.nick {
|
||||
nick
|
||||
} else {
|
||||
&author.user.name
|
||||
});
|
||||
|
||||
if let Some(url) = &author.user.avatar_url() {
|
||||
f.icon_url(url);
|
||||
}
|
||||
f
|
||||
})
|
||||
} else {
|
||||
e
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
pub(crate) mod message;
|
||||
pub(crate) mod permissions;
|
|
@ -0,0 +1,29 @@
|
|||
use serenity::{
|
||||
client::Context,
|
||||
framework::standard::CommandResult,
|
||||
model::{guild::Member, Permissions},
|
||||
};
|
||||
|
||||
pub(crate) async fn has_permission(
|
||||
ctx: &Context,
|
||||
member: &Member,
|
||||
permissions: &[Permissions],
|
||||
) -> CommandResult<bool> {
|
||||
let p = member.permissions(ctx).await?;
|
||||
for perm in permissions {
|
||||
if p.contains(*perm) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
let roles = member.roles(ctx).await.unwrap();
|
||||
for role in roles {
|
||||
for perm in permissions {
|
||||
if role.has_permissions(*perm, false) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
Loading…
Reference in New Issue