Playlist support, new shit

This commit is contained in:
Joshua Ashton 2023-12-26 10:53:36 +00:00
parent 30c0427a32
commit 23f99bbe24
2 changed files with 219 additions and 189 deletions

View File

@ -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" }

View File

@ -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);