WIP: code compiles now, connects to VC, does not play audio yet

This commit is contained in:
moonleay 2024-05-20 04:07:10 +02:00
parent 6685163c96
commit 880e81646d
Signed by: moonleay
GPG key ID: 82667543CCD715FB
7 changed files with 280 additions and 138 deletions

176
Cargo.lock generated
View file

@ -78,20 +78,10 @@ dependencies = [
]
[[package]]
name = "async-tungstenite"
version = "0.24.0"
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3609af4bbf701ddaf1f6bb4e6257dff4ff8932327d0e685d3f653724c258b1ac"
dependencies = [
"futures-io",
"futures-util",
"log",
"native-tls",
"pin-project-lite",
"tokio",
"tokio-native-tls",
"tungstenite 0.21.0",
]
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "audiopus"
@ -719,6 +709,25 @@ dependencies = [
"tracing",
]
[[package]]
name = "h2"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http 1.1.0",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
@ -764,6 +773,29 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "http-body"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http 1.1.0",
]
[[package]]
name = "http-body-util"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
dependencies = [
"bytes",
"futures-core",
"http 1.1.0",
"http-body 1.0.0",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
@ -786,9 +818,9 @@ dependencies = [
"futures-channel",
"futures-core",
"futures-util",
"h2",
"h2 0.3.26",
"http 0.2.12",
"http-body",
"http-body 0.4.6",
"httparse",
"httpdate",
"itoa",
@ -800,6 +832,26 @@ dependencies = [
"want",
]
[[package]]
name = "hyper"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.5",
"http 1.1.0",
"http-body 1.0.0",
"httparse",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.24.2"
@ -808,7 +860,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [
"futures-util",
"http 0.2.12",
"hyper",
"hyper 0.14.28",
"rustls 0.21.12",
"tokio",
"tokio-rustls 0.24.1",
@ -821,12 +873,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"hyper 0.14.28",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.3.1",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.1.0",
"http-body 1.0.0",
"hyper 1.3.1",
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower-service",
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
@ -902,25 +990,29 @@ dependencies = [
[[package]]
name = "lavalink-rs"
version = "0.10.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87676c01f7c63f255899cc2672f11a1d7d6848787cf0e6abbc6b79c839cf9f24"
checksum = "4ba892eecbfb64bfa0e4fca87462a33fa8616915a6532d38026267147dd62cdb"
dependencies = [
"arc-swap",
"async-tungstenite",
"bytes",
"dashmap",
"futures",
"http 1.1.0",
"http-body-util",
"hyper 1.3.1",
"hyper-tls 0.6.0",
"hyper-util",
"oneshot",
"reqwest",
"serde",
"serde_json",
"serde_qs",
"serenity",
"songbird",
"tokio",
"tokio-tungstenite 0.21.0",
"tracing",
"url",
"urlencoding",
"version_check",
]
@ -1515,12 +1607,12 @@ dependencies = [
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"h2 0.3.26",
"http 0.2.12",
"http-body",
"hyper",
"http-body 0.4.6",
"hyper 0.14.28",
"hyper-rustls",
"hyper-tls",
"hyper-tls 0.5.0",
"ipnet",
"js-sys",
"log",
@ -1874,9 +1966,9 @@ dependencies = [
[[package]]
name = "serde_qs"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c"
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
dependencies = [
"percent-encoding",
"serde",
@ -2530,6 +2622,28 @@ dependencies = [
"winnow",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]]
name = "tower-service"
version = "0.3.2"
@ -2813,6 +2927,12 @@ dependencies = [
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf-8"
version = "0.7.6"

View file

@ -14,7 +14,7 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
confy = "0.6.0"
songbird = { version = "0.4", features = ["gateway", "serenity", "native", "driver"], default-features = false }
lavalink-rs = { version = "0.10", features = ["songbird", "serenity-native", "native-tls"], 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,6 +1,9 @@
use std::time::Duration;
use crate::music::preview::Preview;
use crate::util::embed::Embed;
use lavalink_rs::client::LavalinkClient;
use serenity::all::{CommandInteraction, Context};
use serenity::all::{CommandInteraction, Context, GuildId};
use serenity::builder::{CreateCommand, CreateEmbed};
pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClient) -> CreateEmbed {
@ -17,47 +20,27 @@ pub async fn run(ctx: &Context, command: &CommandInteraction, llc: &LavalinkClie
}
};
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_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, llc: &LavalinkClie
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

@ -8,22 +8,22 @@ pub async fn ready_event(client: LavalinkClient, _session_id: String, event: &ev
}
#[hook]
pub async fn track_start(client: LavalinkClient, _session_id: String, event: &events::TrackStart) {
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) {
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) {
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) {
pub async fn track_stuck(_client: LavalinkClient, _session_id: String, _event: &events::TrackStuck) {
}

View file

@ -3,8 +3,12 @@ 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,
@ -29,7 +33,7 @@ use crate::events::lavalink_event_handler;
extern crate lazy_static;
struct Handler {
llc: LavalinkClient
pub llc: LavalinkClient
}
#[async_trait]
@ -120,26 +124,26 @@ 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,
..Default::default()
events: event.clone(),
session_id: None
};
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 lavalink_client = LavalinkClient::new(event, vec![node_builder]);
tokio::spawn(async move {
lavalink_client.start().await;
});
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())

View file

@ -2,11 +2,12 @@ use crate::util::embed::Embed;
use crate::util::user_util;
use crate::util::user_util::get_vc_id;
use crate::music::preview::Preview;
use lavalink_rs::client::LavalinkClient;
use lavalink_rs::{client::LavalinkClient, player_context::QueueMessage};
use lavalink_rs::model::search::SearchEngines;
use lavalink_rs::model::track::TrackLoadData;
use lavalink_rs::player_context::TrackInQueue;
use serenity::all::{Context, CreateEmbed, GuildId, UserId};
use songbird::error::JoinError;
use std::sync::Arc;
/// Either queues the song, or start playing it instantly, depending on if there is already a song playing
@ -56,7 +57,7 @@ pub async fn attempt_to_queue_song(
.unwrap();
}
Err(why) => {
Err(_) => {
return Embed::create_error_respose(username, "Cannot join", "Could not join the channel.");
}
}
@ -79,7 +80,7 @@ pub async fn attempt_to_queue_song(
let search_query = if do_search {
match SearchEngines::YouTube.to_query(&query) {
Ok(x) => x,
Err(why) => {
Err(_) => {
return Embed::create_error_respose(username, "Cannot generate query", "Could not generate a seach query..");
}
}
@ -139,13 +140,17 @@ pub async fn attempt_to_queue_song(
}
};
let mut q = match player.get_queue().await {
Ok(q) => q,
Err(why) => {
return Embed::create_error_respose(username, "Cannont get queue", "Could not get queue.");
let q = player.get_queue();
q.append(tracks.into());
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();
}
};
q.append(&mut tracks.into());
}
Embed::create_playing(preview_data.unwrap(), username, &response_title).await
}
@ -166,54 +171,76 @@ 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, llc, &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_playing(head, username, "Song skipped; Now playing").await
}
/// Try to clear the queue and stop playing. Also leave the vc
@ -248,6 +275,9 @@ pub async fn attempt_to_stop(
);
}
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) => {
@ -267,26 +297,26 @@ 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, llc: &LavalinkClient, guild_id: &GuildId) -> Result<bool, JoinError> {
let manager = songbird::get(ctx)
.await
.expect("Cannot get Songbird")
.clone();
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();
let Ok(_) = llc.delete_player(guild_id.get()).await else {
return Err(JoinError::Dropped);
};
if has_handler {
handler.unwrap().lock().await.stop();
manager.remove(*guild_id).await?;
return Ok(true); // Handler removed
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);
};
}
Ok(false) // No handler, so it's already stopped
return Ok(true)
}

View file

@ -7,7 +7,8 @@ use std::error::Error;
pub struct Config {
pub discord_token: String,
pub lavalink_address: String,
pub lavalink_password: String
pub lavalink_password: String,
pub user_id: u64
}
const CONFIG_FILE: &str = "./data/config.json";
@ -26,7 +27,8 @@ 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()
lavalink_password: "paste_your_lavalink_password".to_string(),
user_id: 976119987330777168
};
let mut config_file = fs::File::create(CONFIG_FILE).unwrap();