This commit is contained in:
parent
c87562b36a
commit
5483ef3595
5 changed files with 2977 additions and 421 deletions
2870
Cargo.lock
generated
2870
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
17
Cargo.toml
17
Cargo.toml
|
|
@ -21,23 +21,20 @@ regex = "1.11.1"
|
|||
once_cell = "1.20.3"
|
||||
sarcastic = { git = "https://github.com/DJMrTV/bored-yaml.git" }
|
||||
chrono = "0.4.39"
|
||||
reqwest = "0.13.0"
|
||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
||||
songbird = { version = "0.6.0", features = ["builtin-queue", "driver", "gateway"] }
|
||||
youtube_dl = { version = "0.10.0", features = ["downloader-rustls-tls"] }
|
||||
dashmap = "6.1.0"
|
||||
symphonia = { version = "0.5.4", features = ["aac", "mp3", "isomp4", "alac", "flac", "pcm", "vorbis"] }
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.43.0"
|
||||
features = [
|
||||
"rt",
|
||||
"macros",
|
||||
]
|
||||
features = ["rt", "macros"]
|
||||
|
||||
|
||||
[dependencies.serenity]
|
||||
version = "0.12.4"
|
||||
features = [
|
||||
"interactions_endpoint",
|
||||
"utils",
|
||||
"collector"
|
||||
]
|
||||
features = ["interactions_endpoint", "utils", "collector"]
|
||||
|
||||
[build-dependencies]
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -29,7 +29,13 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|||
mkdir -p /app/dist && \
|
||||
cp /app/target/x86_64-unknown-linux-musl/release/professor /app/dist/
|
||||
|
||||
FROM scratch AS final
|
||||
FROM alpine:latest AS final
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache ffmpeg python3 curl libstdc++ \
|
||||
&& curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp \
|
||||
&& chmod a+rx /usr/local/bin/yt-dlp \
|
||||
&& apk del curl
|
||||
|
||||
COPY --from=builder /app/dist/professor ./splatie
|
||||
ENTRYPOINT ["./splatie"]
|
||||
34
src/main.rs
34
src/main.rs
|
|
@ -2,19 +2,49 @@ mod emergency_report;
|
|||
mod error_codes;
|
||||
mod fest_fax;
|
||||
mod miiverse_mod_application;
|
||||
mod music;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serenity::async_trait;
|
||||
use serenity::prelude::*;
|
||||
use songbird::SerenityInit;
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
match Command::new("ffmpeg").arg("-version").output() {
|
||||
Ok(output) => {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let first_line = stdout.lines().next().unwrap_or("");
|
||||
println!("detected ffmpeg ver {}", first_line);
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("'ffmpeg' was not found in your system PATH");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
match Command::new("yt-dlp").arg("--version").output() {
|
||||
Ok(output) => {
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
let version = stdout.trim();
|
||||
println!("detected yt-dlp ver {}", version);
|
||||
}
|
||||
Err(_) => {
|
||||
eprintln!("'yt-dlp' was not found in your system PATH");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
let token = env::var("SPACEBOT_TOKEN").expect("Token not specified");
|
||||
let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT;
|
||||
let intents = GatewayIntents::GUILD_MESSAGES
|
||||
| GatewayIntents::MESSAGE_CONTENT
|
||||
| GatewayIntents::GUILDS
|
||||
| GatewayIntents::GUILD_VOICE_STATES;
|
||||
|
||||
let mut client = Client::builder(&token, intents)
|
||||
.event_handler(error_codes::ErrorCodeHandler)
|
||||
|
|
@ -24,6 +54,8 @@ async fn main() {
|
|||
.event_handler(emergency_report::EmergencyReportHandler::default())
|
||||
.event_handler(miiverse_mod_application::MiiverseModApplicationHandler::default())
|
||||
//.event_handler(fest_fax::FestFaxHandler::default())
|
||||
.event_handler(music::MusicHandler::default())
|
||||
.register_songbird()
|
||||
.await
|
||||
.expect("unable to create client");
|
||||
|
||||
|
|
|
|||
271
src/music.rs
Normal file
271
src/music.rs
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
//note to anyone reading my commits lol: not exactly efficient but this probs will only be used internally so who gaf!! meow meow
|
||||
use serenity::async_trait;
|
||||
use serenity::model::channel::Message;
|
||||
use serenity::prelude::*;
|
||||
use serenity::builder::{CreateMessage, CreateEmbed, CreateEmbedAuthor};
|
||||
use serenity::model::id::{GuildId, ChannelId};
|
||||
use songbird::input::{YoutubeDl, Input, Compose};
|
||||
use dashmap::DashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Arc;
|
||||
use std::env;
|
||||
|
||||
static PLAYLISTS: Lazy<DashMap<u64, Vec<String>>> = Lazy::new(DashMap::new);
|
||||
|
||||
pub struct MusicHandler {
|
||||
prefix: String,
|
||||
}
|
||||
|
||||
impl Default for MusicHandler {
|
||||
fn default() -> Self {
|
||||
// account for lowercase & uppercase... cuz that was pissing me off for no reason that it didnt??
|
||||
let prefix = env::var("PREFIX")
|
||||
.or_else(|_| env::var("prefix"))
|
||||
.unwrap_or_else(|_| "space!".to_string()) // fallback to space!, ngl binder probably wont check so hopefully this will stay :3
|
||||
.to_lowercase();
|
||||
|
||||
Self { prefix }
|
||||
}
|
||||
}
|
||||
|
||||
async fn join_channel(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
guild_id: GuildId,
|
||||
channel_id: ChannelId
|
||||
) -> Result<Arc<Mutex<songbird::Call>>, &'static str> {
|
||||
let manager = songbird::get(ctx).await.expect("Songbird client not initialized").clone();
|
||||
|
||||
if manager.get(guild_id).is_some() {
|
||||
return Err("bubs im already in another channel...");
|
||||
}
|
||||
|
||||
if manager.join(guild_id, channel_id).await.is_ok() {
|
||||
PLAYLISTS.insert(guild_id.get(), Vec::new());
|
||||
if let Some(handler) = manager.get(guild_id) {
|
||||
return Ok(handler);
|
||||
}
|
||||
}
|
||||
|
||||
Err("Failed to join the voice channel.")
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for MusicHandler {
|
||||
async fn message(&self, ctx: Context, msg: Message) {
|
||||
if msg.author.bot { return; }
|
||||
|
||||
let content_lower = msg.content.trim_start().to_lowercase();
|
||||
|
||||
if !content_lower.starts_with(&self.prefix) { return; }
|
||||
|
||||
let original_content = msg.content.trim_start();
|
||||
let stripped_content = &original_content[self.prefix.len()..];
|
||||
let args: Vec<&str> = stripped_content.split_whitespace().collect();
|
||||
if args.is_empty() { return; }
|
||||
|
||||
match args[0].to_lowercase().as_str() {
|
||||
"join" => {
|
||||
let guild = msg.guild(&ctx.cache).unwrap().clone();
|
||||
let guild_id = guild.id;
|
||||
|
||||
let voice_channel_id = guild
|
||||
.voice_states
|
||||
.get(&msg.author.id)
|
||||
.and_then(|state| state.channel_id);
|
||||
|
||||
match voice_channel_id {
|
||||
Some(channel_id) => {
|
||||
if let Err(err_msg) = join_channel(&ctx, &msg, guild_id, channel_id).await {
|
||||
let builder = CreateMessage::new().content(err_msg);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("You must be in a voice channel to use ts!!!!!")
|
||||
.color(16728123);
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"leave" => {
|
||||
let guild_id = msg.guild_id.unwrap();
|
||||
let manager = songbird::get(&ctx).await.expect("Songbird client not initialized").clone();
|
||||
|
||||
if manager.get(guild_id).is_some() {
|
||||
let _ = manager.leave(guild_id).await;
|
||||
let _ = manager.remove(guild_id).await;
|
||||
PLAYLISTS.remove(&guild_id.get());
|
||||
} else {
|
||||
let builder = CreateMessage::new().content("uuuuhhhhhhhh i think i have dementia...");
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
}
|
||||
}
|
||||
|
||||
"play" => {
|
||||
let _ = msg.delete(&ctx.http).await;
|
||||
|
||||
let guild = msg.guild(&ctx.cache).unwrap().clone();
|
||||
let guild_id = guild.id;
|
||||
let manager = songbird::get(&ctx).await.expect("Songbird client not initialized").clone();
|
||||
|
||||
let voice_channel_id = guild
|
||||
.voice_states
|
||||
.get(&msg.author.id)
|
||||
.and_then(|state| state.channel_id);
|
||||
|
||||
let handler_lock = match manager.get(guild_id) {
|
||||
Some(handler) => handler,
|
||||
None => match voice_channel_id {
|
||||
Some(channel_id) => match join_channel(&ctx, &msg, guild_id, channel_id).await {
|
||||
Ok(handler) => handler,
|
||||
Err(err_msg) => {
|
||||
let builder = CreateMessage::new().content(err_msg);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
return;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("IM NOT IN A VOICE CHANNEL THOUGH...")
|
||||
.color(16728123)
|
||||
.description("what kinda drugs you on?? in fact.. lemme have some of those.")
|
||||
.author(CreateEmbedAuthor::new("help: use join first"));
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if args.len() < 2 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("You must be in a voice channel to use ts!!!!!")
|
||||
.color(16728123);
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let url = args[1].to_string();
|
||||
|
||||
if !url.contains("youtube.com") && !url.contains("youtu.be") {
|
||||
let embed = CreateEmbed::new()
|
||||
.title("You must be in a voice channel to use ts!!!!!")
|
||||
.color(16728123);
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let is_playing = {
|
||||
let handler = handler_lock.lock().await;
|
||||
!handler.queue().is_empty()
|
||||
};
|
||||
|
||||
let client = reqwest::Client::new();
|
||||
let mut src = YoutubeDl::new(client, url.clone());
|
||||
|
||||
match src.aux_metadata().await {
|
||||
Ok(metadata) => {
|
||||
let mut handler = handler_lock.lock().await;
|
||||
let input: Input = src.into();
|
||||
|
||||
let queue = handler.queue().clone();
|
||||
let _ = queue.add_source(input, &mut *handler).await;
|
||||
|
||||
let mut queue_list = PLAYLISTS.entry(guild_id.get()).or_insert_with(Vec::new);
|
||||
queue_list.push(url);
|
||||
|
||||
let current_position = queue_list.len();
|
||||
|
||||
let title = metadata.title.unwrap_or_else(|| "Unknown Track".to_string());
|
||||
let artist = metadata.artist.or(metadata.channel).unwrap_or_else(|| "Unknown Artist".to_string());
|
||||
let thumbnail = metadata.thumbnail.unwrap_or_default();
|
||||
|
||||
let display_name = msg.author.global_name.as_ref().unwrap_or(&msg.author.name);
|
||||
let user_avatar = msg.author.avatar_url().unwrap_or_default();
|
||||
|
||||
let mut author_node = CreateEmbedAuthor::new(format!("Requested by {}", display_name));
|
||||
if !user_avatar.is_empty() {
|
||||
author_node = author_node.icon_url(user_avatar);
|
||||
}
|
||||
|
||||
let mut embed = CreateEmbed::new()
|
||||
.color(12543992)
|
||||
.author(author_node)
|
||||
.field("Title", title, true)
|
||||
.field("Artist", format!("by\n{}", artist), true)
|
||||
.field(" ", "Meowwww :3", false)
|
||||
.field("Position in Queue", format!("#{}", current_position), false);
|
||||
|
||||
if !thumbnail.is_empty() {
|
||||
embed = embed.thumbnail(thumbnail);
|
||||
}
|
||||
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
|
||||
if !is_playing {
|
||||
let start_embed = CreateEmbed::new()
|
||||
.title(":white_check_mark: | Playback started successfully!")
|
||||
.color(3066993);
|
||||
let start_builder = CreateMessage::new().embed(start_embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, start_builder).await;
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("YTDL extraction error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"skip" => {
|
||||
let guild_id = msg.guild_id.unwrap();
|
||||
let manager = songbird::get(&ctx).await.expect("Songbird client not initialized").clone();
|
||||
|
||||
if let Some(handler_lock) = manager.get(guild_id) {
|
||||
let handler = handler_lock.lock().await;
|
||||
|
||||
let queue = handler.queue();
|
||||
if queue.is_empty() {
|
||||
let builder = CreateMessage::new().content("uuuuhhhhhhhh i think i have dementia...");
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
return;
|
||||
}
|
||||
|
||||
let skip_count = args.get(1).and_then(|s| s.parse::<usize>().ok()).unwrap_or(1);
|
||||
|
||||
if skip_count > 1 {
|
||||
if let Some(mut local_queue) = PLAYLISTS.get_mut(&guild_id.get()) {
|
||||
let len = local_queue.len();
|
||||
let skip_limit = std::cmp::min(len, skip_count - 1);
|
||||
local_queue.drain(0..skip_limit);
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title(format!(":wastebasket: | Trashed **{}** songs from queue.", skip_limit));
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = queue.skip();
|
||||
if skip_count <= 1 {
|
||||
let embed = CreateEmbed::new()
|
||||
.title(":fast_forward: | Skipped that mf!! Playing next song in queue..");
|
||||
let builder = CreateMessage::new().embed(embed);
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
}
|
||||
} else {
|
||||
let builder = CreateMessage::new().content("IM NOT IN A VOICE CHANNEL U CHUD");
|
||||
let _ = msg.channel_id.send_message(&ctx.http, builder).await;
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue