Winter/src/main.rs

1145 lines
32 KiB
Rust

//! Example demonstrating how to make use of individual track audio events,
//! and how to use the `TrackQueue` system.
//!
//! Requires the "cache", "standard_framework", and "voice" features be enabled in your
//! Cargo.toml, like so:
//!
//! ```toml
//! [dependencies.serenity]
//! git = "https://github.com/serenity-rs/serenity.git"
//! features = ["cache", "framework", "standard_framework", "voice"]
//! ```
use std::{
env,
sync::{
Arc,
},
};
use reqwest::Client as HttpClient;
use serenity::{
async_trait,
client::{Client, Context, EventHandler},
framework::{
standard::{
macros::{command, group},
Args,
CommandResult,
Configuration,
},
StandardFramework,
},
http::Http,
model::{channel::Message, gateway::Ready, prelude::ChannelId},
prelude::{GatewayIntents, Mentionable, TypeMapKey},
Result as SerenityResult, builder::{CreateEmbed, CreateMessage}, gateway::ShardManager,
};
use serenity::all::standard::HelpOptions;
use serenity::all::standard::CommandGroup;
use serenity::all::GuildId;
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::{
input::YoutubeDl,
Event,
EventContext,
EventHandler as VoiceEventHandler,
SerenityInit,
TrackEvent, tracks::Track,
};
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::io::Write;
use std::collections::HashSet;
use std::collections::HashMap;
use base64_stream::FromBase64Writer;
use tokio::{sync::Mutex, process::Command};
use uuid::Uuid;
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
#[group]
#[commands(
leave, mute, play, skip, stop, ping, unmute, volume, vox, chaos, restart, seek, tts, tts_list
)]
struct General;
#[help]
async fn my_help(
context: &Context,
msg: &Message,
args: Args,
help_options: &'static HelpOptions,
groups: &[&'static CommandGroup],
owners: HashSet<UserId>,
) -> CommandResult {
let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
Ok(())
}
/////////
#[derive(Serialize, Deserialize, Debug)]
struct WinterGuildOptions {
volume : f32,
}
impl Default for WinterGuildOptions {
fn default() -> WinterGuildOptions {
WinterGuildOptions {
volume: 1.0,
}
}
}
#[derive(Serialize, Deserialize, Default, Debug)]
struct WinterOptions {
guild_options : HashMap<GuildId, WinterGuildOptions>,
}
impl WinterOptions {
fn set_volume(&mut self, id: GuildId, volume: f32) {
self.guild_options.entry(id).or_default().volume = volume;
self.save_to_file();
}
fn get_volume(&self, id: GuildId) -> f32 {
if let Some(options) = self.guild_options.get(&id) {
options.volume
} else {
WinterGuildOptions::default().volume
}
}
fn load_from_file() -> WinterOptions {
if let Ok(data) = fs::read_to_string("./data/winter_options.json") {
let options : WinterOptions = serde_json::from_str(&data)
.expect("winter_options is not valid JSON.");
options
} else {
println!("Using default options.");
WinterOptions::default()
}
}
fn save_to_file(&self) {
let data = serde_json::to_string(&self);
if data.is_err() {
println!("Failed to serialize options file.");
return;
}
if fs::write("./data/winter_options.json", data.unwrap()).is_err() {
println!("Failed to write options file.");
}
}
}
pub struct Winter {
options : WinterOptions,
}
impl Winter {
pub fn serenity() -> Arc<Mutex<Self>> {
Arc::new(Mutex::new(Self {
options: WinterOptions::load_from_file()
}))
}
}
struct WinterKey;
impl TypeMapKey for WinterKey {
type Value = Arc<Mutex<Winter>>;
}
pub trait WinterSerenityInit {
fn register_winter(self) -> Self;
fn register_winter_with(self, voice: Arc<Mutex<Winter>>) -> Self;
}
impl WinterSerenityInit for ClientBuilder {
fn register_winter(self) -> Self {
register(self)
}
fn register_winter_with(self, voice: Arc<Mutex<Winter>>) -> Self {
register_with(self, voice)
}
}
pub fn register(client_builder: ClientBuilder) -> ClientBuilder {
let voice = Winter::serenity();
register_with(client_builder, voice)
}
pub fn register_with(client_builder: ClientBuilder, voice: Arc<Mutex<Winter>>) -> ClientBuilder {
client_builder
.type_map_insert::<WinterKey>(voice)
}
pub async fn winter_get(ctx: &Context) -> Option<Arc<Mutex<Winter>>> {
let data = ctx.data.read().await;
return data.get::<WinterKey>().cloned();
}
/////////
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]
async fn main() {
tracing_subscriber::fmt::init();
// 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 framework = StandardFramework::new().group(&GENERAL_GROUP);
framework.configure(Configuration::new().prefix("~"));
let intents = GatewayIntents::non_privileged()
| GatewayIntents::MESSAGE_CONTENT;
let mut client = Client::builder(&token, intents)
.event_handler(Handler)
.framework(framework)
.register_songbird()
.register_winter()
.type_map_insert::<HttpKey>(HttpClient::new())
.await
.expect("Err creating client");
{
let mut data = client.data.write().await;
data.insert::<ShardManagerContainer>(client.shard_manager.clone());
}
let shard_manager = client.shard_manager.clone();
tokio::spawn(async move {
tokio::signal::ctrl_c().await.expect("Could not register ctrl+c handler");
shard_manager.shutdown_all().await;
});
if let Err(why) = client.start().await {
error!("Client error: {:?}", why);
}
}
async fn ensure_joined(ctx: &Context, msg: &Message) -> bool {
let (guild_id, channel_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);
(guild.id, channel_id)
};
let connect_to = match channel_id {
Some(channel) => channel,
None => {
reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await;
return false;
},
};
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let winter = winter_get(ctx)
.await
.expect("Winter placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
if handler.current_channel().is_some() {
if handler.current_channel().unwrap() == songbird::id::ChannelId::from(connect_to) {
return true;
} else {
reply(&ctx, &msg, "Wrong channel, silly!", &format!("You must to be in my channel, {}, to play music!", connect_to.mention()), None, true).await;
return false;
}
}
}
if let Ok(handler_lock) = manager.join(guild_id, connect_to).await {
check_msg(
msg.channel_id
.say(&ctx.http, &format!("Joined {}", connect_to.mention()))
.await,
);
let chan_id = msg.channel_id;
let send_http = ctx.http.clone();
let mut handler = handler_lock.lock().await;
if !handler.is_deaf() {
if let Err(e) = handler.deafen(true).await {
check_msg(
msg.channel_id
.say(&ctx.http, format!("Failed: {:?}", e))
.await,
);
}
}
handler.add_global_event(
Event::Track(TrackEvent::End),
TrackEndNotifier {
chan_id,
http: send_http,
},
);
handler.add_global_event(
Event::Track(TrackEvent::End),
TrackVolumeUpdater {
winter,
guild_id,
},
);
return true;
} else {
reply(&ctx, &msg, "I'm not allowed here!", "Failed to join the channel.", None, true).await;
return false;
}
}
struct TrackVolumeUpdater {
winter: Arc<Mutex<Winter>>,
guild_id: GuildId,
}
#[async_trait]
impl VoiceEventHandler for TrackVolumeUpdater {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
// Update the volume for the track we are about to play.
if let EventContext::Track(track_list) = ctx {
if track_list.len() > 1 {
let winter = self.winter.lock().await;
let volume = winter.options.get_volume(self.guild_id);
let _ = track_list[1].1.set_volume(volume);
}
}
None
}
}
struct TrackEndNotifier {
chan_id: ChannelId,
http: Arc<Http>,
}
#[async_trait]
impl VoiceEventHandler for TrackEndNotifier {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
if let EventContext::Track(track_list) = ctx {
check_msg(
self.chan_id
.say(&self.http, &format!("Tracks ended: {}.", track_list.len()))
.await,
);
}
None
}
}
#[command]
#[only_in(guilds)]
async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let has_handler = manager.get(guild_id).is_some();
if has_handler {
if let Err(e) = manager.remove(guild_id).await {
check_msg(
msg.channel_id
.say(&ctx.http, format!("Failed: {:?}", e))
.await,
);
}
reply(&ctx, &msg, "Left Voice Channel", "Goodbye! <3", None, false).await;
} else {
reply(&ctx, &msg, "Oops!", "I wasn't in a voice channel, so I did nothing", None, true).await;
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn mute(ctx: &Context, msg: &Message) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let handler_lock = match manager.get(guild_id) {
Some(handler) => handler,
None => {
reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await;
return Ok(());
},
};
let mut handler = handler_lock.lock().await;
if handler.is_mute() {
reply(&ctx, &msg, "Aaaaaa!", "I am already being quiet! Why are you being mean to me :<", None, true).await;
} else {
if let Err(e) = handler.mute(true).await {
check_msg(
msg.channel_id
.say(&ctx.http, format!("Failed: {:?}", e))
.await,
);
}
reply(&ctx, &msg, "Muted!", "I'll be quiet for now...", None, false).await;
}
Ok(())
}
#[command]
async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
reply(&ctx, &msg, "Ping!", "Hello there! :3", None, false).await;
Ok(())
}
struct SongEndNotifier {
chan_id: ChannelId,
http: Arc<Http>,
}
#[async_trait]
impl VoiceEventHandler for SongEndNotifier {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
check_msg(
self.chan_id
.say(&self.http, "Song faded out completely!")
.await,
);
None
}
}
#[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]
#[only_in(guilds)]
async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let url = args.rest().to_string();
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;
return Ok(());
}
let http_client = get_http_client(ctx).await;
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.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 {
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) {
let mut handler = handler_lock.lock().await;
for source in sources {
let mut input = songbird::input::Input::from(source);
let aux_metadata = input.aux_metadata().await.unwrap();
let title = aux_metadata.title.clone().unwrap_or("Unknown Title".to_string());
let artist = aux_metadata.artist.clone().unwrap_or("Unknown Artist".to_string());
let thumbnail = aux_metadata.thumbnail.clone();
let mut duration = "Unknown duration".to_string();
if aux_metadata.duration.is_some() {
let meta_duration = aux_metadata.duration.unwrap();
let seconds = meta_duration.as_secs() % 60;
let minutes = (meta_duration.as_secs() / 60) % 60;
let hours = (meta_duration.as_secs() / 60) / 60;
duration = format!("{:02}:{:02}:{:02}", hours, minutes, seconds).to_string();
}
let track = songbird::tracks::Track::new(input)
.volume(winter.options.get_volume(guild_id));
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));
if thumbnail.is_some() {
embed = embed.thumbnail(thumbnail.unwrap());
}
let builder = CreateMessage::new().embed(embed);
let msg = msg.channel_id.send_message(&ctx, builder).await;
if let Err(why) = msg {
println!("Error sending message: {:?}", why);
}
}
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn chaos(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let url = match args.single::<String>() {
Ok(url) => url,
Err(_) => {
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(());
},
};
if !url.starts_with("http") {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a valid URL")
.await,
);
return Ok(());
}
let http_client = get_http_client(ctx).await;
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.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 {
return Ok(());
}
let do_search = !url.starts_with("http");
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
let src = if do_search {
YoutubeDl::new_search(http_client, url)
} else {
YoutubeDl::new(http_client, url)
};
check_msg(msg.reply(&ctx.http, "Added song to chaos mode.").await);
let track = songbird::tracks::Track::new(src.into())
.volume(winter.options.get_volume(guild_id));
handler.play(track);
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn vox(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let line = args.rest();
if line.is_empty() {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a VOX line")
.await,
);
return Ok(());
}
let words = line.split(" ");
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.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 {
return Ok(());
}
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
for (_, word) in words.enumerate() {
// basic sanity check
if word.chars().all(|x| x.is_alphanumeric()) {
let vox_path = format!("./assets/vox/{}.wav", word);
let source = songbird::input::File::new(vox_path);
let track = Track::new(source.into())
.volume(winter.options.get_volume(guild_id));
handler.enqueue(track).await;
}
}
check_msg(
msg.channel_id
.say(
&ctx.http,
format!("Added vox line: {} to queue! (position {})", line, handler.queue().len()),
)
.await,
);
}
Ok(())
}
#[derive(Serialize, Deserialize, Debug)]
struct TikTokPOSTBody {
text : String,
voice : String,
}
#[derive(Serialize, Deserialize, Debug)]
struct TikTokResponse {
data : String,
}
const TIKTOK_VOICES: &'static [&'static str] = &[
// DISNEY VOICES
"en_us_ghostface", // Ghost Face
"en_us_chewbacca", // Chewbacca
"en_us_c3po", // C3PO
"en_us_stitch", // Stitch
"en_us_stormtrooper", // Stormtrooper
"en_us_rocket", // Rocket
// ENGLISH VOICES
"en_au_001", // English AU - Female
"en_au_002", // English AU - Male
"en_uk_001", // English UK - Male 1
"en_uk_003", // English UK - Male 2
"en_us_001", // English US - Female (Int. 1)
"en_us_002", // English US - Female (Int. 2)
"en_us_006", // English US - Male 1
"en_us_007", // English US - Male 2
"en_us_009", // English US - Male 3
"en_us_010", // English US - Male 4
// EUROPE VOICES
"fr_001", // French - Male 1
"fr_002", // French - Male 2
"de_001", // German - Female
"de_002", // German - Male
"es_002", // Spanish - Male
// AMERICA VOICES
"es_mx_002", // Spanish MX - Male
"br_001", // Portuguese BR - Female 1
"br_003", // Portuguese BR - Female 2
"br_004", // Portuguese BR - Female 3
"br_005", // Portuguese BR - Male
// ASIA VOICES
"id_001", // Indonesian - Female
"jp_001", // Japanese - Female 1
"jp_003", // Japanese - Female 2
"jp_005", // Japanese - Female 3
"jp_006", // Japanese - Male
"kr_002", // Korean - Male 1
"kr_003", // Korean - Female
"kr_004", // Korean - Male 2
// SINGING VOICES
"en_female_f08_salut_damour", // Alto
"en_male_m03_lobby", // Tenor
"en_female_f08_warmy_breeze", // Warmy Breeze
"en_male_m03_sunshine_soon", // Sunshine Soon
// OTHER
"en_male_narration", // narrator
"en_male_funny", // wacky
"en_female_emotional" // peaceful
];
use rand::seq::SliceRandom;
#[command]
#[only_in(guilds)]
async fn tts_list(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut voice_list : String = "".to_string();
for voice in TIKTOK_VOICES {
voice_list += voice;
voice_list += "\n";
}
reply(&ctx, &msg, "TikTok TTS Voices", voice_list.as_str(), None, false).await;
Ok(())
}
#[command]
#[only_in(guilds)]
async fn tts(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let mut line = args.rest();
if line.is_empty() {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a TTS line")
.await,
);
return Ok(());
}
line = line.trim();
let mut voice = TIKTOK_VOICES.choose(&mut rand::thread_rng()).unwrap().to_string();
if line.starts_with(".") {
match line.split_once(' ') {
Some((key, value)) => {
voice = key[1..key.len()].to_string();
line = value;
}
None => {
reply(&ctx, &msg, "Oh no!", "No text for TTS!", None, true).await;
return Ok(());
}
}
}
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.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 {
return Ok(());
}
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
let args = TikTokPOSTBody {
text: line.to_string(),
voice: voice.clone(),
};
let json = serde_json::to_string(&args).expect("Failed to serialize TikTokPOST Body");
let client = reqwest::Client::new();
let res = client
.post("https://tiktok-tts.weilnet.workers.dev/api/generation")
.header(reqwest::header::CONTENT_TYPE, "application/json")
.body(json)
.send()
.await?;
let response : TikTokResponse = match serde_json::from_str(res.text().await?.as_str()) {
Ok(response) => response,
Err(why) => {
reply(&ctx, &msg, "Oh no!", &format!("Error querying tts. Reason: {:?}", why), None, true).await;
return Ok(());
},
};
let file_path = format!("/tmp/tts{}.wav", Uuid::new_v4().to_string());
let tts_data = File::create(file_path.clone()).unwrap();
let mut writer = FromBase64Writer::new(tts_data);
writer.write_all(response.data.as_bytes()).unwrap();
writer.flush().unwrap();
let source = songbird::input::File::new(file_path);
let track = Track::new(source.into())
.volume(winter.options.get_volume(guild_id));
handler.play(track);
check_msg(
msg.channel_id
.say(
&ctx.http,
format!("Playing TTS line: {} with voice: {}", line, voice),
)
.await,
);
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
let queue = handler.queue();
let _ = queue.skip();
check_msg(
msg.channel_id
.say(
&ctx.http,
format!("Song skipped: {} in queue.", queue.len()),
)
.await,
);
} else {
reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await;
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn stop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
let queue = handler.queue();
let _ = queue.stop();
let _ = handler.stop();
reply(&ctx, &msg, "Empty inside", "Queue + chaos cleared.", None, false).await;
} else {
reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await;
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn unmute(ctx: &Context, msg: &Message) -> CommandResult {
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
if let Err(e) = handler.mute(false).await {
check_msg(
msg.channel_id
.say(&ctx.http, format!("Failed: {:?}", e))
.await,
);
}
reply(&ctx, &msg, "I am free!", "I am unmuted now! :3", None, false).await;
} else {
reply(&ctx, &msg, "No channel", "You are not in any voice channel!", None, true).await;
}
Ok(())
}
#[command]
#[only_in(guilds)]
async fn volume(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let volume_string = args.rest();
if volume_string.is_empty() {
reply(&ctx, &msg, "Tell me what you want!", "You need to tell me a volume expression in percentage.", None, true).await;
return Ok(());
}
let volume_human_eval = meval::eval_str(volume_string);
if !volume_human_eval.is_ok() {
reply(&ctx, &msg, "/shrug", "Couldn't evaluate volume expression.", None, true).await;
return Ok(());
}
let volume_human = volume_human_eval.unwrap() as f32;
let volume = volume_human / 100.0;
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let winter_lock = winter_get(ctx)
.await
.expect("Winter placed in at initialisation.")
.clone();
let mut winter = winter_lock.lock().await;
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
for (_, track) in handler.queue().current_queue().iter().enumerate() {
if !track.set_volume(volume).is_ok() {
reply(&ctx, &msg, "IDK!", "Failed to set current volume.", None, true).await;
return Ok(());
}
}
}
winter.options.set_volume(guild_id, volume);
reply(&ctx, &msg, "Am I being too loud?", &format!("Set volume to {}%.", volume_human), None, false).await;
Ok(())
}
#[command]
#[only_in(guilds)]
async fn seek(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let seek_string = args.rest();
if seek_string.is_empty() {
reply(&ctx, &msg, "Tell me what you want!", "You need to tell me a position expression in seconds.", None, true).await;
return Ok(());
}
let seek_human_eval = meval::eval_str(seek_string);
if !seek_human_eval.is_ok() {
reply(&ctx, &msg, "/shrug", "Couldn't evaluate seek expression.", None, true).await;
return Ok(());
}
let seek_human = seek_human_eval.unwrap() as f32;
let seek = std::time::Duration::from_secs_f32(seek_human);
let guild_id = msg.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
for (_, track) in handler.queue().current_queue().iter().enumerate() {
let _success = track.seek(seek);
}
}
let seconds = seek.as_secs() % 60;
let minutes = (seek.as_secs() / 60) % 60;
let hours = (seek.as_secs() / 60) / 60;
reply(&ctx, &msg, "I'm moving!", &format!("Seeked to to {:02}:{:02}:{:02}.", hours, minutes, seconds), None, false).await;
Ok(())
}
#[command]
#[only_in(guilds)]
async fn restart(ctx: &Context, msg: &Message) -> CommandResult {
let data = ctx.data.read().await;
if let Some(manager) = data.get::<ShardManagerContainer>() {
msg.reply(ctx, "Shutting down!").await?;
manager.shutdown_all().await;
} else {
msg.reply(ctx, "There was a problem getting the shard manager").await?;
return Ok(());
}
Ok(())
}
/// Checks that a message successfully sent; if not, then logs why to stdout.
fn check_msg(result: SerenityResult<Message>) {
if let Err(why) = result {
println!("Error sending message: {:?}", why);
}
}
async fn reply<S :Into<String>>(ctx: &Context, context_msg: &Message, title: S, desc: S, image: Option<S>, error: bool) {
let mut embed = CreateEmbed::new()
.title(title.into())
.description(desc.into());
if image.is_some() {
embed = embed.image(image.unwrap().into());
}
if error {
embed = embed.color(Colour::from_rgb(255,218,218));
} else {
embed = embed.color(Colour::from_rgb(223,255,198));
}
let builder = CreateMessage::new().embed(embed);
let msg = context_msg.channel_id.send_message(&ctx, builder).await;
if let Err(why) = msg {
println!("Error sending message: {:?}", why);
}
}