6169e6065d
closes #8
310 lines
12 KiB
Rust
310 lines
12 KiB
Rust
use modules::*;
|
|
use opentelemetry::KeyValue;
|
|
use opentelemetry::global;
|
|
use opentelemetry::global::ObjectSafeSpan;
|
|
use opentelemetry::trace::SpanKind;
|
|
use opentelemetry::trace::Status;
|
|
use opentelemetry::trace::TraceContextExt;
|
|
use opentelemetry::trace::Tracer;
|
|
use serenity::all::ChannelId;
|
|
use serenity::async_trait;
|
|
use serenity::futures::channel::oneshot::channel;
|
|
use serenity::model::channel::Message;
|
|
use serenity::model::gateway::Ready;
|
|
use serenity::prelude::*;
|
|
use songbird::SerenityInit;
|
|
use songbird::get;
|
|
use songbird::input::HlsRequest;
|
|
use songbird::input::Input;
|
|
use std::io;
|
|
use std::process::Command;
|
|
use std::process::Stdio;
|
|
pub mod modules;
|
|
|
|
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)?;
|
|
guild
|
|
.voice_states
|
|
.get(&msg.author.id)
|
|
.and_then(|vs| vs.channel_id)
|
|
}
|
|
|
|
#[async_trait]
|
|
impl EventHandler for Handler {
|
|
async fn message(&self, ctx: Context, msg: Message) {
|
|
if msg.content == "!ping"
|
|
&& let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await
|
|
{
|
|
println!("Error sending message: {why:?}");
|
|
}
|
|
if msg.content == "!join" {
|
|
let tracer = global::tracer("RhythmWorks");
|
|
let span = tracer.start("Join Message Received");
|
|
let cx = opentelemetry::Context::current_with_span(span);
|
|
let guild_id = match msg.guild_id {
|
|
Some(g) => {
|
|
cx.span().add_event(
|
|
"Received Guild ID",
|
|
vec![KeyValue::new("Server ID", g.to_string())],
|
|
);
|
|
cx.span().set_status(opentelemetry::trace::Status::Ok);
|
|
cx.span()
|
|
.set_attribute(KeyValue::new("Guild_ID", g.to_string()));
|
|
g
|
|
}
|
|
None => {
|
|
cx.span().add_event(
|
|
"Failed to Retreive Guild ID",
|
|
vec![KeyValue::new("Server ID", "None")],
|
|
);
|
|
cx.span()
|
|
.set_status(opentelemetry::trace::Status::error("Failed"));
|
|
return;
|
|
}
|
|
};
|
|
|
|
let channel_id = match get_user_voice_channel(&ctx, &msg) {
|
|
Some(c) => {
|
|
cx.span().add_event(
|
|
"Received Channel ID",
|
|
vec![KeyValue::new("Channel ID", c.to_string())],
|
|
);
|
|
cx.span().set_status(Status::Ok);
|
|
c
|
|
}
|
|
None => {
|
|
cx.span().add_event(
|
|
"Failed to Retreive Channel ID",
|
|
vec![KeyValue::new("Channel ID", "None")],
|
|
);
|
|
cx.span()
|
|
.set_status(opentelemetry::trace::Status::error(format!(
|
|
"{} not in Voice Channel",
|
|
msg.author.to_string()
|
|
)));
|
|
let _ = msg
|
|
.channel_id
|
|
.say(&ctx.http, "You must be in a voice channel")
|
|
.await;
|
|
return;
|
|
}
|
|
};
|
|
let manager = get(&ctx).await.expect("Songbird not initialized").clone();
|
|
|
|
let _ = manager.join(guild_id, channel_id).await;
|
|
|
|
let _ = msg
|
|
.channel_id
|
|
.say(&ctx.http, "Joined your voice channel!")
|
|
.await;
|
|
cx.span().add_event(
|
|
"Joined Voice Channel",
|
|
vec![KeyValue::new("Voice Channel", msg.channel_id.to_string())],
|
|
);
|
|
cx.span().set_status(Status::Ok);
|
|
}
|
|
if msg.content == "!leave" {
|
|
let guild_id = match msg.guild_id {
|
|
Some(g) => g,
|
|
None => return,
|
|
};
|
|
let manager = songbird::get(&ctx).await.unwrap();
|
|
if manager.get(guild_id).is_some() {
|
|
let _ = manager.remove(guild_id).await;
|
|
let _ = msg
|
|
.channel_id
|
|
.say(&ctx.http, "Left the voice channel")
|
|
.await;
|
|
}
|
|
}
|
|
if msg.content.starts_with("!play") {
|
|
let tracer = global::tracer("RhythmWorks");
|
|
let span = tracer.start("Play Message Received");
|
|
let mut yt_url = msg
|
|
.content
|
|
.strip_prefix("!play ")
|
|
.unwrap_or("")
|
|
.trim()
|
|
.to_string();
|
|
if let Some(offset) = yt_url.find('&') {
|
|
yt_url.truncate(offset);
|
|
}
|
|
if yt_url.is_empty() {
|
|
let _ = msg.reply(&ctx, "Please provide a YouTube URL.").await;
|
|
return;
|
|
}
|
|
let cx = opentelemetry::Context::current_with_span(span);
|
|
cx.span()
|
|
.set_attribute(KeyValue::new("Youtube_URL", yt_url.to_string()));
|
|
let guild_id = match msg.guild_id {
|
|
Some(g) => {
|
|
cx.span().add_event(
|
|
"Received Guild ID",
|
|
vec![KeyValue::new("Server ID", g.to_string())],
|
|
);
|
|
cx.span().set_status(opentelemetry::trace::Status::Ok);
|
|
cx.span()
|
|
.set_attribute(KeyValue::new("Guild_ID", g.to_string()));
|
|
g
|
|
}
|
|
None => {
|
|
cx.span().add_event(
|
|
"Failed to Retreive Guild ID",
|
|
vec![KeyValue::new("Server ID", "None")],
|
|
);
|
|
cx.span()
|
|
.set_status(opentelemetry::trace::Status::error("Failed"));
|
|
return;
|
|
}
|
|
};
|
|
let manager = songbird::get(&ctx).await.unwrap().clone();
|
|
let handler_lock = if let Some(call) = manager.get(guild_id) {
|
|
call
|
|
} else {
|
|
let channel_id = match get_user_voice_channel(&ctx, &msg) {
|
|
Some(c) => c,
|
|
None => {
|
|
cx.span().add_event(
|
|
"Failed to Retreive Channel ID",
|
|
vec![KeyValue::new("Channel ID", "None")],
|
|
);
|
|
cx.span()
|
|
.set_status(opentelemetry::trace::Status::error(format!(
|
|
"{} not in Voice Channel",
|
|
msg.author.to_string()
|
|
)));
|
|
let _ = msg
|
|
.channel_id
|
|
.say(&ctx.http, "You must be in a voice channel")
|
|
.await;
|
|
return;
|
|
}
|
|
};
|
|
manager.join(guild_id, channel_id).await.unwrap()
|
|
};
|
|
let output = match Command::new("./yt-dlp")
|
|
.args(["-f", "bestaudio", "-g", &yt_url])
|
|
.stdout(Stdio::piped())
|
|
.output()
|
|
{
|
|
Ok(output) => {
|
|
cx.span().add_event(
|
|
"Retreiving Youtube Information",
|
|
vec![KeyValue::new("Success", yt_url.to_string())],
|
|
);
|
|
output
|
|
}
|
|
Err(e) => {
|
|
cx.span().add_event(
|
|
"Retreiving Youtube Information",
|
|
vec![KeyValue::new("Failed", e.to_string())],
|
|
);
|
|
cx.span().set_status(Status::error("Failed"));
|
|
eprintln!("{}", e);
|
|
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 {
|
|
cx.span().add_event("Starting Playback", vec![KeyValue::new("Status", "Playing".to_string())]);
|
|
"🎶 Now playing!".to_string()
|
|
} else {
|
|
cx.span().add_event("Starting Playback", vec![KeyValue::new("Status", "Added to Queue".to_string())]);
|
|
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,
|
|
};
|
|
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" {
|
|
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().resume();
|
|
}
|
|
}
|
|
if msg.content == "!stop" {
|
|
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().stop();
|
|
}
|
|
}
|
|
}
|
|
async fn ready(&self, _: Context, ready: Ready) {
|
|
println!("{} is connected!", ready.user.name);
|
|
}
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
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."
|
|
);
|
|
let tracer_provider = telemetry::init_telemetry();
|
|
global::set_tracer_provider(tracer_provider.clone());
|
|
let tracer: global::BoxedTracer = global::tracer("tracer");
|
|
tracer
|
|
.in_span("Checking for Updates", |cx| 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");
|
|
let token = input.trim();
|
|
let intents = GatewayIntents::GUILD_MESSAGES
|
|
| GatewayIntents::GUILDS
|
|
| GatewayIntents::DIRECT_MESSAGES
|
|
| GatewayIntents::MESSAGE_CONTENT
|
|
| GatewayIntents::GUILD_VOICE_STATES;
|
|
let mut client = Client::builder(&token, intents)
|
|
.event_handler(Handler)
|
|
.register_songbird()
|
|
.await
|
|
.expect("Err creating client");
|
|
if let Err(why) = client.start().await {
|
|
println!("Client error: {why:?}");
|
|
}
|
|
}
|