1 Commits

Author SHA1 Message Date
brotoskyj b2059a76c6 Closes #1 2026-01-12 12:36:15 -05:00
4 changed files with 79 additions and 56 deletions
Generated
+1 -1
View File
@@ -4,7 +4,7 @@ version = 4
[[package]]
name = "RhythmWorks"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"reqwest",
"semver",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "RhythmWorks"
version = "0.2.0"
version = "0.3.0"
edition = "2024"
[dependencies]
+65 -46
View File
@@ -8,18 +8,12 @@ use songbird::SerenityInit;
use songbird::get;
use songbird::input::HlsRequest;
use songbird::input::Input;
use songbird::tracks::TrackHandle;
use std::collections::HashMap;
use std::io;
use std::process::Command;
use std::process::Stdio;
use std::sync::Arc;
pub mod modules;
type TrackMap = Arc<Mutex<HashMap<u64, TrackHandle>>>;
struct Handler {
track_map: TrackMap,
}
struct Handler;
fn get_user_voice_channel(ctx: &Context, msg: &Message) -> Option<ChannelId> {
let guild_id = msg.guild_id?;
let guild = ctx.cache.guild(guild_id)?;
@@ -77,61 +71,78 @@ impl EventHandler for Handler {
}
}
if msg.content.starts_with("!play") {
let mut url = msg
let mut yt_url = msg
.content
.strip_prefix("!play ")
.unwrap_or("")
.trim()
.to_string();
let separator = "&";
if let Some(offset) = url.find(separator) {
url.truncate(offset);
if let Some(offset) = yt_url.find('&') {
yt_url.truncate(offset);
}
if url.is_empty() {
if yt_url.is_empty() {
let _ = msg.reply(&ctx, "Please provide a YouTube URL.").await;
return;
}
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(&ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let handler_lock = if let Some(handler) = manager.get(guild_id) {
handler
let manager = songbird::get(&ctx).await.unwrap().clone();
let handler_lock = if let Some(call) = manager.get(guild_id) {
call
} else {
let channel_id = get_user_voice_channel(&ctx, &msg);
match channel_id {
Some(c) => manager.join(guild_id, c).await.unwrap(),
let channel_id = match get_user_voice_channel(&ctx, &msg) {
Some(c) => c,
None => {
let _ = msg.reply(&ctx, "Join a voice channel first!").await;
return;
}
}
};
manager.join(guild_id, channel_id).await.unwrap()
};
let output = Command::new("./yt-dlp")
.args(["-f", "bestaudio", "-g", &url])
.args(["-f", "bestaudio", "-g", &yt_url])
.stdout(Stdio::piped())
.output()
.expect("Failed");
let url_bytes = output.stdout;
let url = String::from_utf8(url_bytes).expect("Failed");
let mut handler = handler_lock.lock().await;
let input = Input::from(HlsRequest::new(reqwest::Client::new(), url.to_string()));
let track_handle = handler.play_input(input);
let _ = track_handle.play();
self.track_map
.lock()
.await
.insert(guild_id.into(), track_handle);
let _ = msg.channel_id.say(&ctx.http, "🎶 Playing...").await;
.expect("yt-dlp failed");
if !output.status.success() {
let _ = msg.reply(&ctx, "Failed to fetch audio stream.").await;
return;
}
let stream_url = String::from_utf8(output.stdout)
.expect("Invalid UTF-8")
.trim()
.to_string();
let mut call = handler_lock.lock().await;
let input = Input::from(HlsRequest::new(reqwest::Client::new(), stream_url));
let _ = call.enqueue_input(input).await;
let position = call.queue().len();
let response = if position == 1 {
"🎶 Now playing!".to_string()
} else {
format!("🎶 Added Song to Queue: #{}", position)
};
let _ = msg.channel_id.say(&ctx.http, response).await;
}
if msg.content == "!skip" {
let guild_id = match msg.guild_id {
Some(g) => g,
None => return,
};
let manager = songbird::get(&ctx).await.unwrap();
if let Some(call) = manager.get(guild_id) {
let call = call.lock().await;
let _ = call.queue().skip();
let _ = msg.channel_id.say(&ctx.http, "Skipped").await;
}
}
if msg.content == "!pause" {
let guild_id = match msg.guild_id {
Some(g) => g,
None => return,
};
if let Some(handle) = self.track_map.lock().await.get(&guild_id.into()) {
let _ = handle.pause();
let manager = songbird::get(&ctx).await.unwrap();
if let Some(call) = manager.get(guild_id) {
let call = call.lock().await;
let _ = call.queue().pause();
}
}
if msg.content == "!resume" {
@@ -139,8 +150,10 @@ impl EventHandler for Handler {
Some(g) => g,
None => return,
};
if let Some(handle) = self.track_map.lock().await.get(&guild_id.into()) {
let _ = handle.play();
let manager = songbird::get(&ctx).await.unwrap();
if let Some(call) = manager.get(guild_id) {
let call = call.lock().await;
let _ = call.queue().resume();
}
}
if msg.content == "!stop" {
@@ -148,8 +161,10 @@ impl EventHandler for Handler {
Some(g) => g,
None => return,
};
if let Some(handle) = self.track_map.lock().await.get(&guild_id.into()) {
let _ = handle.stop();
let manager = songbird::get(&ctx).await.unwrap();
if let Some(call) = manager.get(guild_id) {
let call = call.lock().await;
let _ = call.queue().stop();
}
}
}
@@ -157,24 +172,28 @@ impl EventHandler for Handler {
println!("{} is connected!", ready.user.name);
}
}
#[tokio::main]
async fn main() {
println!("RhythmWorks Copyright (C) 2025 James Brotosky\n
println!(
"RhythmWorks Copyright (C) 2025 James Brotosky\n
This program comes with ABSOLUTELY NO WARRANTY\n
This is free software, and you are welcome to redistribute it under certain conditions.");
This is free software, and you are welcome to redistribute it under certain conditions."
);
updator::update().await;
println!("Please paste in your bot token");
let mut input = String::new();
io::stdin().read_line(&mut input).expect("Failed to read line");
io::stdin()
.read_line(&mut input)
.expect("Failed to read line");
let token = input.trim();
let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::GUILDS
| GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILD_VOICE_STATES;
let track_map: TrackMap = Arc::new(Mutex::new(HashMap::new()));
let mut client = Client::builder(&token, intents)
.event_handler(Handler { track_map })
.event_handler(Handler)
.register_songbird()
.await
.expect("Err creating client");
+11 -7
View File
@@ -1,15 +1,22 @@
use std::thread;
use serde::Deserialize;
use semver::Version;
use reqwest;
use semver::Version;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct Release {
tag_name: String,
}
pub async fn update() {
let body = reqwest::get("https://git.racooncity.org/api/v1/repos/brotoskyj/RhythmWorks/releases/latest").await.unwrap().text().await.unwrap();
let body = reqwest::get(
"https://git.racooncity.org/api/v1/repos/brotoskyj/RhythmWorks/releases/latest",
)
.await
.unwrap()
.text()
.await
.unwrap();
let release_info: Release = serde_json::from_str(&body).unwrap();
let latest_version = Version::parse(&release_info.tag_name).unwrap();
let current_version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
@@ -18,9 +25,6 @@ pub async fn update() {
println!("URL: https://git.racooncity.org/brotoskyj/RhythmWorks/releases");
thread::sleep(std::time::Duration::from_secs(10));
std::process::exit(1);
}
else {
} else {
}
}