Modularize player
This commit is contained in:
parent
684f2a1fb4
commit
f2ab6f1314
18 changed files with 717 additions and 650 deletions
|
@ -45,3 +45,20 @@ macro_rules! display_error {
|
|||
};
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! check_option {
|
||||
($v:expr) => {
|
||||
match $v {
|
||||
Some(t) => t,
|
||||
None => return,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! print_error {
|
||||
($v:expr) => {
|
||||
if let Err(msg) = $v {
|
||||
println!("{}", msg);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -6,52 +6,14 @@ extern crate typemap;
|
|||
|
||||
mod confighandler;
|
||||
mod macros;
|
||||
mod player;
|
||||
|
||||
// Import the client's bridge to the voice manager. Since voice is a standalone
|
||||
// feature, it's not as ergonomic to work with as it could be. The client
|
||||
// provides a clean bridged integration with voice.
|
||||
use serenity::client::bridge::voice::ClientVoiceManager;
|
||||
use serenity::client::{Client, Context, EventHandler};
|
||||
use serenity::client::Client;
|
||||
use serenity::framework::StandardFramework;
|
||||
use serenity::model::channel::Message;
|
||||
use serenity::model::gateway::Ready;
|
||||
// Import the `Context` from the client and `parking_lot`'s `Mutex`.
|
||||
//
|
||||
// `parking_lot` offers much more efficient implementations of `std::sync`'s
|
||||
// types. You can read more about it here:
|
||||
//
|
||||
// <https://github.com/Amanieu/parking_lot#features>
|
||||
use serenity::prelude::Mutex;
|
||||
use serenity::Result as SerenityResult;
|
||||
use std::sync::Arc;
|
||||
use typemap::Key;
|
||||
|
||||
use confighandler::*;
|
||||
|
||||
mod player;
|
||||
mod youtube;
|
||||
|
||||
use player::*;
|
||||
|
||||
/*
|
||||
const fn empty_vec<T>() -> Vec<T> {
|
||||
Vec::new()
|
||||
}
|
||||
*/
|
||||
|
||||
pub struct VoiceManager;
|
||||
|
||||
impl Key for VoiceManager {
|
||||
type Value = Arc<Mutex<ClientVoiceManager>>;
|
||||
}
|
||||
|
||||
struct Handler;
|
||||
|
||||
impl EventHandler for Handler {
|
||||
fn ready(&self, _: Context, ready: Ready) {
|
||||
println!("{} is connected!", ready.user.name);
|
||||
}
|
||||
}
|
||||
use player::prelude::*;
|
||||
|
||||
struct Config {
|
||||
token: String,
|
||||
|
@ -127,7 +89,6 @@ fn main() {
|
|||
"stop".to_string(),
|
||||
"help".to_string(),
|
||||
"list".to_string(),
|
||||
"help".to_string(),
|
||||
"skip".to_string(),
|
||||
"ip".to_string(),
|
||||
],
|
||||
|
@ -142,10 +103,3 @@ fn main() {
|
|||
.start()
|
||||
.map_err(|why| println!("Client ended: {:?}", why));
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
|
600
src/player.rs
600
src/player.rs
|
@ -1,600 +0,0 @@
|
|||
use parking_lot;
|
||||
|
||||
use serenity;
|
||||
use serenity::client::CACHE;
|
||||
use serenity::model::id::{ChannelId, GuildId};
|
||||
use serenity::voice::{AudioSource, Handler, LockedAudio};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::DerefMut;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str::from_utf8;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time;
|
||||
|
||||
macro_rules! check_option {
|
||||
($v:expr) => {
|
||||
match $v {
|
||||
Some(t) => t,
|
||||
None => return,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct Song {
|
||||
pub source: Box<AudioSource>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct MediaData {
|
||||
playlist: Mutex<RefCell<Vec<Song>>>,
|
||||
current_song: Mutex<RefCell<Option<LockedAudio>>>,
|
||||
watcher_thread: Mutex<RefCell<Option<JoinHandle<()>>>>,
|
||||
}
|
||||
|
||||
fn guild_id(channel_id: ChannelId) -> Option<GuildId> {
|
||||
match CACHE.read().guild_channel(channel_id) {
|
||||
Some(channel) => Some(channel.read().guild_id),
|
||||
None => {
|
||||
super::check_msg(channel_id.say("Error finding channel info"));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handler<'a>(
|
||||
guild_id: GuildId,
|
||||
manager: &'a mut parking_lot::MutexGuard<
|
||||
'_,
|
||||
serenity::client::bridge::voice::ClientVoiceManager,
|
||||
>,
|
||||
) -> Option<&'a mut Handler> {
|
||||
manager.get_mut(guild_id)
|
||||
}
|
||||
|
||||
impl MediaData {
|
||||
fn song_mut(&self) -> MutexGuard<RefCell<Option<LockedAudio>>> {
|
||||
self.current_song.lock().unwrap()
|
||||
}
|
||||
|
||||
fn playlist_mut(&self) -> MutexGuard<RefCell<Vec<Song>>> {
|
||||
self.playlist.lock().unwrap()
|
||||
}
|
||||
|
||||
fn reset_thread(&self) {
|
||||
let thread_mutex = self.watcher_thread.lock().unwrap();
|
||||
*thread_mutex.borrow_mut() = None;
|
||||
}
|
||||
|
||||
fn start_thread(
|
||||
media: &Arc<MediaData>,
|
||||
channel_id: ChannelId,
|
||||
manager_lock: &Arc<
|
||||
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
|
||||
>,
|
||||
) {
|
||||
let media_clone = media.clone();
|
||||
let manager_clone = manager_lock.clone();
|
||||
|
||||
let thread_mutex = media.watcher_thread.lock().unwrap();
|
||||
let mut watcher_mut = thread_mutex.borrow_mut();
|
||||
|
||||
if watcher_mut.is_none() {
|
||||
*watcher_mut = Some(thread::spawn(move || loop {
|
||||
{
|
||||
let song_lock = media_clone.song_mut();
|
||||
let mut borrow = song_lock.borrow_mut();
|
||||
|
||||
if let Some(ref mut song) = borrow.deref_mut() {
|
||||
let song_lock = song.clone();
|
||||
let audio = song_lock.lock();
|
||||
|
||||
if audio.finished {
|
||||
let playlist = media_clone.playlist_mut();
|
||||
|
||||
if !Self::next_song(
|
||||
song,
|
||||
playlist.borrow_mut().deref_mut(),
|
||||
channel_id,
|
||||
&manager_clone,
|
||||
) {
|
||||
manager_clone
|
||||
.lock()
|
||||
.remove(check_option!(guild_id(channel_id)));
|
||||
|
||||
media_clone.reset_thread();
|
||||
|
||||
println!("left channel");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
let playlist_mutex = media_clone.playlist_mut();
|
||||
let mut playlist = playlist_mutex.borrow_mut();
|
||||
|
||||
if !playlist.is_empty() {
|
||||
let mut manager = manager_clone.lock();
|
||||
|
||||
if let Some(handler) =
|
||||
handler(check_option!(guild_id(channel_id)), &mut manager)
|
||||
{
|
||||
let first = playlist.remove(0);
|
||||
|
||||
*borrow = Some(handler.play_returning(first.source));
|
||||
|
||||
super::check_msg(
|
||||
channel_id.say(format!("Playing song: {}", first.name)),
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
manager_clone
|
||||
.lock()
|
||||
.remove(check_option!(guild_id(channel_id)));
|
||||
|
||||
media_clone.reset_thread();
|
||||
|
||||
println!("left channel");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let two_sec = time::Duration::from_secs(2);
|
||||
thread::sleep(two_sec);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn next_song(
|
||||
song: &mut LockedAudio,
|
||||
playlist: &mut Vec<Song>,
|
||||
channel_id: ChannelId,
|
||||
manager_lock: &Arc<
|
||||
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
|
||||
>,
|
||||
) -> bool {
|
||||
let mut manager = manager_lock.lock();
|
||||
|
||||
let guild_id = match guild_id(channel_id) {
|
||||
Some(id) => id,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
if let Some(handler) = handler(guild_id, &mut manager) {
|
||||
if playlist.is_empty() {
|
||||
return false;
|
||||
} else {
|
||||
let first = playlist.remove(0);
|
||||
|
||||
handler.stop();
|
||||
*song = handler.play_returning(first.source);
|
||||
|
||||
super::check_msg(channel_id.say(format!("Playing song: {}", first.name)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MediaData {
|
||||
fn default() -> MediaData {
|
||||
MediaData {
|
||||
playlist: Mutex::new(RefCell::new(Vec::new())),
|
||||
current_song: Mutex::new(RefCell::new(None)),
|
||||
watcher_thread: Mutex::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MediaData {}
|
||||
unsafe impl Sync for MediaData {}
|
||||
|
||||
pub struct Play {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Play {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Play {
|
||||
Play { media: media_data }
|
||||
}
|
||||
|
||||
fn check_for_continue(song_lock: MutexGuard<RefCell<Option<LockedAudio>>>) -> bool {
|
||||
match song_lock.borrow_mut().deref_mut() {
|
||||
Some(song) => {
|
||||
let song_clone = song.clone();
|
||||
let mut audio_lock = song_clone.lock();
|
||||
|
||||
audio_lock.play();
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_join_channel(
|
||||
manager_lock: &Arc<parking_lot::Mutex<serenity::client::bridge::voice::ClientVoiceManager>>,
|
||||
msg: &serenity::model::channel::Message,
|
||||
) {
|
||||
let guild = match msg.guild() {
|
||||
Some(guild) => guild,
|
||||
None => {
|
||||
display_error!(msg.channel_id.say("Groups and DMs not supported"));
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let guild_id = guild.read().id;
|
||||
|
||||
let channel_id = guild
|
||||
.read()
|
||||
.voice_states
|
||||
.get(&msg.author.id)
|
||||
.and_then(|voice_state| voice_state.channel_id);
|
||||
|
||||
let connect_to = match channel_id {
|
||||
Some(channel) => channel,
|
||||
None => {
|
||||
display_error!(msg.reply("Not in a voice channel"));
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut manager = manager_lock.lock();
|
||||
|
||||
manager.join(guild_id, connect_to);
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Play {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
mut ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
mut args: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let url = match args.single::<String>() {
|
||||
Ok(url) => {
|
||||
if !url.starts_with("http") {
|
||||
super::check_msg(msg.channel_id.say("Must provide a valid URL"));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
url
|
||||
}
|
||||
Err(_) => {
|
||||
if !Self::check_for_continue(self.media.song_mut()) {
|
||||
super::check_msg(msg.channel_id.say("Must provide a URL to a video or audio"));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let mut source = match super::youtube::youtube_dl(&url) {
|
||||
Ok(source) => source,
|
||||
Err(why) => {
|
||||
println!("Err starting source: {:?}", why);
|
||||
|
||||
super::check_msg(
|
||||
msg.channel_id
|
||||
.say(format!("Error using youtube-dl: {}", why)),
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
let playlist_mutex = self.media.playlist_mut();
|
||||
playlist_mutex.borrow_mut().append(&mut source);
|
||||
}
|
||||
|
||||
let mut manager_lock = ctx
|
||||
.data
|
||||
.lock()
|
||||
.get::<super::VoiceManager>()
|
||||
.cloned()
|
||||
.unwrap();
|
||||
|
||||
Self::check_join_channel(&manager_lock, msg);
|
||||
|
||||
MediaData::start_thread(&self.media, msg.channel_id, &manager_lock);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pause {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Pause {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Pause {
|
||||
Pause { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Pause {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
_: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let song_lock = self.media.song_mut();
|
||||
|
||||
if let Some(song) = song_lock.borrow_mut().deref_mut() {
|
||||
let song_clone = song.clone();
|
||||
let mut audio_lock = song_clone.lock();
|
||||
|
||||
audio_lock.pause();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct List {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl List {
|
||||
pub fn new(media_data: Arc<MediaData>) -> List {
|
||||
List { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for List {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let mut output = String::new();
|
||||
|
||||
{
|
||||
let playlist_mutex = self.media.playlist_mut();
|
||||
let playlist = playlist_mutex.borrow();
|
||||
|
||||
output += &format!(
|
||||
"{} {} queued\n",
|
||||
playlist.len(),
|
||||
if playlist.len() == 1 { "song" } else { "songs" }
|
||||
);
|
||||
|
||||
for (i, song) in playlist.iter().enumerate() {
|
||||
output += &format!("\t{}.\t{}\n", i + 1, song.name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
super::check_msg(msg.channel_id.say(output));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Help {
|
||||
prefix: String,
|
||||
commands: Vec<String>,
|
||||
}
|
||||
|
||||
impl Help {
|
||||
pub fn new(prefix: &String, commands: Vec<String>) -> Help {
|
||||
Help {
|
||||
prefix: prefix.clone(),
|
||||
commands: commands,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Help {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let mut output = String::new();
|
||||
|
||||
output += "Available commands:\n";
|
||||
|
||||
for command in &self.commands {
|
||||
output += &format!("\t{}{}\n", self.prefix, command);
|
||||
}
|
||||
|
||||
super::check_msg(msg.channel_id.say(output));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Stop {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Stop {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Stop {
|
||||
Stop { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Stop {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
{
|
||||
let playlist = self.media.playlist_mut();
|
||||
playlist.borrow_mut().clear();
|
||||
}
|
||||
|
||||
{
|
||||
let song = self.media.song_mut();
|
||||
*song.borrow_mut() = None;
|
||||
}
|
||||
|
||||
{
|
||||
let mut manager_lock = ctx
|
||||
.data
|
||||
.lock()
|
||||
.get::<super::VoiceManager>()
|
||||
.cloned()
|
||||
.unwrap();
|
||||
|
||||
let mut manager = manager_lock.lock();
|
||||
let guild_id = match guild_id(msg.channel_id) {
|
||||
Some(guild_id) => guild_id,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let handler = match handler(guild_id, &mut manager) {
|
||||
Some(handler) => handler,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
println!("stopped handler");
|
||||
|
||||
handler.stop();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Skip {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Skip {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Skip {
|
||||
Skip { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Skip {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let mut manager_lock = ctx
|
||||
.data
|
||||
.lock()
|
||||
.get::<super::VoiceManager>()
|
||||
.cloned()
|
||||
.unwrap();
|
||||
|
||||
let mut manager = manager_lock.lock();
|
||||
|
||||
let guild_id = match guild_id(msg.channel_id) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
println!("error getting guild id");
|
||||
|
||||
super::check_msg(msg.channel_id.say("error getting guild id"));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(handler) = handler(guild_id, &mut manager) {
|
||||
let playlist_mutex = self.media.playlist_mut();
|
||||
let mut playlist = playlist_mutex.borrow_mut();
|
||||
|
||||
let song_mutex = self.media.song_mut();
|
||||
let mut song = song_mutex.borrow_mut();
|
||||
|
||||
if playlist.is_empty() {
|
||||
super::check_msg(
|
||||
msg.channel_id
|
||||
.say("playlist is empty, no next song available"),
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
let first = playlist.remove(0);
|
||||
|
||||
handler.stop();
|
||||
*song = Some(handler.play_returning(first.source));
|
||||
|
||||
super::check_msg(
|
||||
msg.channel_id
|
||||
.say(format!("Skipped current song, now playing: {}", first.name)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IP {}
|
||||
|
||||
impl IP {
|
||||
pub fn new() -> IP {
|
||||
IP {}
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for IP {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let args = [
|
||||
"@resolver1.opendns.com",
|
||||
"AAAA",
|
||||
"myip.opendns.com",
|
||||
"+short",
|
||||
];
|
||||
|
||||
let out = match Command::new("dig")
|
||||
.args(&args)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
{
|
||||
Ok(out) => out,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
if !out.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match from_utf8(out.stdout.as_slice()) {
|
||||
Ok(string) => super::check_msg(msg.channel_id.say(string)),
|
||||
Err(_) => super::check_msg(msg.channel_id.say("error getting IP string")),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
37
src/player/commands/help.rs
Normal file
37
src/player/commands/help.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use serenity;
|
||||
|
||||
pub struct Help {
|
||||
prefix: String,
|
||||
commands: Vec<String>,
|
||||
}
|
||||
|
||||
impl Help {
|
||||
pub fn new(prefix: &String, commands: Vec<String>) -> Help {
|
||||
Help {
|
||||
prefix: prefix.clone(),
|
||||
commands: commands,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Help {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let mut output = String::new();
|
||||
|
||||
output += "Available commands:\n";
|
||||
|
||||
for command in &self.commands {
|
||||
output += &format!("\t{}{}\n", self.prefix, command);
|
||||
}
|
||||
|
||||
print_error!(msg.channel_id.say(output));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
49
src/player/commands/ip.rs
Normal file
49
src/player/commands/ip.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
use serenity;
|
||||
|
||||
use std::process::{Command, Stdio};
|
||||
use std::str::from_utf8;
|
||||
|
||||
pub struct IP {}
|
||||
|
||||
impl IP {
|
||||
pub fn new() -> IP {
|
||||
IP {}
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for IP {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let args = [
|
||||
"@resolver1.opendns.com",
|
||||
"AAAA",
|
||||
"myip.opendns.com",
|
||||
"+short",
|
||||
];
|
||||
|
||||
let out = match Command::new("dig")
|
||||
.args(&args)
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
{
|
||||
Ok(out) => out,
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
if !out.status.success() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
match from_utf8(out.stdout.as_slice()) {
|
||||
Ok(string) => print_error!(msg.channel_id.say(string)),
|
||||
Err(_) => print_error!(msg.channel_id.say("error getting IP string")),
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
46
src/player/commands/list.rs
Normal file
46
src/player/commands/list.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use serenity;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::prelude::*;
|
||||
|
||||
pub struct List {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl List {
|
||||
pub fn new(media_data: Arc<MediaData>) -> List {
|
||||
List { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for List {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let mut output = String::new();
|
||||
|
||||
{
|
||||
let playlist_mutex = self.media.playlist_mut();
|
||||
let playlist = playlist_mutex.borrow();
|
||||
|
||||
output += &format!(
|
||||
"{} {} queued\n",
|
||||
playlist.len(),
|
||||
if playlist.len() == 1 { "song" } else { "songs" }
|
||||
);
|
||||
|
||||
for (i, song) in playlist.iter().enumerate() {
|
||||
output += &format!("\t{}.\t{}\n", i + 1, song.name.clone());
|
||||
}
|
||||
}
|
||||
|
||||
print_error!(msg.channel_id.say(output));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
7
src/player/commands/mod.rs
Normal file
7
src/player/commands/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod help;
|
||||
pub mod ip;
|
||||
pub mod list;
|
||||
pub mod pause;
|
||||
pub mod play;
|
||||
pub mod skip;
|
||||
pub mod stop;
|
37
src/player/commands/pause.rs
Normal file
37
src/player/commands/pause.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use serenity;
|
||||
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::prelude::*;
|
||||
|
||||
pub struct Pause {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Pause {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Pause {
|
||||
Pause { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Pause {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
_: &mut serenity::client::Context,
|
||||
_: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let song_lock = self.media.song_mut();
|
||||
|
||||
if let Some(song) = song_lock.borrow_mut().deref_mut() {
|
||||
let song_clone = song.clone();
|
||||
let mut audio_lock = song_clone.lock();
|
||||
|
||||
audio_lock.pause();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
144
src/player/commands/play.rs
Normal file
144
src/player/commands/play.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use parking_lot;
|
||||
|
||||
use serenity;
|
||||
use serenity::voice::LockedAudio;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, MutexGuard};
|
||||
|
||||
use super::super::prelude::*;
|
||||
|
||||
pub struct Play {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Play {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Play {
|
||||
Play { media: media_data }
|
||||
}
|
||||
|
||||
fn check_for_continue(song_lock: MutexGuard<RefCell<Option<LockedAudio>>>) -> bool {
|
||||
match song_lock.borrow_mut().deref_mut() {
|
||||
Some(song) => {
|
||||
let song_clone = song.clone();
|
||||
let mut audio_lock = song_clone.lock();
|
||||
|
||||
audio_lock.play();
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn check_join_channel(
|
||||
manager_lock: &Arc<parking_lot::Mutex<serenity::client::bridge::voice::ClientVoiceManager>>,
|
||||
msg: &serenity::model::channel::Message,
|
||||
) {
|
||||
let guild = match msg.guild() {
|
||||
Some(guild) => guild,
|
||||
None => {
|
||||
display_error!(msg.channel_id.say("Groups and DMs not supported"));
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let guild_id = guild.read().id;
|
||||
|
||||
let channel_id = guild
|
||||
.read()
|
||||
.voice_states
|
||||
.get(&msg.author.id)
|
||||
.and_then(|voice_state| voice_state.channel_id);
|
||||
|
||||
let connect_to = match channel_id {
|
||||
Some(channel) => channel,
|
||||
None => {
|
||||
display_error!(msg.reply("Not in a voice channel"));
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut manager = manager_lock.lock();
|
||||
|
||||
manager.join(guild_id, connect_to);
|
||||
}
|
||||
|
||||
fn handle_http_request(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
url: &String,
|
||||
) {
|
||||
let mut source = match youtube_dl(&url) {
|
||||
Ok(source) => source,
|
||||
Err(why) => {
|
||||
println!("Err starting source: {:?}", why);
|
||||
|
||||
print_error!(
|
||||
msg.channel_id
|
||||
.say(format!("Error using youtube-dl: {}", why))
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
let playlist_mutex = self.media.playlist_mut();
|
||||
playlist_mutex.borrow_mut().append(&mut source);
|
||||
}
|
||||
|
||||
let manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
|
||||
|
||||
Self::check_join_channel(&manager_lock, msg);
|
||||
|
||||
MediaData::start_thread(&self.media, msg.channel_id, &manager_lock);
|
||||
}
|
||||
|
||||
fn handle_local_request(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
) {
|
||||
print_error!(self.media.reset(ctx, msg));
|
||||
|
||||
// TODO: read local file directory and play songs in arbitrary order
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Play {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
mut ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
mut args: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
if args.len() == 0 {
|
||||
if !Self::check_for_continue(self.media.song_mut()) {
|
||||
print_error!(msg.channel_id.say("Must provide a URL to a video or audio"));
|
||||
}
|
||||
} else if args.len() == 1 {
|
||||
let arg = match args.single::<String>() {
|
||||
Ok(arg) => arg,
|
||||
// can't happen, since we tested for length == 1
|
||||
Err(_) => return Ok(()),
|
||||
};
|
||||
|
||||
if arg == "--local" {
|
||||
self.handle_local_request(ctx, msg);
|
||||
} else if arg.starts_with("http") {
|
||||
self.handle_http_request(ctx, msg, &arg);
|
||||
} else {
|
||||
print_error!(msg.channel_id.say("Unsupported argument list"));
|
||||
}
|
||||
} else {
|
||||
print_error!(msg.channel_id.say("Unsupported argument list"));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
68
src/player/commands/skip.rs
Normal file
68
src/player/commands/skip.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use serenity;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::prelude::*;
|
||||
|
||||
pub struct Skip {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Skip {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Skip {
|
||||
Skip { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Skip {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
let mut manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
|
||||
|
||||
let mut manager = manager_lock.lock();
|
||||
|
||||
let guild_id = match guild_id(msg.channel_id) {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
println!("error getting guild id");
|
||||
print_error!(msg.channel_id.say("error getting guild id"));
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(handler) = handler(guild_id, &mut manager) {
|
||||
let playlist_mutex = self.media.playlist_mut();
|
||||
let mut playlist = playlist_mutex.borrow_mut();
|
||||
|
||||
let song_mutex = self.media.song_mut();
|
||||
let mut song = song_mutex.borrow_mut();
|
||||
|
||||
if playlist.is_empty() {
|
||||
print_error!(
|
||||
msg.channel_id
|
||||
.say("playlist is empty, no next song available")
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
} else {
|
||||
let first = playlist.remove(0);
|
||||
|
||||
handler.stop();
|
||||
*song = Some(handler.play_returning(first.source));
|
||||
|
||||
print_error!(
|
||||
msg.channel_id
|
||||
.say(format!("Skipped current song, now playing: {}", first.name))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
29
src/player/commands/stop.rs
Normal file
29
src/player/commands/stop.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use serenity;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::super::prelude::*;
|
||||
|
||||
pub struct Stop {
|
||||
media: Arc<MediaData>,
|
||||
}
|
||||
|
||||
impl Stop {
|
||||
pub fn new(media_data: Arc<MediaData>) -> Stop {
|
||||
Stop { media: media_data }
|
||||
}
|
||||
}
|
||||
|
||||
impl serenity::framework::standard::Command for Stop {
|
||||
#[allow(unreachable_code, unused_mut)]
|
||||
fn execute(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
_: serenity::framework::standard::Args,
|
||||
) -> ::std::result::Result<(), serenity::framework::standard::CommandError> {
|
||||
print_error!(self.media.reset(ctx, msg));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
10
src/player/eventhandler.rs
Normal file
10
src/player/eventhandler.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use serenity::client::{Context, EventHandler};
|
||||
use serenity::model::gateway::Ready;
|
||||
|
||||
pub struct Handler;
|
||||
|
||||
impl EventHandler for Handler {
|
||||
fn ready(&self, _: Context, ready: Ready) {
|
||||
println!("{} is connected!", ready.user.name);
|
||||
}
|
||||
}
|
208
src/player/mediadata.rs
Normal file
208
src/player/mediadata.rs
Normal file
|
@ -0,0 +1,208 @@
|
|||
use serenity;
|
||||
use serenity::model::id::ChannelId;
|
||||
use serenity::voice::{AudioSource, LockedAudio};
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time;
|
||||
|
||||
use super::prelude::*;
|
||||
|
||||
pub struct Song {
|
||||
pub source: Box<AudioSource>,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
pub struct MediaData {
|
||||
playlist: Mutex<RefCell<Vec<Song>>>,
|
||||
current_song: Mutex<RefCell<Option<LockedAudio>>>,
|
||||
watcher_thread: Mutex<RefCell<Option<JoinHandle<()>>>>,
|
||||
}
|
||||
|
||||
impl MediaData {
|
||||
pub fn reset(
|
||||
&self,
|
||||
ctx: &mut serenity::client::Context,
|
||||
msg: &serenity::model::channel::Message,
|
||||
) -> Result<(), String> {
|
||||
{
|
||||
let playlist = self.playlist_mut();
|
||||
playlist.borrow_mut().clear();
|
||||
}
|
||||
|
||||
{
|
||||
let song = self.song_mut();
|
||||
*song.borrow_mut() = None;
|
||||
}
|
||||
|
||||
{
|
||||
let manager_lock = ctx.data.lock().get::<VoiceManager>().cloned().unwrap();
|
||||
|
||||
let mut manager = manager_lock.lock();
|
||||
let guild_id = match guild_id(msg.channel_id) {
|
||||
Some(guild_id) => guild_id,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
let handler = match handler(guild_id, &mut manager) {
|
||||
Some(handler) => handler,
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
println!("stopped handler");
|
||||
|
||||
handler.stop();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn song_mut(&self) -> MutexGuard<RefCell<Option<LockedAudio>>> {
|
||||
self.current_song.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn playlist_mut(&self) -> MutexGuard<RefCell<Vec<Song>>> {
|
||||
self.playlist.lock().unwrap()
|
||||
}
|
||||
|
||||
pub fn reset_thread(&self) {
|
||||
let thread_mutex = self.watcher_thread.lock().unwrap();
|
||||
*thread_mutex.borrow_mut() = None;
|
||||
}
|
||||
|
||||
pub fn start_thread(
|
||||
media: &Arc<MediaData>,
|
||||
channel_id: ChannelId,
|
||||
manager_lock: &Arc<
|
||||
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
|
||||
>,
|
||||
) {
|
||||
let media_clone = media.clone();
|
||||
let manager_clone = manager_lock.clone();
|
||||
|
||||
let thread_mutex = media.watcher_thread.lock().unwrap();
|
||||
let mut watcher_mut = thread_mutex.borrow_mut();
|
||||
|
||||
if watcher_mut.is_none() {
|
||||
*watcher_mut = Some(thread::spawn(move || loop {
|
||||
{
|
||||
let song_lock = media_clone.song_mut();
|
||||
let mut borrow = song_lock.borrow_mut();
|
||||
|
||||
if let Some(ref mut song) = borrow.deref_mut() {
|
||||
let song_lock = song.clone();
|
||||
let audio = song_lock.lock();
|
||||
|
||||
if audio.finished {
|
||||
let playlist = media_clone.playlist_mut();
|
||||
|
||||
if !Self::next_song(
|
||||
song,
|
||||
playlist.borrow_mut().deref_mut(),
|
||||
channel_id,
|
||||
&manager_clone,
|
||||
) {
|
||||
manager_clone
|
||||
.lock()
|
||||
.remove(check_option!(guild_id(channel_id)));
|
||||
|
||||
media_clone.reset_thread();
|
||||
|
||||
println!("left channel");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
let playlist_mutex = media_clone.playlist_mut();
|
||||
let mut playlist = playlist_mutex.borrow_mut();
|
||||
|
||||
if !playlist.is_empty() {
|
||||
let mut manager = manager_clone.lock();
|
||||
|
||||
if let Some(handler) =
|
||||
handler(check_option!(guild_id(channel_id)), &mut manager)
|
||||
{
|
||||
let first = playlist.remove(0);
|
||||
|
||||
*borrow = Some(handler.play_returning(first.source));
|
||||
|
||||
print_error!(
|
||||
channel_id.say(format!("Playing song: {}", first.name))
|
||||
);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
manager_clone
|
||||
.lock()
|
||||
.remove(check_option!(guild_id(channel_id)));
|
||||
|
||||
media_clone.reset_thread();
|
||||
|
||||
println!("left channel");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let two_sec = time::Duration::from_secs(2);
|
||||
thread::sleep(two_sec);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn next_song(
|
||||
song: &mut LockedAudio,
|
||||
playlist: &mut Vec<Song>,
|
||||
channel_id: ChannelId,
|
||||
manager_lock: &Arc<
|
||||
serenity::prelude::Mutex<serenity::client::bridge::voice::ClientVoiceManager>,
|
||||
>,
|
||||
) -> bool {
|
||||
let mut manager = manager_lock.lock();
|
||||
|
||||
let guild_id = match guild_id(channel_id) {
|
||||
Some(id) => id,
|
||||
None => return false,
|
||||
};
|
||||
|
||||
if let Some(handler) = handler(guild_id, &mut manager) {
|
||||
if playlist.is_empty() {
|
||||
return false;
|
||||
} else {
|
||||
let first = playlist.remove(0);
|
||||
|
||||
handler.stop();
|
||||
*song = handler.play_returning(first.source);
|
||||
|
||||
print_error!(channel_id.say(format!("Playing song: {}", first.name)));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MediaData {
|
||||
fn default() -> MediaData {
|
||||
MediaData {
|
||||
playlist: Mutex::new(RefCell::new(Vec::new())),
|
||||
current_song: Mutex::new(RefCell::new(None)),
|
||||
watcher_thread: Mutex::new(RefCell::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for MediaData {}
|
||||
unsafe impl Sync for MediaData {}
|
9
src/player/mod.rs
Normal file
9
src/player/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub mod eventhandler;
|
||||
pub mod mediadata;
|
||||
pub mod player;
|
||||
pub mod voicemanager;
|
||||
mod youtube;
|
||||
|
||||
pub mod commands;
|
||||
|
||||
pub mod prelude;
|
26
src/player/player.rs
Normal file
26
src/player/player.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use parking_lot;
|
||||
|
||||
use serenity;
|
||||
use serenity::client::CACHE;
|
||||
use serenity::model::id::{ChannelId, GuildId};
|
||||
use serenity::voice::Handler;
|
||||
|
||||
pub fn guild_id(channel_id: ChannelId) -> Option<GuildId> {
|
||||
match CACHE.read().guild_channel(channel_id) {
|
||||
Some(channel) => Some(channel.read().guild_id),
|
||||
None => {
|
||||
print_error!(channel_id.say("Error finding channel info"));
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handler<'a>(
|
||||
guild_id: GuildId,
|
||||
manager: &'a mut parking_lot::MutexGuard<
|
||||
'_,
|
||||
serenity::client::bridge::voice::ClientVoiceManager,
|
||||
>,
|
||||
) -> Option<&'a mut Handler> {
|
||||
manager.get_mut(guild_id)
|
||||
}
|
16
src/player/prelude.rs
Normal file
16
src/player/prelude.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
pub use super::mediadata::{MediaData, Song};
|
||||
|
||||
pub use super::commands::help::Help;
|
||||
pub use super::commands::ip::IP;
|
||||
pub use super::commands::list::List;
|
||||
pub use super::commands::pause::Pause;
|
||||
pub use super::commands::play::Play;
|
||||
pub use super::commands::skip::Skip;
|
||||
pub use super::commands::stop::Stop;
|
||||
|
||||
pub use super::eventhandler::Handler;
|
||||
pub use super::voicemanager::VoiceManager;
|
||||
|
||||
pub use super::youtube::youtube_dl;
|
||||
|
||||
pub use super::player::{guild_id, handler};
|
10
src/player/voicemanager.rs
Normal file
10
src/player/voicemanager.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use serenity::client::bridge::voice::ClientVoiceManager;
|
||||
use serenity::prelude::Mutex;
|
||||
use std::sync::Arc;
|
||||
use typemap::Key;
|
||||
|
||||
pub struct VoiceManager;
|
||||
|
||||
impl Key for VoiceManager {
|
||||
type Value = Arc<Mutex<ClientVoiceManager>>;
|
||||
}
|
|
@ -2,7 +2,7 @@ use serenity::voice::ffmpeg;
|
|||
use std::process::{Command, Output, Stdio};
|
||||
use std::str::from_utf8;
|
||||
|
||||
use player::Song;
|
||||
use super::prelude::*;
|
||||
|
||||
const FIRST_LOAD_PREFIX: &str = "[download] Destination:";
|
||||
const RELOAD_SUFFIX: &str = " has already been downloaded";
|
Loading…
Reference in a new issue