Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
40a576ff0b |
|||
|
2564c154cf |
|||
|
c9d3b44026 |
|||
|
558625b4ec |
10 changed files with 194 additions and 3583 deletions
|
|
@ -10,7 +10,7 @@ env:
|
|||
SHORT_SHA: ${{ github.sha }}
|
||||
|
||||
jobs:
|
||||
splatie:
|
||||
spacebot:
|
||||
runs-on: debian-trixie
|
||||
|
||||
steps:
|
||||
|
|
|
|||
2973
Cargo.lock
generated
2973
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "professor"
|
||||
name = "spacebot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
|
@ -19,11 +19,8 @@ panic = "abort"
|
|||
dotenv = "0.15.0"
|
||||
regex = "1.11.1"
|
||||
once_cell = "1.20.3"
|
||||
sarcastic = { git = "https://github.com/DJMrTV/bored-yaml.git" }
|
||||
chrono = "0.4.39"
|
||||
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.6.0", features = ["aac", "mp3", "isomp4", "alac", "flac", "pcm", "vorbis"] }
|
||||
|
||||
|
|
|
|||
11
Dockerfile
11
Dockerfile
|
|
@ -27,15 +27,12 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|||
--mount=type=cache,target=/app/target \
|
||||
cargo build --release --target x86_64-unknown-linux-musl && \
|
||||
mkdir -p /app/dist && \
|
||||
cp /app/target/x86_64-unknown-linux-musl/release/professor /app/dist/
|
||||
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/spacebot /app/dist/
|
||||
|
||||
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
|
||||
RUN apk add --no-cache python3 libstdc++
|
||||
|
||||
COPY --from=builder /app/dist/professor ./splatie
|
||||
ENTRYPOINT ["./splatie"]
|
||||
COPY --from=builder /app/dist/spacebot ./spacebot
|
||||
ENTRYPOINT ["./spacebot"]
|
||||
31
build.rs
31
build.rs
|
|
@ -1,28 +1,7 @@
|
|||
use std::fmt::Write;
|
||||
use std::{env, fs};
|
||||
use std::fs::FileType;
|
||||
use std::iter::Map;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CategoryInfo{
|
||||
name: String,
|
||||
description: String,
|
||||
system: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ErrorInfo{
|
||||
name: String,
|
||||
message: String,
|
||||
short_description: String,
|
||||
long_description: String,
|
||||
short_solution: String,
|
||||
long_solution: String,
|
||||
support_link: String,
|
||||
}
|
||||
|
||||
fn main(){
|
||||
println!("cargo:rerun-if-changed=./error-codes");
|
||||
|
||||
|
|
@ -173,13 +152,6 @@ fn main(){
|
|||
let long_solution = long_solution.replace("\"", "\\\"")
|
||||
.replace("\\n", "\n");
|
||||
|
||||
let Value::String(support_link) = &error["support_link"] else {
|
||||
panic!("unable to parse error json");
|
||||
};
|
||||
|
||||
let support_link = support_link.replace("\"", "\\\"")
|
||||
.replace("\\n", "\n");
|
||||
|
||||
code.write_str(
|
||||
&format!("\
|
||||
({},{}) => super::ErrorInfo{{\
|
||||
|
|
@ -189,9 +161,8 @@ fn main(){
|
|||
long_description: \"{}\",
|
||||
short_solution: \"{}\",
|
||||
long_solution: \"{}\",
|
||||
support_link: \"{}\",
|
||||
}},\
|
||||
", category_num, error_num, name, message, short_description, long_description, short_solution, long_solution, support_link)
|
||||
", category_num, error_num, name, message, short_description, long_description, short_solution, long_solution)
|
||||
).expect("unable to write");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use serenity::all::CreateActionRow::Buttons;
|
||||
use serenity::all::{
|
||||
Context, CreateActionRow, CreateButton, CreateEmbed, CreateEmbedFooter, CreateMessage,
|
||||
EditInteractionResponse, EditMessage, EventHandler, Http, Interaction, Message,
|
||||
Context, CreateActionRow, CreateButton, CreateEmbed, CreateMessage, EditMessage, EventHandler, Http, Interaction, Message,
|
||||
};
|
||||
use serenity::async_trait;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
|
@ -38,7 +35,6 @@ struct ErrorInfo {
|
|||
long_description: &'static str,
|
||||
short_solution: &'static str,
|
||||
long_solution: &'static str,
|
||||
support_link: &'static str,
|
||||
}
|
||||
|
||||
impl Default for ErrorInfo {
|
||||
|
|
@ -50,15 +46,12 @@ impl Default for ErrorInfo {
|
|||
long_solution: "unknown",
|
||||
short_description: "unknown",
|
||||
short_solution: "unknown",
|
||||
support_link: "unknown",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static ERROR_CODE_REGEX: Lazy<Regex> =
|
||||
Lazy::new(|| Regex::new("\\b\\d\\d\\d-\\d\\d\\d\\d\\b").expect("invalid regex"));
|
||||
static WORD_BOUNDARY_START: Lazy<Regex> = Lazy::new(|| Regex::new("^\\b").expect("invalid regex"));
|
||||
static WORD_BOUNDARY_END: Lazy<Regex> = Lazy::new(|| Regex::new("\\b$").expect("invalid regex"));
|
||||
|
||||
pub struct ErrorCodeHandler;
|
||||
|
||||
|
|
@ -133,7 +126,7 @@ impl EventHandler for ErrorCodeHandler {
|
|||
if let Some(err_code) = ERROR_CODE_REGEX.find(&msg.content) {
|
||||
let str_code = err_code.as_str();
|
||||
|
||||
let Some(message) = create_error_explain_message(str_code, true) else {
|
||||
let Some(_message) = create_error_explain_message(str_code, true) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
|
|
|||
194
src/fest_fax.rs
194
src/fest_fax.rs
|
|
@ -1,194 +0,0 @@
|
|||
use sarcastic::byaml::{Byaml, Node};
|
||||
use serenity::all::{ChannelId, Colour, Command, CommandId, CommandOptionType, Context, CreateCommand, CreateCommandOption, CreateEmbed, CreateEmbedAuthor, CreateInputText, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, CreateQuickModal, EventHandler, InputTextStyle, Interaction, Ready, ResolvedValue, Timestamp};
|
||||
use serenity::all::ResolvedValue::Attachment;
|
||||
use serenity::async_trait;
|
||||
use tokio::sync::OnceCell;
|
||||
use std::fmt::Write;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FestFaxHandler{
|
||||
command_id: OnceCell<CommandId>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TeamData{
|
||||
color: [f32; 4],
|
||||
name: String
|
||||
}
|
||||
|
||||
fn read_team_data(node: &Node<'_>) -> Option<TeamData>{
|
||||
let Node::DictionaryNode(node) = node else{
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(Node::StringValue(color)) = node.find("Color") else{
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(color): Option<[f32; 4]> = color.split(',')
|
||||
.filter_map(|v| v.parse::<f32>().ok())
|
||||
.collect::<Vec<f32>>().try_into().ok() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(Node::DictionaryNode(names)) = node.find("Name") else{
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(Node::StringValue(name)) = names.find("EUen") else{
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(TeamData{
|
||||
name: name.to_string(),
|
||||
color
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for FestFaxHandler {
|
||||
async fn ready(&self, ctx: Context, data_about_bot: Ready) {
|
||||
let command = CreateCommand::new("process-fest-in-fax-machine")
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::String, "fes_tex_url", "Texture url")
|
||||
.required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::String, "details", "Details")
|
||||
.required(true))
|
||||
.description("Announce a fest!");
|
||||
|
||||
let cmd = Command::create_global_command(&ctx.http, command).await.expect("unable to register command");
|
||||
|
||||
self.command_id.set(cmd.id).ok();
|
||||
}
|
||||
|
||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||
let Interaction::Command(command_interaction) = interaction else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(command_id) = self.command_id.get() else{
|
||||
return;
|
||||
};
|
||||
|
||||
if command_interaction.data.id != *command_id{
|
||||
return
|
||||
}
|
||||
|
||||
if command_interaction.user.id != 400291421799710720 {
|
||||
command_interaction.create_response(
|
||||
&ctx.http,
|
||||
CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content("nuh uh(cnd still isnt up so im using this to test for perms lol)")
|
||||
)).await.ok();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
let options = command_interaction.data.options();
|
||||
|
||||
|
||||
let Ok(byaml_data) = reqwest::get("https://dl.app.spfn.cc/p01/data/1/Festival.byaml").await else{
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(byaml_data) = byaml_data.bytes().await else{
|
||||
return;
|
||||
};
|
||||
|
||||
macro_rules! byaml_handle_error {
|
||||
($($stuff:tt)*) => {
|
||||
match $($stuff)*{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
command_interaction.create_response(&ctx.http, CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.content(format!("an error ocurred whilest reading the byaml: {}", e)))).await.ok();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let byaml = byaml_handle_error!(Byaml::new(&byaml_data));
|
||||
let root_node = byaml_handle_error!(byaml.get_root_node());
|
||||
|
||||
let Node::DictionaryNode(dict) = root_node else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(Node::ArrayNode(teams)) = dict.find("Teams") else {
|
||||
return
|
||||
};
|
||||
|
||||
let mut iter = teams.into_iter()
|
||||
.filter_map(|v| v.ok());
|
||||
|
||||
let Some(alpha) = iter.next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(bravo) = iter.next() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(alpha) = read_team_data(&alpha) else{
|
||||
return;
|
||||
};
|
||||
let Some(bravo) = read_team_data(&bravo) else{
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
let Some(Node::DictionaryNode(times)) = dict.find("Time") else {
|
||||
return
|
||||
};
|
||||
|
||||
let Some(Node::StringValue(start_time)) = times.find("Start") else {
|
||||
return
|
||||
};
|
||||
|
||||
let Ok(start_time) = DateTime::parse_from_rfc3339(start_time) else{
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(Node::StringValue(end_time)) = times.find("End") else {
|
||||
return
|
||||
};
|
||||
|
||||
let Ok(end_time) = DateTime::parse_from_rfc3339(end_time) else{
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(fest_image) = options.iter().find(|o| o.name == "fes_tex_url") else{
|
||||
return;
|
||||
};
|
||||
|
||||
let ResolvedValue::String(fest_image) = fest_image.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
let Some(fest_file) = options.iter().find(|o| o.name == "details") else{
|
||||
return;
|
||||
};
|
||||
|
||||
let ResolvedValue::String(details) = fest_file.value else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
||||
command_interaction.create_response(&ctx.http, CreateInteractionResponse::Message(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(
|
||||
CreateEmbed::new()
|
||||
.image(fest_image)
|
||||
.field("Theme", format!("{} vs {}", alpha.name, bravo.name), false)
|
||||
.field("Starts", format!("<t:{}:R>", start_time.timestamp()), false)
|
||||
.field("End", format!("<t:{}:f>", end_time.timestamp()), true)
|
||||
.field("Details", details, true)
|
||||
)
|
||||
)
|
||||
).await.ok();
|
||||
}
|
||||
}
|
||||
37
src/main.rs
37
src/main.rs
|
|
@ -1,51 +1,20 @@
|
|||
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() {
|
||||
println!("splattie starting!");
|
||||
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);
|
||||
}
|
||||
}
|
||||
println!("spacebot starting!");
|
||||
|
||||
dotenv::dotenv().ok();
|
||||
|
||||
let token = env::var("SPACEBOT_TOKEN").expect("Token not specified");
|
||||
let intents = GatewayIntents::GUILD_MESSAGES
|
||||
| GatewayIntents::MESSAGE_CONTENT
|
||||
| GatewayIntents::GUILDS
|
||||
| GatewayIntents::GUILD_VOICE_STATES;
|
||||
| GatewayIntents::GUILDS;
|
||||
|
||||
let mut client = Client::builder(&token, intents)
|
||||
.event_handler(error_codes::ErrorCodeHandler)
|
||||
|
|
@ -55,8 +24,6 @@ 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");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
use serenity::all::{ActivityData, ActivityType, ChannelId, Colour, Command, CommandId, Context, CreateCommand, CreateEmbed, CreateEmbedAuthor, CreateInputText, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, CreateQuickModal, EventHandler, Guild, GuildId, InputTextStyle, Interaction, Message, Ready, Timestamp};
|
||||
use serenity::all::{ChannelId, Colour, Command, CommandId, Context, CreateCommand, CreateEmbed, CreateEmbedAuthor, CreateInteractionResponse, CreateInteractionResponseMessage, CreateMessage, CreateQuickModal, EventHandler, Interaction, Ready, Timestamp};
|
||||
use serenity::async_trait;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
|
|
@ -10,7 +10,7 @@ pub struct MiiverseModApplicationHandler{
|
|||
|
||||
#[async_trait]
|
||||
impl EventHandler for MiiverseModApplicationHandler {
|
||||
async fn ready(&self, ctx: Context, data_about_bot: Ready) {
|
||||
async fn ready(&self, ctx: Context, _data_about_bot: Ready) {
|
||||
let command = CreateCommand::new("mod-application")
|
||||
.description("Apply for moderator");
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ impl EventHandler for MiiverseModApplicationHandler {
|
|||
|
||||
let emergency_channel = ChannelId::new(1504018819071610970);
|
||||
|
||||
let mut author = CreateEmbedAuthor::from(response.interaction.user);
|
||||
let author = CreateEmbedAuthor::from(response.interaction.user);
|
||||
|
||||
let embed = CreateEmbed::new()
|
||||
.title("Moderator Application")
|
||||
|
|
|
|||
271
src/music.rs
271
src/music.rs
|
|
@ -1,271 +0,0 @@
|
|||
//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