Compare commits

...

3 commits

16 changed files with 683 additions and 591 deletions

645
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -9,11 +9,12 @@ edition = "2021"
[dependencies]
serenity = "0.12"
tokio = { version = "1.36", features = ["macros", "rt-multi-thread"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
confy = "0.6.0"
songbird = "0.4"
songbird = { version = "0.4", features = ["gateway", "serenity", "native", "driver"], default-features = false }
lavalink-rs = { version = "0.12", features = ["songbird", "serenity", "native-tls"], default-features = false }
chrono = "0.4"
reqwest = "0.11"
symphonia = "0.5"

View file

@ -1,9 +1,12 @@
use crate::music::music_queue;
use std::time::Duration;
use crate::music::preview::Preview;
use crate::util::embed::Embed;
use serenity::all::{CommandInteraction, Context};
use lavalink_rs::client::LavalinkClient;
use serenity::all::{CommandInteraction, Context, GuildId};
use serenity::builder::{CreateCommand, CreateEmbed};
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClient) -> CreateEmbed {
let username = command.user.name.as_str();
let guild_id = match &command.guild_id {
@ -17,47 +20,27 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
}
};
let now_plaing = match music_queue::get_now_playing(&guild_id).await {
Some(ytdl) => ytdl,
None => {
return Embed::create_error_respose(
username,
"Not playing",
"I'm not playing anything!",
);
}
let Some(player_context) = llc.get_player_context(guild_id.get()) else {
return Embed::create_error_respose(username, "Not playing", "There is no player context and therefore there is nothing playing.");
};
let now_handle = match music_queue::get_now_playing_track_handle(&guild_id).await {
Some(handle) => handle,
None => {
return Embed::create_error_respose(
username,
"Cannot get TrackHandle",
"The TrackHandle is empty.",
);
}
let Ok(player) = player_context.get_player().await else {
return Embed::create_error_respose(username, "Can't get player", "Cannot get player from player context.");
};
let manager = songbird::get(ctx)
.await
.expect("Cannot get Songbird")
.clone();
let _ = match manager.get(*guild_id) {
Some(handler) => handler,
None => {
return Embed::create_error_respose(
username,
"Error",
"Error while getting the audio handler.",
);
}
if let Some(npt) = player.track {
let npti = npt.info;
let preview_data = Preview {
title: npti.title,
artist: Some(npti.author.to_string()),
duration: Some(npti.length),
thumbnail: npti.artwork_url,
link: npti.uri
};
let position = Duration::from_millis(player_context.get_player().await.expect("Can't get player").state.position);
let position = now_handle.get_info().await.unwrap().position;
Embed::create_yt_playing(now_plaing, username, "Currently playing")
return Embed::create_playing(preview_data, username, "Now playing")
.await
.field(
"Position",
@ -66,8 +49,11 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
position.as_secs() / 60,
position.as_secs() % 60
),
true,
)
true
);
}
return Embed::create_error_respose(username, "Not playing", "I'm currently not playing anything.");
}
pub fn register() -> CreateCommand {

View file

@ -1,12 +1,13 @@
use crate::music::music_manager;
use lavalink_rs::client::LavalinkClient;
use serenity::all::{CommandDataOptionValue, CommandInteraction, Context};
use serenity::builder::{CreateCommand, CreateCommandOption, CreateEmbed};
use serenity::model::application::CommandOptionType;
use crate::util::embed::Embed;
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClient) -> CreateEmbed {
let username = command.user.name.as_str();
let options = &command.data.options;
@ -39,6 +40,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
music_manager::attempt_to_queue_song(
ctx,
llc,
guild_id,
&command.user.id,
&command.user.name,

View file

@ -1,9 +1,10 @@
use crate::music::music_manager;
use crate::util::embed::Embed;
use lavalink_rs::client::LavalinkClient;
use serenity::all::{CommandInteraction, Context};
use serenity::builder::{CreateCommand, CreateEmbed};
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClient) -> CreateEmbed {
let username = command.user.name.as_str();
let guild_id = match &command.guild_id {
@ -17,7 +18,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
}
};
music_manager::attempt_to_skip_current_song(ctx, guild_id, &command.user.id, &command.user.name)
music_manager::attempt_to_skip_current_song(ctx, llc, guild_id, &command.user.id, &command.user.name)
.await
}

View file

@ -1,9 +1,10 @@
use crate::music::music_manager;
use crate::util::embed::Embed;
use lavalink_rs::client::LavalinkClient;
use serenity::all::{CommandInteraction, Context};
use serenity::builder::{CreateCommand, CreateEmbed};
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClient) -> CreateEmbed {
let username = command.user.name.as_str();
let guild_id = match &command.guild_id {
@ -17,7 +18,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
}
};
music_manager::attempt_to_stop(ctx, guild_id, &command.user.id, &command.user.name).await
music_manager::attempt_to_stop(ctx, llc, guild_id, &command.user.id, &command.user.name).await
}
pub fn register() -> CreateCommand {

View file

@ -0,0 +1,30 @@
use lavalink_rs::{client::LavalinkClient, model::events};
use serenity::all::{standard::macros::hook, Sticker};
#[hook]
pub async fn ready_event(client: LavalinkClient, _session_id: String, event: &events::Ready) {
client.delete_all_player_contexts().await.unwrap();
println!("Lavalink is ready for Freddy:: {:?}", event);
}
#[hook]
pub async fn track_start(_client: LavalinkClient, _session_id: String, _event: &events::TrackStart) {
}
#[hook]
pub async fn track_end_event(_client: LavalinkClient, _session_id: String, _event: &events::TrackEnd) {
}
#[hook]
pub async fn track_exception(_client: LavalinkClient, _session_id: String, _event: &events::TrackException) {
}
#[hook]
pub async fn track_stuck(_client: LavalinkClient, _session_id: String, _event: &events::TrackStuck) {
}

1
src/events/mod.rs Normal file
View file

@ -0,0 +1 @@
pub mod lavalink_event_handler;

View file

@ -1,7 +1,15 @@
mod commands;
mod music;
mod util;
mod events;
use std::ops::Deref;
use lavalink_rs::client::LavalinkClient;
use lavalink_rs::model::client::NodeDistributionStrategy;
use lavalink_rs::model::events::Events;
use lavalink_rs::model::UserId;
use lavalink_rs::node::NodeBuilder;
use serenity::all::{
CommandInteraction, CreateInteractionResponseFollowup, OnlineStatus, VoiceState,
};
@ -18,19 +26,15 @@ use util::{config, embed::Embed, user_util};
// The voice client can be retrieved in any command using `songbird::get(ctx).await`.
use songbird::SerenityInit;
// YtDl requests need an HTTP client to operate -- we'll create and store our own.
use reqwest::Client as HttpClient;
struct HttpKey;
impl TypeMapKey for HttpKey {
type Value = HttpClient;
}
use crate::events::lavalink_event_handler;
// lazy static stuff. I don't like it, but it has to be here, bc it has to be @ root
#[macro_use]
extern crate lazy_static;
struct Handler;
struct Handler {
pub llc: LavalinkClient
}
#[async_trait]
impl EventHandler for Handler {
@ -40,10 +44,10 @@ impl EventHandler for Handler {
let content = Some(match command.data.name.as_str() {
"info" => commands::info::run(&ctx, &command).await,
"play" => commands::play::run(&ctx, &command).await,
"stop" => commands::stop::run(&ctx, &command).await,
"skip" => commands::skip::run(&ctx, &command).await,
"nowplaying" => commands::now_playing::run(&ctx, &command).await,
"play" => commands::play::run(&ctx, &command, &self.llc).await,
"stop" => commands::stop::run(&ctx, &command, &self.llc).await,
"skip" => commands::skip::run(&ctx, &command, &self.llc).await,
"nowplaying" => commands::now_playing::run(&ctx, &command, &self.llc).await,
_ => respond_with_error(&ctx, &command).await,
});
@ -120,13 +124,35 @@ async fn main() {
let status = OnlineStatus::DoNotDisturb;
let activity = ActivityData::streaming("music", "https://twitch.tv/moonleaytv").unwrap();
let event = Events {
ready: Some(lavalink_event_handler::ready_event),
// track_start: Some(lavalink_event_handler::track_start),
// track_end: Some(lavalink_event_handler::track_end_event),
// track_exception: Some(lavalink_event_handler::track_exception),
// track_stuck: Some(lavalink_event_handler::track_stuck),
..Default::default()
};
let node_builder = NodeBuilder {
hostname: config.lavalink_address,
password: config.lavalink_password,
user_id: UserId(config.user_id),
is_ssl: false,
events: event.clone(),
session_id: None
};
let lavalink_client = LavalinkClient::new(event, vec![node_builder], NodeDistributionStrategy::round_robin()).await;
// Build the client
let mut client = Client::builder(config.discord_token, GatewayIntents::empty())
.event_handler(Handler)
.event_handler(Handler{
llc: lavalink_client
})
.status(status)
.activity(activity)
.register_songbird()
.type_map_insert::<HttpKey>(HttpClient::new())
.intents(GatewayIntents::all())
.await
.expect("Error creating client");

View file

@ -1,3 +1,2 @@
pub mod music_events;
pub mod music_manager;
pub mod music_queue;
pub mod preview;

View file

@ -1,56 +0,0 @@
use crate::music::{music_manager, music_queue};
use serenity::all::{ChannelId, GuildId, Http};
use serenity::async_trait;
use songbird::input::Compose;
use songbird::{Event, EventContext, EventHandler};
use std::sync::Arc;
pub struct TrackEndNotifier {
pub guild_id: GuildId,
pub channel_id: ChannelId,
pub http: Arc<Http>,
pub cmdctx: Arc<serenity::client::Context>,
}
#[async_trait]
impl EventHandler for TrackEndNotifier {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
// TODO: Does this need to be unsafe?
if let EventContext::Track(..) = ctx {
println!("The track ended!");
if music_queue::is_empty(&self.guild_id).await {
// No more songs in queue, exit the vc
let stopped = match music_manager::leave(&self.cmdctx, &self.guild_id).await {
Ok(stopped) => stopped,
Err(e) => {
println!("Cannot stop: {:?}", e);
return None;
}
};
if stopped {
music_queue::delete_queue(&self.guild_id).await;
println!("Stopped playing successfully.");
} else {
println!("Failed to stop playing.");
}
return None;
}
let mut head = match music_queue::next(&self.guild_id).await {
Some(head) => head,
None => {
println!("Cannot get head of queue");
return None;
}
};
println!(
"Now playing: {}",
head.aux_metadata().await.unwrap().title.unwrap()
);
music_manager::play_song(&self.cmdctx, &self.guild_id, &head).await;
}
None
}
}

View file

@ -1,17 +1,20 @@
use crate::music::{music_events, music_queue};
use crate::util::embed::Embed;
use crate::util::user_util;
use crate::util::user_util::get_vc_id;
use crate::HttpKey;
use serenity::all::{Context, CreateEmbed, GuildId, UserId};
use crate::music::preview::Preview;
use lavalink_rs::client::LavalinkClient;
use lavalink_rs::model::search::SearchEngines;
use lavalink_rs::model::track::TrackLoadData;
use lavalink_rs::model::ChannelId;
use lavalink_rs::player_context::TrackInQueue;
use serenity::all::{Context, CreateEmbed, GuildId, Http, UserId};
use songbird::error::JoinError;
use songbird::input::YoutubeDl;
use songbird::{Event, TrackEvent};
use std::sync::Arc;
/// Either queues the song, or start playing it instantly, depending on if there is already a song playing
pub async fn attempt_to_queue_song(
ctx: &Context,
llc: &LavalinkClient,
guild_id: &GuildId,
user_id: &UserId,
username: &str,
@ -32,21 +35,38 @@ pub async fn attempt_to_queue_song(
}
};
let manager: &Arc<songbird::Songbird> = &songbird::get(ctx) // TODO match
let songbird: &Arc<songbird::Songbird> = &songbird::get(ctx) // TODO match
.await
.expect("Cannot get Songbird.")
.clone();
let self_channel = user_util::get_self_vc_id(ctx, guild_id).await;
if !user_util::is_self_connected_to_vc(ctx, guild_id).await {
if !user_util::is_self_connected_to_vc(ctx, &guild_id).await {
// self is connected to vc, check if user is in same vc
if self_channel.is_none() {
// Connect to VC
manager // TODO match
.join(*guild_id, connect_to)
.await
.expect("Cannot connect>...");
let handler = songbird.join_gateway(GuildId::new(guild_id.get()), connect_to).await;
//
match handler {
Ok((connection_info, _)) => {
let Ok(_) = llc
.create_player_context_with_data::<(ChannelId, std::sync::Arc<Http>)>(
guild_id.get(),
connection_info,
std::sync::Arc::new((
ChannelId::from(connect_to.get()),
ctx.http.clone(),
)),
).await else {
return Embed::create_error_respose(username, "error_title", "error_desc");
};
}
Err(_) => {
return Embed::create_error_respose(username, "Cannot join", "Could not join the channel.");
}
}
}
} else {
let self_channel = self_channel.expect("Cannot get self channel"); // TODO: match
@ -63,87 +83,91 @@ pub async fn attempt_to_queue_song(
// Get query
let do_search = !query.starts_with("http");
let http_client = {
let data = ctx.data.read().await;
data.get::<HttpKey>()
.cloned()
.expect("Guaranteed to exist in the typemap.")
let search_query = if do_search {
match SearchEngines::YouTube.to_query(&query) {
Ok(x) => x,
Err(_) => {
return Embed::create_error_respose(username, "Cannot generate query", "Could not generate a seach query..");
}
}
} else { // Allow piped links
query.to_string().replace("https://piped.moonleay.net/", "https://youtube.com/")
};
// Create source
let src = if do_search {
YoutubeDl::new_search(http_client, query.to_string())
} else {
YoutubeDl::new(http_client, query.to_string())
};
let loaded_tracks = llc.load_tracks(guild_id.get(), &search_query).await.expect("Fuck.");
let currently_playing = music_queue::get_now_playing(guild_id).await;
music_queue::add_to_queue(guild_id, src.clone()).await;
if currently_playing.is_some() {
// Add to queue
return Embed::create_yt_playing(src, username, "Added to queue").await;
let mut playlist_info = None;
let tracks: Vec<TrackInQueue> = match loaded_tracks.data {
Some(TrackLoadData::Track(x)) => vec![x.into()],
Some(TrackLoadData::Search(x)) => vec![x[0].clone().into()],
Some(TrackLoadData::Playlist(x)) => {
playlist_info = Some(x.info);
x.tracks.iter().map(|x| x.clone().into()).collect()
}
let _query = music_queue::next(guild_id)
.await
.expect("Cannot get head of queue");
music_queue::set_now_playing(guild_id, Some(src.clone())).await;
let handler_lock = match manager.get(*guild_id) {
Some(handler) => handler,
None => {
_ => {
return Embed::create_error_respose(
username,
"Error",
"Cannot get handler of this guild.",
);
"500: Server failure",
"Something went wrong when loading the track...");
}
};
// Start playing
let mut handler = handler_lock.lock().await;
let track_handle = handler.play_input(src.clone().into()); // TODO: Add event handlers
music_queue::set_now_playing_track_handle(guild_id, Some(track_handle)).await;
handler.add_global_event(
Event::Track(TrackEvent::End),
music_events::TrackEndNotifier {
guild_id: *guild_id,
channel_id: connect_to,
http: Arc::clone(&ctx.http),
cmdctx: Arc::new(ctx.clone()),
},
);
let mut response_title = "";
let mut preview_data = None;
Embed::create_yt_playing(src, username, "Now playing").await
}
if let Some(info) = playlist_info {
response_title = "Added playlist to queue";
preview_data = Some(Preview {
title: info.name.to_string(),
artist: None,
duration: None,
thumbnail: None,
link: None,
});
} else {
let track = tracks[0].track.clone();
/// Play the provided song
pub async fn play_song(ctx: &Context, guild_id: &GuildId, target: &YoutubeDl) {
let manager = &songbird::get(ctx) // TODO match
.await
.expect("Cannot get Songbird.")
.clone();
if !user_util::is_self_connected_to_vc(ctx, guild_id).await {
println!("Bot is not connected to a VC, cannot play.");
return;
response_title = "Added to queue";
preview_data = Some(Preview {
title: track.info.title,
artist: Some(track.info.author),
duration: Some(track.info.length),
thumbnail: track.info.artwork_url,
link: track.info.uri
});
}
music_queue::set_now_playing(guild_id, Some(target.clone())).await;
let handler_lock = match manager.get(*guild_id) {
Some(handler) => handler,
None => return,
let player = match llc.get_player_context(guild_id.get()) {
Some(player) => player,
None => {
return Embed::create_error_respose(username, "Cannot get player", "Could not get player context.");
}
};
let mut handler = handler_lock.lock().await;
handler.stop(); // Stop playing the current song
let track_handle = handler.play_input(target.clone().into()); // TODO: Add event handlers
music_queue::set_now_playing_track_handle(guild_id, Some(track_handle)).await;
// let q = player.get_queue();
// q.append(tracks.into());
let Ok(_) = player.play_now(&tracks[0].track).await else {
return Embed::create_error_respose(username, "Error playing", "Could not play track.");
};
// if let Ok(player_data) = player.get_player().await {
// if player_data.track.is_none() && q.get_track(0).await.is_ok_and(|x| x.is_some()) {
// player.skip();
// }
// }
Embed::create_playing(preview_data.unwrap(), username, &response_title).await
}
/// Attempt to skip the song, which is currently playing. Do nothing if there is no next song
pub async fn attempt_to_skip_current_song(
ctx: &Context,
llc: &LavalinkClient,
guild_id: &GuildId,
user_id: &UserId,
username: &str,
@ -156,59 +180,82 @@ pub async fn attempt_to_skip_current_song(
);
}
let connect_to = match get_vc_id(ctx, guild_id, user_id).await {
let user_channel = match get_vc_id(ctx, guild_id, user_id).await {
Some(channel_id) => channel_id,
None => {
return Embed::create_error_respose(username, "Error", "Cannot find channel_id.");
}
};
let manager: &Arc<songbird::Songbird> = &songbird::get(ctx) // TODO match
.await
.expect("Cannot get Songbird.")
.clone();
let self_channel = user_util::get_self_vc_id(ctx, guild_id).await;
if !user_util::is_self_connected_to_vc(ctx, guild_id).await {
// self is connected to vc, check if user is in same vc
// Self is not connected to vc, cannot skip.
if self_channel.is_none() {
// Connect to VC
manager // TODO match
.join(*guild_id, connect_to)
.await
.expect("Cannot connect>...");
}
return Embed::create_error_respose(username, "Not connected", "I am not connected and I am not playing anything, therefore you cannot skip.");
} else {
let self_channel = self_channel.expect("Cannot get self channel"); // TODO: match
let self_channel = self_channel.expect("Cannot get self channel");
// Check if user is in the same VC as the bot
if self_channel != connect_to {
if self_channel != user_channel {
return Embed::create_error_respose(
username,
"You are not in my VC",
"You have to be in my VC in order to controll the music.",
"You have to be in my VC in order to control the music.",
);
}
}
let head = music_queue::next(guild_id).await; // TODO match
if head.is_none() {
return Embed::create_error_respose(
username,
"Cannot find a song to play",
"The queue is empty.",
);
}
let head = head.unwrap();
play_song(ctx, guild_id, &head).await;
let Some(player_context) = llc.get_player_context(guild_id.get()) else {
return Embed::create_error_respose(username, "Not playing", "There is no player context and therefore there is nothing playing.");
};
let Ok(player) = player_context.get_player().await else {
return Embed::create_error_respose(username, "Can't get player", "Cannot get player from player context.");
};
if let Some(_) = player.track {
let Ok(_) = player_context.skip() else {
return Embed::create_error_respose(username, "Cannot skip", "Could not skip track.");
};
let ct = match player_context.get_player().await.expect("Can't get player").track {
Some(data) => data.info,
None => { // Disconnect
let songbird = songbird::get(ctx).await.unwrap().clone();
let Ok(_) = llc.delete_player(guild_id.get()).await else {
return Embed::create_error_respose(username, "Cannot delete player", "Could not delete player.");
};
if songbird.get(GuildId::new(guild_id.get())).is_some() {
let Ok(_) = songbird.remove(GuildId::new(guild_id.get())).await else {
return Embed::create_error_respose(username, "Cannot remove ref", "Cannot remove songbird ref.");
};
}
return Embed::create_error_respose(username, "No track to skip to", "Therefore I am leaving the channel.")
}
};
let preview_data = Preview {
title: ct.title,
artist: Some(ct.author.to_string()),
duration: Some(ct.length),
thumbnail: ct.artwork_url,
link: ct.uri
};
return Embed::create_playing(preview_data, username, "Skipped; Now playing").await;
}
return Embed::create_error_respose(username, "Not playing", "I'm currently not playing anything.");
Embed::create_yt_playing(head, username, "Song skipped; Now playing").await
}
/// Try to clear the queue and stop playing. Also leave the vc
pub async fn attempt_to_stop(
ctx: &Context,
llc: &LavalinkClient,
guild_id: &GuildId,
user_id: &UserId,
username: &str,
@ -237,7 +284,10 @@ pub async fn attempt_to_stop(
);
}
let stopped = match leave(ctx, guild_id).await {
let player = llc.get_player_context(guild_id.get()).expect("Can't get player context.");
player.get_queue().clear();
let stopped = match leave(ctx, llc, guild_id).await {
Ok(stopped) => stopped,
Err(e) => {
println!("Error while stopping: {:?}", e);
@ -256,26 +306,28 @@ pub async fn attempt_to_stop(
"I am not connected.\nI cant stop doing something, when I'm not doing it.",
)
} else {
music_queue::delete_queue(guild_id).await; // Clear queue
Embed::create_success_response(username, "I stopped and left", "Just like you girlfriend.")
}
}
/// Make the bot leave the voice channel. Returns Ok(true) if bot was connected, returns Ok(false) if bot was not connected. Returns Err if something went wrong.
pub async fn leave(ctx: &Context, guild_id: &GuildId) -> Result<bool, JoinError> {
let manager = songbird::get(ctx)
.await
.expect("Cannot get Songbird")
.clone();
pub async fn leave(ctx: &Context, llc: &LavalinkClient, guild_id: &GuildId) -> Result<bool, JoinError> {
let Some(songbird) = songbird::get(ctx)
.await.clone() else {
return Err(JoinError::NoSender);
};
let handler = manager.get(*guild_id);
let has_handler = handler.is_some();
if has_handler {
handler.unwrap().lock().await.stop();
manager.remove(*guild_id).await?;
return Ok(true); // Handler removed
if llc.get_player_context(guild_id.get()).is_some() {
let Ok(_) = llc.delete_player(guild_id.get()).await else {
return Err(JoinError::NoCall)
};
}
Ok(false) // No handler, so it's already stopped
if songbird.get(GuildId::new(guild_id.get())).is_some() {
let Ok(_) = songbird.remove(GuildId::new(guild_id.get())).await else {
return Err(JoinError::Dropped);
};
}
return Ok(true)
}

View file

@ -1,84 +0,0 @@
use serenity::all::GuildId;
use songbird::input::YoutubeDl;
use songbird::tracks::TrackHandle;
use tokio::sync::Mutex;
use std::collections::{HashMap, VecDeque};
use std::sync::Arc;
type MusicQueueItem = Arc<Mutex<MusicQueue>>;
#[derive(Debug)]
pub struct MusicQueue {
pub queue: VecDeque<YoutubeDl>,
pub now_playing: Option<YoutubeDl>,
pub now_playing_track_handle: Option<TrackHandle>,
}
lazy_static! {
static ref HASHMAP: Mutex<HashMap<GuildId, MusicQueueItem>> = Mutex::new(HashMap::new());
}
async fn get_music_queue(guild_id: &GuildId) -> MusicQueueItem {
let mut queues = HASHMAP.lock().await;
queues
.entry(*guild_id)
.or_insert(Arc::new(Mutex::new(MusicQueue {
queue: VecDeque::new(),
now_playing: None,
now_playing_track_handle: None,
})))
.clone()
}
pub async fn with_music_queue<F, T>(guild_id: &GuildId, f: F) -> T
where
F: FnOnce(&mut MusicQueue) -> T,
T: Send,
{
let queue = get_music_queue(guild_id).await;
let mut queue = queue.lock().await;
f(&mut queue)
}
pub async fn delete_queue(guild_id: &GuildId) {
with_music_queue(guild_id, |queue| {
queue.now_playing = None;
queue.queue.clear();
})
.await;
}
pub async fn add_to_queue(guild_id: &GuildId, input: YoutubeDl) {
with_music_queue(guild_id, |queue| queue.queue.push_back(input)).await;
}
/// Get next track in queue
pub async fn next(guild_id: &GuildId) -> Option<YoutubeDl> {
with_music_queue(guild_id, |queue| queue.queue.pop_front()).await
}
pub async fn set_now_playing(guild_id: &GuildId, now_playing: Option<YoutubeDl>) {
with_music_queue(guild_id, |queue| queue.now_playing = now_playing).await;
}
pub async fn get_now_playing(guild_id: &GuildId) -> Option<YoutubeDl> {
with_music_queue(guild_id, |queue| queue.now_playing.to_owned()).await
}
pub async fn set_now_playing_track_handle(guild_id: &GuildId, track_handle: Option<TrackHandle>) {
with_music_queue(guild_id, |queue| {
queue.now_playing_track_handle = track_handle
})
.await
}
pub async fn get_now_playing_track_handle(guild_id: &GuildId) -> Option<TrackHandle> {
with_music_queue(guild_id, |queue| queue.now_playing_track_handle.to_owned()).await
}
pub async fn is_empty(guild_id: &GuildId) -> bool {
with_music_queue(guild_id, |queue| queue.queue.is_empty()).await
}

7
src/music/preview.rs Normal file
View file

@ -0,0 +1,7 @@
pub struct Preview {
pub title: String,
pub artist: Option<String>,
pub duration: Option<u64>,
pub thumbnail: Option<String>,
pub link: Option<String>,
}

View file

@ -6,6 +6,9 @@ use std::error::Error;
#[derive(Deserialize, Serialize)]
pub struct Config {
pub discord_token: String,
pub lavalink_address: String,
pub lavalink_password: String,
pub user_id: u64
}
const CONFIG_FILE: &str = "./data/config.json";
@ -23,6 +26,9 @@ pub fn load() -> Result<Config, Box<dyn Error>> {
fn create_empty() -> fs::File {
let example_config = Config {
discord_token: "paste_your_token".to_string(),
lavalink_address: "paste_your_lavalink_address".to_string(),
lavalink_password: "paste_your_lavalink_password".to_string(),
user_id: 976119987330777168
};
let mut config_file = fs::File::create(CONFIG_FILE).unwrap();

View file

@ -2,7 +2,8 @@ use std::time::Duration;
use chrono::Local;
use serenity::all::{Color, CreateEmbed, CreateEmbedAuthor, CreateEmbedFooter};
use songbird::input::{Compose, YoutubeDl};
use crate::music::preview::Preview;
pub struct Embed;
@ -38,23 +39,21 @@ impl Embed {
)))
}
pub async fn create_yt_playing(
mut src: YoutubeDl,
pub async fn create_playing(
preview: Preview,
username: &str,
show_as_author: &str,
) -> CreateEmbed {
let current_time = Local::now().format("%Y-%m-%d @ %H:%M:%S");
// Get metadata
let metadata = src.aux_metadata().await.expect("Cannot get metadata");
let title = metadata.title.unwrap_or("Unknown title".to_string());
let artist = metadata.artist.unwrap_or("Unknown artist".to_string());
let duration = metadata.duration.unwrap_or(Duration::from_millis(0));
let thumbnail = metadata
let title = preview.title;
let artist = preview.artist.unwrap_or("t".to_string());
let duration = Duration::from_millis(preview.duration.unwrap_or(0));
let thumbnail = preview
.thumbnail
.unwrap_or("https://http.cat/images/403.jpg".to_string());
let link = metadata
.source_url
let link = preview.link
.unwrap_or("https://piped.moonleay.net/403".to_string());
CreateEmbed::new()