5 Commits

Author SHA1 Message Date
brotoskyj 8fefbddffd Added --no-playlist to yt-dlp command. This seems to fix the bot randomly skipping songs 2026-01-20 21:30:39 -05:00
brotoskyj 7dea731cbf Added Extra Telemetry for Queue 2026-01-19 14:54:13 -05:00
brotoskyj ff9a46c05c Feature Changes
Added a Token Credential Manager - Closes #4
Added TUI Menu - Closes #2
2026-01-16 10:26:17 -05:00
brotoskyj 6169e6065d Added Telemetry
closes #8
2026-01-15 14:32:28 -05:00
James Brotosky 0ae38b0e01 Merge pull request 'Closes #1' (#10) from feature/queue into master
Reviewed-on: brotoskyj/RhythmWorks#10
2026-01-12 12:37:26 -05:00
8 changed files with 714 additions and 41 deletions
Generated
+396 -3
View File
@@ -4,8 +4,15 @@ version = 4
[[package]] [[package]]
name = "RhythmWorks" name = "RhythmWorks"
version = "0.3.0" version = "0.5.1"
dependencies = [ dependencies = [
"dialoguer",
"keyring",
"opentelemetry 0.27.1",
"opentelemetry-otlp",
"opentelemetry-proto",
"opentelemetry-semantic-conventions",
"opentelemetry_sdk",
"reqwest", "reqwest",
"semver", "semver",
"serde", "serde",
@@ -14,6 +21,10 @@ dependencies = [
"songbird", "songbird",
"symphonia", "symphonia",
"tokio", "tokio",
"tonic",
"tracing",
"tracing-opentelemetry",
"tracing-subscriber",
"yt-dlp", "yt-dlp",
] ]
@@ -82,6 +93,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "anyhow"
version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "arbitrary" name = "arbitrary"
version = "1.4.2" version = "1.4.2"
@@ -100,6 +117,28 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "async-stream"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [
"async-stream-impl",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-stream-impl"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.89"
@@ -152,6 +191,53 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "axum"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"sync_wrapper",
"tower 0.5.2",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
"pin-project-lite",
"rustversion",
"sync_wrapper",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@@ -360,6 +446,19 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "console"
version = "0.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e45a4a8926227e4197636ba97a9fc9b00477e9f4bd711395687c5f0734bec4"
dependencies = [
"encode_unicode",
"libc",
"once_cell",
"unicode-width",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.6" version = "0.9.6"
@@ -693,6 +792,18 @@ dependencies = [
"syn 2.0.111", "syn 2.0.111",
] ]
[[package]]
name = "dialoguer"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f104b501bf2364e78d0d3974cbc774f738f5865306ed128e1e0d7499c0ad96"
dependencies = [
"console",
"shell-words",
"tempfile",
"zeroize",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -747,6 +858,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.33" version = "0.8.33"
@@ -1243,6 +1360,12 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.8.1" version = "1.8.1"
@@ -1257,6 +1380,7 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"pin-utils", "pin-utils",
@@ -1275,6 +1399,7 @@ dependencies = [
"hyper", "hyper",
"hyper-util", "hyper-util",
"rustls 0.23.35", "rustls 0.23.35",
"rustls-native-certs",
"rustls-pki-types", "rustls-pki-types",
"tokio", "tokio",
"tokio-rustls 0.26.4", "tokio-rustls 0.26.4",
@@ -1282,6 +1407,19 @@ dependencies = [
"webpki-roots 1.0.4", "webpki-roots 1.0.4",
] ]
[[package]]
name = "hyper-timeout"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0"
dependencies = [
"hyper",
"hyper-util",
"pin-project-lite",
"tokio",
"tower-service",
]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.6.0" version = "0.6.0"
@@ -1515,6 +1653,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "itertools"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.16" version = "1.0.16"
@@ -1541,6 +1688,19 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "keyring"
version = "3.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
dependencies = [
"byteorder",
"linux-keyutils",
"log",
"windows-sys 0.60.2",
"zeroize",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -1605,6 +1765,16 @@ dependencies = [
"zlib-rs", "zlib-rs",
] ]
[[package]]
name = "linux-keyutils"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e"
dependencies = [
"bitflags 2.10.0",
"libc",
]
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.11.0" version = "0.11.0"
@@ -1683,6 +1853,12 @@ dependencies = [
"regex-automata", "regex-automata",
] ]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]] [[package]]
name = "md-5" name = "md-5"
version = "0.10.6" version = "0.10.6"
@@ -1937,6 +2113,108 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "opentelemetry"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab70038c28ed37b97d8ed414b6429d343a8bbf44c9f79ec854f3a643029ba6d7"
dependencies = [
"futures-core",
"futures-sink",
"js-sys",
"pin-project-lite",
"thiserror 1.0.69",
"tracing",
]
[[package]]
name = "opentelemetry"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0"
dependencies = [
"futures-core",
"futures-sink",
"js-sys",
"pin-project-lite",
"thiserror 2.0.17",
]
[[package]]
name = "opentelemetry-http"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a8a7f5f6ba7c1b286c2fbca0454eaba116f63bbe69ed250b642d36fbb04d80"
dependencies = [
"async-trait",
"bytes",
"http",
"opentelemetry 0.27.1",
"reqwest",
]
[[package]]
name = "opentelemetry-otlp"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91cf61a1868dacc576bf2b2a1c3e9ab150af7272909e80085c3173384fe11f76"
dependencies = [
"async-trait",
"futures-core",
"http",
"opentelemetry 0.27.1",
"opentelemetry-http",
"opentelemetry-proto",
"opentelemetry_sdk",
"prost",
"reqwest",
"thiserror 1.0.69",
"tokio",
"tonic",
"tracing",
]
[[package]]
name = "opentelemetry-proto"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6e05acbfada5ec79023c85368af14abd0b307c015e9064d249b2a950ef459a6"
dependencies = [
"hex",
"opentelemetry 0.27.1",
"opentelemetry_sdk",
"prost",
"serde",
"tonic",
]
[[package]]
name = "opentelemetry-semantic-conventions"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc1b6902ff63b32ef6c489e8048c5e253e2e4a803ea3ea7e783914536eb15c52"
[[package]]
name = "opentelemetry_sdk"
version = "0.27.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "231e9d6ceef9b0b2546ddf52335785ce41252bc7474ee8ba05bfad277be13ab8"
dependencies = [
"async-trait",
"futures-channel",
"futures-executor",
"futures-util",
"glob",
"opentelemetry 0.27.1",
"percent-encoding",
"rand 0.8.5",
"serde_json",
"thiserror 1.0.69",
"tokio",
"tokio-stream",
"tracing",
]
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "2.10.1" version = "2.10.1"
@@ -2195,6 +2473,29 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "prost"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5"
dependencies = [
"bytes",
"prost-derive",
]
[[package]]
name = "prost-derive"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d"
dependencies = [
"anyhow",
"itertools",
"proc-macro2",
"quote",
"syn 2.0.111",
]
[[package]] [[package]]
name = "pulldown-cmark" name = "pulldown-cmark"
version = "0.9.6" version = "0.9.6"
@@ -2442,6 +2743,7 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
"quinn", "quinn",
"rustls 0.23.35", "rustls 0.23.35",
"rustls-native-certs",
"rustls-pki-types", "rustls-pki-types",
"serde", "serde",
"serde_json", "serde_json",
@@ -2451,7 +2753,7 @@ dependencies = [
"tokio-native-tls", "tokio-native-tls",
"tokio-rustls 0.26.4", "tokio-rustls 0.26.4",
"tokio-util", "tokio-util",
"tower", "tower 0.5.2",
"tower-http", "tower-http",
"tower-service", "tower-service",
"url", "url",
@@ -2581,6 +2883,7 @@ version = "0.23.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f"
dependencies = [ dependencies = [
"log",
"once_cell", "once_cell",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
@@ -2601,6 +2904,15 @@ dependencies = [
"security-framework 3.5.1", "security-framework 3.5.1",
] ]
[[package]]
name = "rustls-pemfile"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
dependencies = [
"rustls-pki-types",
]
[[package]] [[package]]
name = "rustls-pki-types" name = "rustls-pki-types"
version = "1.13.2" version = "1.13.2"
@@ -2983,6 +3295,12 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shell-words"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "1.3.0" version = "1.3.0"
@@ -3963,6 +4281,59 @@ dependencies = [
"tokio-util", "tokio-util",
] ]
[[package]]
name = "tonic"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [
"async-stream",
"async-trait",
"axum",
"base64",
"bytes",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-timeout",
"hyper-util",
"percent-encoding",
"pin-project",
"prost",
"rustls-native-certs",
"rustls-pemfile",
"socket2 0.5.10",
"tokio",
"tokio-rustls 0.26.4",
"tokio-stream",
"tower 0.4.13",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"indexmap 1.9.3",
"pin-project",
"pin-project-lite",
"rand 0.8.5",
"slab",
"tokio",
"tokio-util",
"tower-layer",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -3991,7 +4362,7 @@ dependencies = [
"http-body", "http-body",
"iri-string", "iri-string",
"pin-project-lite", "pin-project-lite",
"tower", "tower 0.5.2",
"tower-layer", "tower-layer",
"tower-service", "tower-service",
] ]
@@ -4062,6 +4433,22 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-opentelemetry"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc"
dependencies = [
"js-sys",
"opentelemetry 0.31.0",
"smallvec",
"tracing",
"tracing-core",
"tracing-log",
"tracing-subscriber",
"web-time",
]
[[package]] [[package]]
name = "tracing-subscriber" name = "tracing-subscriber"
version = "0.3.22" version = "0.3.22"
@@ -4265,6 +4652,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.5.1" version = "0.5.1"
+12 -1
View File
@@ -1,9 +1,16 @@
[package] [package]
name = "RhythmWorks" name = "RhythmWorks"
version = "0.3.0" version = "0.5.1"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
dialoguer = "0.12.0"
keyring = { version = "3.6.3", features = ["linux-native", "windows-native"] }
opentelemetry = { version = "0.27.0", features = ["logs", "metrics", "trace"] }
opentelemetry-otlp = { version = "0.27.0", features = ["trace", "metrics", "grpc-tonic", "http-proto", "tls", "reqwest-client", "reqwest-rustls"] }
opentelemetry-proto = "0.27.0"
opentelemetry-semantic-conventions = "0.27.0"
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio", "trace"] }
reqwest = { version = "0.12.28", features = ["blocking", "json"] } reqwest = { version = "0.12.28", features = ["blocking", "json"] }
semver = "1.0.27" semver = "1.0.27"
serde = "1.0.228" serde = "1.0.228"
@@ -12,6 +19,10 @@ serenity = { version = "0.12.5", features = ["client", "gateway", "voice"] }
songbird = { version = "0.5.0", features = ["builtin-queue", "driver", "serenity"] } songbird = { version = "0.5.0", features = ["builtin-queue", "driver", "serenity"] }
symphonia = { version = "0.5.5", features = ["aac", "alac", "isomp4", "mp3"] } symphonia = { version = "0.5.5", features = ["aac", "alac", "isomp4", "mp3"] }
tokio = "1.48.0" tokio = "1.48.0"
tonic = { version = "0.12.3", features = ["tls-roots"] }
tracing = "0.1.41"
tracing-opentelemetry = "0.32.0"
tracing-subscriber = "0.3.20"
yt-dlp = "1.4.7" yt-dlp = "1.4.7"
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
cargo clean
cargo fmt
cargo clippy --fix --all-targets --allow-dirty
cargo build --release
cargo build --release --target x86_64-pc-windows-gnu
+221 -22
View File
@@ -1,18 +1,64 @@
use dialoguer::Select;
use dialoguer::theme::ColorfulTheme;
use modules::*; use modules::*;
use opentelemetry::KeyValue;
use opentelemetry::global;
use opentelemetry::trace::Status;
use opentelemetry::trace::TraceContextExt;
use opentelemetry::trace::Tracer;
use serenity::all::ChannelId; use serenity::all::ChannelId;
use serenity::async_trait; use serenity::async_trait;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::model::gateway::Ready; use serenity::model::gateway::Ready;
use serenity::prelude::*; use serenity::prelude::*;
use songbird::Event;
use songbird::EventContext;
use songbird::SerenityInit; use songbird::SerenityInit;
use songbird::events::EventHandler as VoiceEventHandler;
use songbird::get; use songbird::get;
use songbird::input::HlsRequest; use songbird::input::HlsRequest;
use songbird::input::Input; use songbird::input::Input;
use std::io; use std::env;
use std::process::Command; use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
pub mod modules; pub mod modules;
struct TrackTraceHandler {
otel_ctx: opentelemetry::Context,
guild_id: String,
track_url: String,
hls_url: String,
event_name: &'static str,
}
#[async_trait]
impl VoiceEventHandler for TrackTraceHandler {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
let tracer = global::tracer("RhythmWorks");
let track_status = if let EventContext::Track(track_list) = ctx {
track_list.first().map(|state| format!("{:?}", state.0))
} else {
None
};
tracer.in_span(self.event_name, |cx| {
cx.span()
.set_attribute(KeyValue::new("Guild_ID", self.guild_id.clone()));
cx.span()
.set_attribute(KeyValue::new("Youtube_URL", self.track_url.clone()));
cx.span()
.set_attribute(KeyValue::new("Stream_URL", self.hls_url.clone()));
cx.span().add_event(
self.event_name,
vec![KeyValue::new(
"track_status",
track_status.unwrap().to_string(),
)],
);
});
None
}
}
struct Handler; struct Handler;
fn get_user_voice_channel(ctx: &Context, msg: &Message) -> Option<ChannelId> { fn get_user_voice_channel(ctx: &Context, msg: &Message) -> Option<ChannelId> {
let guild_id = msg.guild_id?; let guild_id = msg.guild_id?;
@@ -32,13 +78,50 @@ impl EventHandler for Handler {
println!("Error sending message: {why:?}"); println!("Error sending message: {why:?}");
} }
if msg.content == "!join" { 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 { let guild_id = match msg.guild_id {
Some(g) => g, Some(g) => {
None => return, cx.span().add_event(
}; "Received Guild ID",
let channel_id = match get_user_voice_channel(&ctx, &msg) { vec![KeyValue::new("Server ID", g.to_string())],
Some(c) => c, );
cx.span().set_status(opentelemetry::trace::Status::Ok);
cx.span()
.set_attribute(KeyValue::new("Guild_ID", g.to_string()));
g
}
None => { 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
)));
let _ = msg let _ = msg
.channel_id .channel_id
.say(&ctx.http, "You must be in a voice channel") .say(&ctx.http, "You must be in a voice channel")
@@ -46,7 +129,6 @@ impl EventHandler for Handler {
return; return;
} }
}; };
let manager = get(&ctx).await.expect("Songbird not initialized").clone(); let manager = get(&ctx).await.expect("Songbird not initialized").clone();
let _ = manager.join(guild_id, channel_id).await; let _ = manager.join(guild_id, channel_id).await;
@@ -55,6 +137,11 @@ impl EventHandler for Handler {
.channel_id .channel_id
.say(&ctx.http, "Joined your voice channel!") .say(&ctx.http, "Joined your voice channel!")
.await; .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" { if msg.content == "!leave" {
let guild_id = match msg.guild_id { let guild_id = match msg.guild_id {
@@ -71,6 +158,8 @@ impl EventHandler for Handler {
} }
} }
if msg.content.starts_with("!play") { if msg.content.starts_with("!play") {
let tracer = global::tracer("RhythmWorks");
let span = tracer.start("Play Message Received");
let mut yt_url = msg let mut yt_url = msg
.content .content
.strip_prefix("!play ") .strip_prefix("!play ")
@@ -84,7 +173,30 @@ impl EventHandler for Handler {
let _ = msg.reply(&ctx, "Please provide a YouTube URL.").await; let _ = msg.reply(&ctx, "Please provide a YouTube URL.").await;
return; return;
} }
let guild_id = msg.guild_id.unwrap(); 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 manager = songbird::get(&ctx).await.unwrap().clone();
let handler_lock = if let Some(call) = manager.get(guild_id) { let handler_lock = if let Some(call) = manager.get(guild_id) {
call call
@@ -92,35 +204,100 @@ impl EventHandler for Handler {
let channel_id = match get_user_voice_channel(&ctx, &msg) { let channel_id = match get_user_voice_channel(&ctx, &msg) {
Some(c) => c, Some(c) => c,
None => { None => {
let _ = msg.reply(&ctx, "Join a voice channel first!").await; 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
)));
let _ = msg
.channel_id
.say(&ctx.http, "You must be in a voice channel")
.await;
return; return;
} }
}; };
manager.join(guild_id, channel_id).await.unwrap() manager.join(guild_id, channel_id).await.unwrap()
}; };
let output = Command::new("./yt-dlp") let output = match Command::new("./yt-dlp")
.args(["-f", "bestaudio", "-g", &yt_url]) .args(["-f", "bestaudio", "-g", "--no-playlist", &yt_url])
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.output() .output()
.expect("yt-dlp failed"); {
if !output.status.success() { Ok(output) => {
let _ = msg.reply(&ctx, "Failed to fetch audio stream.").await; 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; return;
} }
};
let stream_url = String::from_utf8(output.stdout) let stream_url = String::from_utf8(output.stdout)
.expect("Invalid UTF-8") .expect("Invalid UTF-8")
.trim() .trim()
.to_string(); .to_string();
let telemetry_stream_url = stream_url.clone();
let mut call = handler_lock.lock().await; let mut call = handler_lock.lock().await;
let input = Input::from(HlsRequest::new(reqwest::Client::new(), stream_url)); let input = Input::from(HlsRequest::new(reqwest::Client::new(), stream_url));
let _ = call.enqueue_input(input).await; let track_handle = call.enqueue_input(input).await;
let otel_ctx = opentelemetry::Context::current();
let position = call.queue().len(); let position = call.queue().len();
let response = if position == 1 { let response = if position == 1 {
cx.span().add_event(
"Starting Playback",
vec![KeyValue::new("Status", "Playing".to_string())],
);
"🎶 Now playing!".to_string() "🎶 Now playing!".to_string()
} else { } else {
cx.span().add_event(
"Starting Playback",
vec![KeyValue::new("Status", "Added to Queue".to_string())],
);
format!("🎶 Added Song to Queue: #{}", position) format!("🎶 Added Song to Queue: #{}", position)
}; };
let _ = msg.channel_id.say(&ctx.http, response).await; let _ = msg.channel_id.say(&ctx.http, response).await;
let _ = track_handle.add_event(
Event::Track(songbird::events::TrackEvent::Play),
TrackTraceHandler {
otel_ctx: otel_ctx.clone(),
guild_id: guild_id.to_string().clone(),
track_url: yt_url.clone(),
hls_url: telemetry_stream_url.clone(),
event_name: "Track Started Playing",
},
);
let _ = track_handle.add_event(
Event::Track(songbird::events::TrackEvent::End),
TrackTraceHandler {
otel_ctx: otel_ctx.clone(),
guild_id: guild_id.to_string().clone(),
track_url: yt_url.clone(),
hls_url: telemetry_stream_url.clone(),
event_name: "Track Finished",
},
);
let _ = track_handle.add_event(
Event::Track(songbird::events::TrackEvent::Error),
TrackTraceHandler {
otel_ctx: otel_ctx.clone(),
guild_id: guild_id.to_string().clone(),
track_url: yt_url.clone(),
hls_url: telemetry_stream_url.clone(),
event_name: "Playback had an error",
},
);
} }
if msg.content == "!skip" { if msg.content == "!skip" {
let guild_id = match msg.guild_id { let guild_id = match msg.guild_id {
@@ -180,13 +357,25 @@ async fn main() {
This program comes with ABSOLUTELY NO WARRANTY\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; let tracer_provider = telemetry::init_telemetry();
println!("Please paste in your bot token"); global::set_tracer_provider(tracer_provider.clone());
let mut input = String::new(); let tracer: global::BoxedTracer = global::tracer("tracer");
io::stdin() tracer
.read_line(&mut input) .in_span("Checking for Updates", |_cx| updator::update())
.expect("Failed to read line"); .await;
let token = input.trim(); let service_name = "RhythmWorks";
let username = env::var("USER").or_else(|_| env::var("USERNAME")).unwrap();
loop {
let token = credentialmanager::get_token(service_name, &username);
let options: Vec<&'static str> = vec!["- Start Bot", "- Change Token", "- Delete Token"];
let selection = Select::with_theme(&ColorfulTheme::default())
.with_prompt("Choose an Option")
.default(0)
.items(&options)
.interact()
.unwrap();
match selection {
0 => {
let intents = GatewayIntents::GUILD_MESSAGES let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::GUILDS | GatewayIntents::GUILDS
| GatewayIntents::DIRECT_MESSAGES | GatewayIntents::DIRECT_MESSAGES
@@ -201,3 +390,13 @@ This is free software, and you are welcome to redistribute it under certain cond
println!("Client error: {why:?}"); println!("Client error: {why:?}");
} }
} }
1 => {
credentialmanager::change_token(service_name, &username);
}
2 => {
credentialmanager::remove_token(service_name, &username);
}
_ => println!("Invalid Option"),
}
}
}
+37
View File
@@ -0,0 +1,37 @@
use dialoguer::{Input, theme::ColorfulTheme};
use keyring::Entry;
pub fn get_token<'a>(service: &str, username: &str) -> String {
let entry = Entry::new(service, username).expect("Failed to get keyring entry");
match entry.get_password() {
Ok(token) => token,
Err(_) => {
let token: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter your bot token")
.interact_text()
.unwrap();
match entry.set_password(&token) {
Ok(_) => {}
Err(e) => {
println!("Failed to set token: {}", e);
std::process::abort()
}
};
token
}
}
}
pub fn change_token(service: &str, username: &str) {
let entry = Entry::new(service, username).expect("Failed to get keyring entry");
let new_token: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("New Token")
.interact_text()
.unwrap();
let _ = entry.set_password(&new_token);
}
pub fn remove_token(service: &str, username: &str) {
let entry = Entry::new(service, username).expect("Failed to get keyring entry");
let _ = entry.delete_credential();
}
+2
View File
@@ -1 +1,3 @@
pub mod credentialmanager;
pub mod telemetry;
pub mod updator; pub mod updator;
+26
View File
@@ -0,0 +1,26 @@
use opentelemetry::KeyValue;
use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
use opentelemetry_sdk::{Resource, runtime};
use tonic::transport::{Channel, ClientTlsConfig};
pub fn init_telemetry() -> opentelemetry_sdk::trace::TracerProvider {
let endpoint = "https://signoz.racooncity.org".to_string();
let channel = Channel::from_shared(endpoint.clone())
.unwrap()
.tls_config(ClientTlsConfig::new().with_native_roots())
.unwrap()
.connect_lazy();
let exporter = opentelemetry_otlp::SpanExporter::builder()
.with_tonic()
.with_endpoint(endpoint.clone())
.with_channel(channel)
.build()
.expect("Failed to Build Exporter");
opentelemetry_sdk::trace::TracerProvider::builder()
.with_batch_exporter(exporter, runtime::Tokio)
.with_resource(Resource::new(vec![KeyValue::new(
"service.name",
"RhythmWorks",
)]))
.build()
}
-1
View File
@@ -25,6 +25,5 @@ pub async fn update() {
println!("URL: https://git.racooncity.org/brotoskyj/RhythmWorks/releases"); println!("URL: https://git.racooncity.org/brotoskyj/RhythmWorks/releases");
thread::sleep(std::time::Duration::from_secs(10)); thread::sleep(std::time::Duration::from_secs(10));
std::process::exit(1); std::process::exit(1);
} else {
} }
} }