Playlist support, new shit
This commit is contained in:
parent
30c0427a32
commit
23f99bbe24
22
Cargo.toml
22
Cargo.toml
|
@ -2,7 +2,7 @@
|
||||||
name = "winter"
|
name = "winter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Joshua Ashton <joshua@froggi.es>"]
|
authors = ["Joshua Ashton <joshua@froggi.es>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
|
@ -13,16 +13,21 @@ base64-stream = "1.2.7"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
[dependencies.songbird]
|
[dependencies.songbird]
|
||||||
features = ["builtin-queue", "yt-dlp"]
|
features = ["builtin-queue"]
|
||||||
version = "0.3.2"
|
git = "https://github.com/serenity-rs/songbird"
|
||||||
|
branch = "current"
|
||||||
|
|
||||||
|
[dependencies.symphonia]
|
||||||
|
version = "0.5.2"
|
||||||
|
features = ["aac", "mp3", "isomp4", "alac"]
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
version = "0.11"
|
version = "0.12"
|
||||||
features = ["client", "standard_framework", "voice", "rustls_backend"]
|
features = ["cache", "framework", "standard_framework", "voice", "http", "rustls_backend"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.0"
|
version = "1"
|
||||||
features = ["macros", "rt-multi-thread", "signal"]
|
features = ["macros", "rt-multi-thread", "signal", "sync"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
@ -40,6 +45,3 @@ features = [
|
||||||
"fast-rng", # Use a faster (but still sufficiently random) RNG
|
"fast-rng", # Use a faster (but still sufficiently random) RNG
|
||||||
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
|
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
|
||||||
]
|
]
|
||||||
|
|
||||||
#[patch.crates-io]
|
|
||||||
#songbird = { git = "https://github.com/Erk-/songbird", branch="do-not-fail-if-new-opcode" }
|
|
||||||
|
|
386
src/main.rs
386
src/main.rs
|
@ -14,58 +14,74 @@ use std::{
|
||||||
sync::{
|
sync::{
|
||||||
Arc,
|
Arc,
|
||||||
},
|
},
|
||||||
collections::HashSet,
|
|
||||||
collections::HashMap, fs,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use reqwest::Client as HttpClient;
|
||||||
|
|
||||||
use serenity::{
|
use serenity::{
|
||||||
async_trait,
|
async_trait,
|
||||||
client::{Client, Context, EventHandler, ClientBuilder},
|
client::{Client, Context, EventHandler},
|
||||||
framework::{
|
framework::{
|
||||||
standard::{
|
standard::{
|
||||||
macros::{command, group, help},
|
macros::{command, group},
|
||||||
help_commands,
|
|
||||||
Args,
|
Args,
|
||||||
CommandResult,
|
CommandResult,
|
||||||
HelpOptions,
|
Configuration,
|
||||||
CommandGroup,
|
|
||||||
},
|
},
|
||||||
StandardFramework,
|
StandardFramework,
|
||||||
},
|
},
|
||||||
http::Http,
|
http::Http,
|
||||||
model::{channel::Message, gateway::Ready, prelude::ChannelId, prelude::UserId, prelude::GuildId},
|
model::{channel::Message, gateway::Ready, prelude::ChannelId},
|
||||||
prelude::{GatewayIntents, Mentionable, TypeMapKey},
|
prelude::{GatewayIntents, Mentionable, TypeMapKey},
|
||||||
Result as SerenityResult,
|
Result as SerenityResult, builder::{CreateEmbed, CreateMessage}, gateway::ShardManager,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serenity::utils::Colour;
|
use serenity::all::standard::HelpOptions;
|
||||||
use serenity::client::bridge::gateway::ShardManager;
|
use serenity::all::standard::CommandGroup;
|
||||||
|
use serenity::all::GuildId;
|
||||||
use tracing::{error, info};
|
use serenity::all::UserId;
|
||||||
|
use serenity::all::ClientBuilder;
|
||||||
|
use serenity::all::Colour;
|
||||||
|
use serenity::all::standard::help_commands;
|
||||||
|
use serenity::all::standard::macros::help;
|
||||||
|
|
||||||
use songbird::{
|
use songbird::{
|
||||||
input::{
|
input::YoutubeDl,
|
||||||
restartable::Restartable, Input,
|
|
||||||
},
|
|
||||||
Event,
|
Event,
|
||||||
EventContext,
|
EventContext,
|
||||||
EventHandler as VoiceEventHandler,
|
EventHandler as VoiceEventHandler,
|
||||||
SerenityInit,
|
SerenityInit,
|
||||||
TrackEvent,
|
TrackEvent, tracks::Track,
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
struct HttpKey;
|
||||||
|
|
||||||
|
impl TypeMapKey for HttpKey {
|
||||||
|
type Value = HttpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShardManagerContainer;
|
||||||
|
|
||||||
|
impl TypeMapKey for ShardManagerContainer {
|
||||||
|
type Value = Arc<ShardManager>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Handler;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tracing::error;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use base64_stream::FromBase64Writer;
|
use base64_stream::FromBase64Writer;
|
||||||
|
|
||||||
use tokio::sync::Mutex;
|
use tokio::{sync::Mutex, process::Command};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
struct Handler;
|
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn ready(&self, _: Context, ready: Ready) {
|
async fn ready(&self, _: Context, ready: Ready) {
|
||||||
|
@ -73,16 +89,11 @@ impl EventHandler for Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ShardManagerContainer;
|
|
||||||
|
|
||||||
impl TypeMapKey for ShardManagerContainer {
|
|
||||||
type Value = Arc<Mutex<ShardManager>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[group]
|
#[group]
|
||||||
#[commands(
|
#[commands(
|
||||||
leave, mute, play, skip, stop, ping, unmute, volume, vox, chaos, restart, seek, tts, tts_list
|
leave, mute, play, skip, stop, ping, unmute, volume, vox, chaos, restart, seek, tts, tts_list
|
||||||
)]
|
)]
|
||||||
|
|
||||||
struct General;
|
struct General;
|
||||||
|
|
||||||
#[help]
|
#[help]
|
||||||
|
@ -208,6 +219,12 @@ pub async fn winter_get(ctx: &Context) -> Option<Arc<Mutex<Winter>>> {
|
||||||
|
|
||||||
/////////
|
/////////
|
||||||
|
|
||||||
|
async fn get_http_client(ctx: &Context) -> HttpClient {
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
data.get::<HttpKey>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Guaranteed to exist in the typemap.")
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -216,10 +233,8 @@ async fn main() {
|
||||||
// Configure the client with your Discord bot token in the environment.
|
// Configure the client with your Discord bot token in the environment.
|
||||||
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
|
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
|
||||||
|
|
||||||
let framework = StandardFramework::new()
|
let framework = StandardFramework::new().group(&GENERAL_GROUP);
|
||||||
.configure(|c| c.prefix("~"))
|
framework.configure(Configuration::new().prefix("~"));
|
||||||
.group(&GENERAL_GROUP)
|
|
||||||
.help(&MY_HELP);
|
|
||||||
|
|
||||||
let intents = GatewayIntents::non_privileged()
|
let intents = GatewayIntents::non_privileged()
|
||||||
| GatewayIntents::MESSAGE_CONTENT;
|
| GatewayIntents::MESSAGE_CONTENT;
|
||||||
|
@ -229,6 +244,7 @@ async fn main() {
|
||||||
.framework(framework)
|
.framework(framework)
|
||||||
.register_songbird()
|
.register_songbird()
|
||||||
.register_winter()
|
.register_winter()
|
||||||
|
.type_map_insert::<HttpKey>(HttpClient::new())
|
||||||
.await
|
.await
|
||||||
.expect("Err creating client");
|
.expect("Err creating client");
|
||||||
|
|
||||||
|
@ -241,7 +257,7 @@ async fn main() {
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler");
|
tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler");
|
||||||
shard_manager.lock().await.shutdown_all().await;
|
shard_manager.shutdown_all().await;
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(why) = client.start().await {
|
if let Err(why) = client.start().await {
|
||||||
|
@ -250,13 +266,15 @@ async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_joined(ctx: &Context, msg: &Message) -> bool {
|
async fn ensure_joined(ctx: &Context, msg: &Message) -> bool {
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let (guild_id, channel_id) = {
|
||||||
let guild_id = guild.id;
|
let guild = msg.guild(&ctx.cache).unwrap();
|
||||||
|
let channel_id = guild
|
||||||
|
.voice_states
|
||||||
|
.get(&msg.author.id)
|
||||||
|
.and_then(|voice_state| voice_state.channel_id);
|
||||||
|
|
||||||
let channel_id = guild
|
(guild.id, channel_id)
|
||||||
.voice_states
|
};
|
||||||
.get(&msg.author.id)
|
|
||||||
.and_then(|voice_state| voice_state.channel_id);
|
|
||||||
|
|
||||||
let connect_to = match channel_id {
|
let connect_to = match channel_id {
|
||||||
Some(channel) => channel,
|
Some(channel) => channel,
|
||||||
|
@ -289,9 +307,7 @@ async fn ensure_joined(ctx: &Context, msg: &Message) -> bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (handler_lock, success) = manager.join(guild_id, connect_to).await;
|
if let Ok(handler_lock) = manager.join(guild_id, connect_to).await {
|
||||||
|
|
||||||
if let Ok(_channel) = success {
|
|
||||||
check_msg(
|
check_msg(
|
||||||
msg.channel_id
|
msg.channel_id
|
||||||
.say(&ctx.http, &format!("Joined {}", connect_to.mention()))
|
.say(&ctx.http, &format!("Joined {}", connect_to.mention()))
|
||||||
|
@ -382,8 +398,7 @@ impl VoiceEventHandler for TrackEndNotifier {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
|
async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -411,8 +426,7 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn mute(ctx: &Context, msg: &Message) -> CommandResult {
|
async fn mute(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -472,24 +486,42 @@ impl VoiceEventHandler for SongEndNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct YtDlpOutput {
|
||||||
|
pub artist: Option<String>,
|
||||||
|
pub album: Option<String>,
|
||||||
|
pub channel: Option<String>,
|
||||||
|
pub duration: Option<f64>,
|
||||||
|
pub filesize: Option<u64>,
|
||||||
|
pub http_headers: Option<HashMap<String, String>>,
|
||||||
|
pub release_date: Option<String>,
|
||||||
|
pub thumbnail: Option<String>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub track: Option<String>,
|
||||||
|
pub upload_date: Option<String>,
|
||||||
|
pub uploader: Option<String>,
|
||||||
|
pub url: String,
|
||||||
|
pub webpage_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
let guild_id = msg.guild_id.unwrap();
|
||||||
|
|
||||||
let url = args.rest().to_string();
|
let url = args.rest().to_string();
|
||||||
if url.is_empty() {
|
if url.is_empty() {
|
||||||
reply(&ctx, &msg, "Tell me what you want!", "You must provide a URL or search term for me to play video or audio!", None, false).await;
|
reply(&ctx, &msg, "Tell me what you want!", "You must provide a URL or search term for me to play video or audio!", None, false).await;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let http_client = get_http_client(ctx).await;
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
.expect("Songbird Voice client placed in at initialisation.")
|
.expect("Songbird Voice client placed in at initialisation.")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
|
||||||
let winter_lock = winter_get(ctx)
|
let winter_lock = winter_get(ctx)
|
||||||
.await
|
.await
|
||||||
.expect("Winter placed in at initialisation.")
|
.expect("Winter placed in at initialisation.")
|
||||||
|
@ -501,71 +533,87 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let do_search = !url.starts_with("http");
|
||||||
|
|
||||||
|
let sources = if do_search {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
vec.push(YoutubeDl::new_search(http_client, url));
|
||||||
|
vec
|
||||||
|
} else {
|
||||||
|
let ytdl_args = [
|
||||||
|
"-j",
|
||||||
|
url.as_str(),
|
||||||
|
"--flat-playlist",
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut output = Command::new("yt-dlp")
|
||||||
|
.args(ytdl_args)
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
println!("Fuck! Couldn't run yt-dlp");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let out = output
|
||||||
|
.stdout
|
||||||
|
.split_mut(|&b| b == b'\n')
|
||||||
|
.filter_map(|x| (!x.is_empty()).then(|| serde_json::from_slice(x)))
|
||||||
|
.collect::<Result<Vec<YtDlpOutput>, _>>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
for playlist_src in out {
|
||||||
|
vec.push(YoutubeDl::new(http_client.clone(), playlist_src.url));
|
||||||
|
}
|
||||||
|
vec
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(handler_lock) = manager.get(guild_id) {
|
if let Some(handler_lock) = manager.get(guild_id) {
|
||||||
let mut handler = handler_lock.lock().await;
|
let mut handler = handler_lock.lock().await;
|
||||||
|
|
||||||
// Here, we use lazy restartable sources to make sure that we don't pay
|
for source in sources {
|
||||||
// for decoding, playback on tracks which aren't actually live yet.
|
let mut input = songbird::input::Input::from(source);
|
||||||
let source : Restartable;
|
|
||||||
|
|
||||||
if url.starts_with("http:") || url.starts_with("https:") {
|
let aux_metadata = input.aux_metadata().await.unwrap();
|
||||||
source = match Restartable::ytdl(url, false).await {
|
|
||||||
Ok(source) => source,
|
|
||||||
Err(why) => {
|
|
||||||
println!("Err starting source: {:?}", why);
|
|
||||||
reply(&ctx, &msg, "Oh no!", &format!("Error playing video. Reason: {:?}", why), None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
source = match Restartable::ytdl_search(url, false).await {
|
|
||||||
Ok(source) => source,
|
|
||||||
Err(why) => {
|
|
||||||
println!("Err starting source: {:?}", why);
|
|
||||||
reply(&ctx, &msg, "Oh no!", &format!("Error playing video. Reason: {:?}", why), None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = Input::from(source);
|
let title = aux_metadata.title.clone().unwrap_or("Unknown Title".to_string());
|
||||||
let title = input.metadata.title.clone().unwrap_or("Unknown Title".to_string());
|
let artist = aux_metadata.artist.clone().unwrap_or("Unknown Artist".to_string());
|
||||||
let artist = input.metadata.artist.clone().unwrap_or("Unknown Artist".to_string());
|
let thumbnail = aux_metadata.thumbnail.clone();
|
||||||
let thumbnail = input.metadata.thumbnail.clone();
|
let mut duration = "Unknown duration".to_string();
|
||||||
let mut duration = "Unknown duration".to_string();
|
if aux_metadata.duration.is_some() {
|
||||||
if input.metadata.duration.is_some() {
|
let meta_duration = aux_metadata.duration.unwrap();
|
||||||
let meta_duration = input.metadata.duration.unwrap();
|
|
||||||
|
|
||||||
let seconds = meta_duration.as_secs() % 60;
|
let seconds = meta_duration.as_secs() % 60;
|
||||||
let minutes = (meta_duration.as_secs() / 60) % 60;
|
let minutes = (meta_duration.as_secs() / 60) % 60;
|
||||||
let hours = (meta_duration.as_secs() / 60) / 60;
|
let hours = (meta_duration.as_secs() / 60) / 60;
|
||||||
duration = format!("{:02}:{:02}:{:02}", hours, minutes, seconds).to_string();
|
duration = format!("{:02}:{:02}:{:02}", hours, minutes, seconds).to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut track, _handle) = songbird::tracks::create_player(input);
|
let track = songbird::tracks::Track::new(input)
|
||||||
track.set_volume(winter.options.get_volume(guild_id));
|
.volume(winter.options.get_volume(guild_id));
|
||||||
handler.enqueue(track);
|
handler.enqueue(track).await;
|
||||||
|
|
||||||
|
let mut embed = CreateEmbed::new()
|
||||||
|
.title(title.clone())
|
||||||
|
.field("Arist", artist, true)
|
||||||
|
.field("Duration", duration, true)
|
||||||
|
.field("Queue Position", &format!("{:02}", handler.queue().len()), true)
|
||||||
|
.color(Colour::from_rgb(202,255,239));
|
||||||
|
|
||||||
let msg = msg
|
if thumbnail.is_some() {
|
||||||
.channel_id
|
embed = embed.thumbnail(thumbnail.unwrap());
|
||||||
.send_message(&ctx.http, |m| {
|
}
|
||||||
m.embed(|e| {
|
|
||||||
e.title(title.clone());
|
|
||||||
if thumbnail.is_some() {
|
|
||||||
e.thumbnail(thumbnail.unwrap());
|
|
||||||
}
|
|
||||||
e.field("Artist", artist, true);
|
|
||||||
e.field("Duration", duration, true);
|
|
||||||
e.field("Queue Position", &format!("{:02}", handler.queue().len()), true);
|
|
||||||
e.color(Colour::from_rgb(202,255,239));
|
|
||||||
e
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
if let Err(why) = msg {
|
let builder = CreateMessage::new().embed(embed);
|
||||||
println!("Error sending message: {:?}", why);
|
|
||||||
|
let msg = msg.channel_id.send_message(&ctx, builder).await;
|
||||||
|
|
||||||
|
if let Err(why) = msg {
|
||||||
|
println!("Error sending message: {:?}", why);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -575,6 +623,8 @@ async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
|
let guild_id = msg.guild_id.unwrap();
|
||||||
|
|
||||||
let url = match args.single::<String>() {
|
let url = match args.single::<String>() {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -594,8 +644,7 @@ async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let http_client = get_http_client(ctx).await;
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -614,22 +663,21 @@ async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let do_search = !url.starts_with("http");
|
||||||
|
|
||||||
if let Some(handler_lock) = manager.get(guild_id) {
|
if let Some(handler_lock) = manager.get(guild_id) {
|
||||||
let mut handler = handler_lock.lock().await;
|
let mut handler = handler_lock.lock().await;
|
||||||
|
|
||||||
let source = match songbird::ytdl(&url).await {
|
let src = if do_search {
|
||||||
Ok(source) => source,
|
YoutubeDl::new_search(http_client, url)
|
||||||
Err(why) => {
|
} else {
|
||||||
println!("Err starting source: {:?}", why);
|
YoutubeDl::new(http_client, url)
|
||||||
reply(&ctx, &msg, "Oh no!", &format!("Error playing video. Reason: {:?}", why), None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
check_msg(msg.reply(&ctx.http, "Added song to chaos mode.").await);
|
check_msg(msg.reply(&ctx.http, "Added song to chaos mode.").await);
|
||||||
|
|
||||||
let (mut track, _handle) = songbird::tracks::create_player(source.into());
|
let track = songbird::tracks::Track::new(src.into())
|
||||||
track.set_volume(winter.options.get_volume(guild_id));
|
.volume(winter.options.get_volume(guild_id));
|
||||||
handler.play(track);
|
handler.play(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -639,6 +687,8 @@ async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn vox(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
async fn vox(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
let guild_id = msg.guild_id.unwrap();
|
||||||
|
|
||||||
let line = args.rest();
|
let line = args.rest();
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
check_msg(
|
check_msg(
|
||||||
|
@ -651,14 +701,19 @@ async fn vox(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
|
||||||
let words = line.split(" ");
|
let words = line.split(" ");
|
||||||
|
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
.expect("Songbird Voice client placed in at initialisation.")
|
.expect("Songbird Voice client placed in at initialisation.")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
|
||||||
|
let winter_lock = winter_get(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Winter placed in at initialisation.")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let winter = winter_lock.lock().await;
|
||||||
|
|
||||||
if !ensure_joined(ctx, msg).await {
|
if !ensure_joined(ctx, msg).await {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -671,19 +726,12 @@ async fn vox(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
if word.chars().all(|x| x.is_alphanumeric()) {
|
if word.chars().all(|x| x.is_alphanumeric()) {
|
||||||
let vox_path = format!("./assets/vox/{}.wav", word);
|
let vox_path = format!("./assets/vox/{}.wav", word);
|
||||||
|
|
||||||
let source = match songbird::ffmpeg(&vox_path).await {
|
let source = songbird::input::File::new(vox_path);
|
||||||
Ok(source) => source,
|
let track = Track::new(source.into())
|
||||||
Err(why) => {
|
.volume(winter.options.get_volume(guild_id));
|
||||||
println!("Err starting source: {:?}", why);
|
handler.enqueue(track).await;
|
||||||
reply(&ctx, &msg, "Oh no!", &format!("Error playing video. Reason: {:?}", why), None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
handler.enqueue_source(source.into());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
check_msg(
|
check_msg(
|
||||||
msg.channel_id
|
msg.channel_id
|
||||||
|
@ -770,7 +818,7 @@ use rand::seq::SliceRandom;
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn tts_list(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
async fn tts_list(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
let mut voice_list : String = "".to_string();
|
let mut voice_list : String = "".to_string();
|
||||||
for voice in TIKTOK_VOICES {
|
for voice in TIKTOK_VOICES {
|
||||||
voice_list += voice;
|
voice_list += voice;
|
||||||
|
@ -783,6 +831,8 @@ async fn tts_list(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
let guild_id = msg.guild_id.unwrap();
|
||||||
|
|
||||||
let mut line = args.rest();
|
let mut line = args.rest();
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
check_msg(
|
check_msg(
|
||||||
|
@ -810,9 +860,6 @@ async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
.expect("Songbird Voice client placed in at initialisation.")
|
.expect("Songbird Voice client placed in at initialisation.")
|
||||||
|
@ -862,17 +909,10 @@ async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
writer.write_all(response.data.as_bytes()).unwrap();
|
writer.write_all(response.data.as_bytes()).unwrap();
|
||||||
writer.flush().unwrap();
|
writer.flush().unwrap();
|
||||||
|
|
||||||
let source = match songbird::ffmpeg(file_path).await {
|
let source = songbird::input::File::new(file_path);
|
||||||
Ok(source) => source,
|
let track = Track::new(source.into())
|
||||||
Err(why) => {
|
.volume(winter.options.get_volume(guild_id));
|
||||||
println!("Err starting source: {:?}", why);
|
|
||||||
reply(&ctx, &msg, "Oh no!", &format!("Error playing tts. Reason: {:?}", why), None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let (mut track, _handle) = songbird::tracks::create_player(source.into());
|
|
||||||
track.set_volume(winter.options.get_volume(guild_id));
|
|
||||||
handler.play(track);
|
handler.play(track);
|
||||||
|
|
||||||
check_msg(
|
check_msg(
|
||||||
|
@ -891,8 +931,7 @@ async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -922,8 +961,7 @@ async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn stop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
async fn stop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -947,8 +985,7 @@ async fn stop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||||
#[command]
|
#[command]
|
||||||
#[only_in(guilds)]
|
#[only_in(guilds)]
|
||||||
async fn unmute(ctx: &Context, msg: &Message) -> CommandResult {
|
async fn unmute(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
.expect("Songbird Voice client placed in at initialisation.")
|
.expect("Songbird Voice client placed in at initialisation.")
|
||||||
|
@ -988,8 +1025,7 @@ async fn volume(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
let volume_human = volume_human_eval.unwrap() as f32;
|
let volume_human = volume_human_eval.unwrap() as f32;
|
||||||
let volume = volume_human / 100.0;
|
let volume = volume_human / 100.0;
|
||||||
|
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -1037,8 +1073,7 @@ async fn seek(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
|
|
||||||
let seek = std::time::Duration::from_secs_f32(seek_human);
|
let seek = std::time::Duration::from_secs_f32(seek_human);
|
||||||
|
|
||||||
let guild = msg.guild(&ctx.cache).unwrap();
|
let guild_id = msg.guild_id.unwrap();
|
||||||
let guild_id = guild.id;
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
let manager = songbird::get(ctx)
|
||||||
.await
|
.await
|
||||||
|
@ -1049,14 +1084,7 @@ async fn seek(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||||
let handler = handler_lock.lock().await;
|
let handler = handler_lock.lock().await;
|
||||||
|
|
||||||
for (_, track) in handler.queue().current_queue().iter().enumerate() {
|
for (_, track) in handler.queue().current_queue().iter().enumerate() {
|
||||||
if !track.is_seekable() {
|
let _success = track.seek(seek);
|
||||||
reply(&ctx, &msg, "I can't do that!", "This track is not seekable. (Livestream, etc)", None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
if !track.seek_time(seek).is_ok() {
|
|
||||||
reply(&ctx, &msg, "I can't do that!", "Failed to set current position", None, true).await;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1075,7 +1103,7 @@ async fn restart(ctx: &Context, msg: &Message) -> CommandResult {
|
||||||
|
|
||||||
if let Some(manager) = data.get::<ShardManagerContainer>() {
|
if let Some(manager) = data.get::<ShardManagerContainer>() {
|
||||||
msg.reply(ctx, "Shutting down!").await?;
|
msg.reply(ctx, "Shutting down!").await?;
|
||||||
manager.lock().await.shutdown_all().await;
|
manager.shutdown_all().await;
|
||||||
} else {
|
} else {
|
||||||
msg.reply(ctx, "There was a problem getting the shard manager").await?;
|
msg.reply(ctx, "There was a problem getting the shard manager").await?;
|
||||||
|
|
||||||
|
@ -1093,23 +1121,23 @@ fn check_msg(result: SerenityResult<Message>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reply<S :Into<String>>(ctx: &Context, context_msg: &Message, title: S, desc: S, image: Option<S>, error: bool) {
|
async fn reply<S :Into<String>>(ctx: &Context, context_msg: &Message, title: S, desc: S, image: Option<S>, error: bool) {
|
||||||
let msg = context_msg
|
let mut embed = CreateEmbed::new()
|
||||||
.channel_id
|
.title(title.into())
|
||||||
.send_message(&ctx.http, |m| {
|
.description(desc.into());
|
||||||
m.embed(|e| {
|
|
||||||
e.title(title.into()).description(desc.into());
|
if image.is_some() {
|
||||||
if image.is_some() {
|
embed = embed.image(image.unwrap().into());
|
||||||
e.image(image.unwrap().into());
|
}
|
||||||
}
|
|
||||||
if error {
|
if error {
|
||||||
e.color(Colour::from_rgb(255,218,218));
|
embed = embed.color(Colour::from_rgb(255,218,218));
|
||||||
} else {
|
} else {
|
||||||
e.color(Colour::from_rgb(223,255,198));
|
embed = embed.color(Colour::from_rgb(223,255,198));
|
||||||
}
|
}
|
||||||
e
|
|
||||||
})
|
let builder = CreateMessage::new().embed(embed);
|
||||||
})
|
|
||||||
.await;
|
let msg = context_msg.channel_id.send_message(&ctx, builder).await;
|
||||||
|
|
||||||
if let Err(why) = msg {
|
if let Err(why) = msg {
|
||||||
println!("Error sending message: {:?}", why);
|
println!("Error sending message: {:?}", why);
|
||||||
|
|
Loading…
Reference in New Issue