commit
24772ac9fb
@ -0,0 +1,38 @@
|
||||
use bot_serenityutils::core::SHORT_TIMEOUT;
|
||||
use bot_serenityutils::ephemeral_message::EphemeralMessage;
|
||||
use futures::future::BoxFuture;
|
||||
use serenity::client::Context;
|
||||
use serenity::framework::standard::macros::command;
|
||||
use serenity::framework::standard::{Args, CommandResult};
|
||||
use serenity::model::channel::Message;
|
||||
use serenity::Result as SerenityResult;
|
||||
|
||||
#[command]
|
||||
#[description("Clears the chat (maximum 100 messages)")]
|
||||
#[usage("[<number>]")]
|
||||
#[example("20")]
|
||||
#[min_args(0)]
|
||||
#[max_args(1)]
|
||||
#[bucket("general")]
|
||||
#[required_permissions("MANAGE_MESSAGES")]
|
||||
async fn clear(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let limit = args.single::<u64>().unwrap_or(20);
|
||||
log::debug!("Deleting messages for channel {}", msg.channel_id);
|
||||
let messages = msg.channel_id.messages(ctx, |b| b.limit(limit)).await?;
|
||||
log::debug!("Deleting {} messages", messages.len());
|
||||
let futures: Vec<BoxFuture<SerenityResult<()>>> = messages
|
||||
.into_iter()
|
||||
.map(|m| async move { ctx.http.delete_message(m.channel_id.0, m.id.0).await }.boxed())
|
||||
.collect();
|
||||
log::debug!("Waiting for all messages to be deleted");
|
||||
let deleted = futures::future::join_all(futures).await;
|
||||
let deleted_count = deleted.into_iter().filter(|d| d.is_ok()).count();
|
||||
log::debug!("{} Messages deleted", deleted_count);
|
||||
|
||||
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |f| {
|
||||
f.content(format!("Deleted {} messages", deleted_count))
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,2 +1,3 @@
|
||||
pub mod no_voicechannel;
|
||||
pub mod now_playing;
|
||||
pub mod queue;
|
||||
|
@ -0,0 +1,19 @@
|
||||
use crate::utils::error::BotResult;
|
||||
use bot_serenityutils::core::SHORT_TIMEOUT;
|
||||
use bot_serenityutils::ephemeral_message::EphemeralMessage;
|
||||
use serenity::http::Http;
|
||||
use serenity::model::prelude::ChannelId;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Creates a not in a voicechannel message
|
||||
pub async fn create_no_voicechannel_message(
|
||||
http: &Arc<Http>,
|
||||
channel_id: ChannelId,
|
||||
) -> BotResult<()> {
|
||||
EphemeralMessage::create(http, channel_id, SHORT_TIMEOUT, |m| {
|
||||
m.content("‼️ I'm not in a Voice Channel")
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
use crate::utils::context_data::MusicPlayers;
|
||||
use lavalink_rs::gateway::LavalinkEventHandler;
|
||||
use lavalink_rs::model::{TrackFinish, TrackStart};
|
||||
use lavalink_rs::LavalinkClient;
|
||||
use serenity::async_trait;
|
||||
use serenity::prelude::TypeMapKey;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use typemap_rev::TypeMap;
|
||||
|
||||
pub struct LavalinkHandler {
|
||||
pub data: Arc<RwLock<TypeMap>>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LavalinkEventHandler for LavalinkHandler {
|
||||
async fn track_start(&self, _client: LavalinkClient, event: TrackStart) {
|
||||
log::info!("Track started!\nGuild: {}", event.guild_id);
|
||||
}
|
||||
async fn track_finish(&self, _: LavalinkClient, event: TrackFinish) {
|
||||
log::info!("Track finished!\nGuild: {}", event.guild_id);
|
||||
let player = {
|
||||
let data = self.data.read().await;
|
||||
let players = data.get::<MusicPlayers>().unwrap();
|
||||
|
||||
players.get(&event.guild_id).cloned()
|
||||
};
|
||||
if let Some(player) = player {
|
||||
let mut player = player.lock().await;
|
||||
if let Err(e) = player.play_next().await {
|
||||
log::error!("Failed to play next song: {:?}", e);
|
||||
}
|
||||
if let Err(e) = player.update_now_playing().await {
|
||||
log::error!("Failed to update now playing embed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lavalink;
|
||||
|
||||
impl TypeMapKey for Lavalink {
|
||||
type Value = Arc<LavalinkClient>;
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
use crate::messages::music::now_playing::update_now_playing_msg;
|
||||
use crate::providers::music::lavalink::Lavalink;
|
||||
use crate::providers::music::lyrics::get_lyrics;
|
||||
use crate::providers::music::queue::MusicQueue;
|
||||
use crate::utils::context_data::MusicPlayers;
|
||||
use crate::utils::error::BotResult;
|
||||
use bot_serenityutils::core::{MessageHandle, SHORT_TIMEOUT};
|
||||
use bot_serenityutils::ephemeral_message::EphemeralMessage;
|
||||
use lavalink_rs::LavalinkClient;
|
||||
use serenity::prelude::TypeMap;
|
||||
use serenity::{
|
||||
client::Context,
|
||||
http::Http,
|
||||
model::id::{ChannelId, GuildId},
|
||||
};
|
||||
use songbird::Songbird;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
|
||||
pub struct MusicPlayer {
|
||||
client: Arc<LavalinkClient>,
|
||||
http: Arc<Http>,
|
||||
queue: MusicQueue,
|
||||
guild_id: GuildId,
|
||||
now_playing_msg: Option<Arc<RwLock<MessageHandle>>>,
|
||||
msg_channel: ChannelId,
|
||||
leave_flag: bool,
|
||||
paused: bool,
|
||||
}
|
||||
|
||||
impl MusicPlayer {
|
||||
/// Creates a new music player
|
||||
pub fn new(
|
||||
client: Arc<LavalinkClient>,
|
||||
http: Arc<Http>,
|
||||
guild_id: GuildId,
|
||||
msg_channel: ChannelId,
|
||||
) -> Self {
|
||||
Self {
|
||||
client,
|
||||
http,
|
||||
guild_id,
|
||||
queue: MusicQueue::new(),
|
||||
msg_channel,
|
||||
now_playing_msg: None,
|
||||
leave_flag: false,
|
||||
paused: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Joins a given voice channel
|
||||
pub async fn join(
|
||||
ctx: &Context,
|
||||
guild_id: GuildId,
|
||||
voice_channel_id: ChannelId,
|
||||
msg_channel_id: ChannelId,
|
||||
) -> BotResult<Arc<Mutex<MusicPlayer>>> {
|
||||
let manager = songbird::get(ctx).await.unwrap();
|
||||
let (_, connection) = manager.join_gateway(guild_id, voice_channel_id).await;
|
||||
let connection = connection?;
|
||||
|
||||
let player = {
|
||||
let mut data = ctx.data.write().await;
|
||||
let client = data.get::<Lavalink>().unwrap();
|
||||
client.create_session(&connection).await?;
|
||||
let player = MusicPlayer::new(
|
||||
Arc::clone(client),
|
||||
Arc::clone(&ctx.http),
|
||||
guild_id,
|
||||
msg_channel_id,
|
||||
);
|
||||
let player = Arc::new(Mutex::new(player));
|
||||
let players = data.get_mut::<MusicPlayers>().unwrap();
|
||||
players.insert(guild_id.0, Arc::clone(&player));
|
||||
player
|
||||
};
|
||||
|
||||
wait_for_disconnect(
|
||||
Arc::clone(&ctx.data),
|
||||
Arc::clone(&player),
|
||||
manager,
|
||||
guild_id,
|
||||
);
|
||||
|
||||
Ok(player)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the inner queue
|
||||
pub fn queue(&mut self) -> &mut MusicQueue {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
/// Skips to the next song
|
||||
pub async fn skip(&mut self) -> BotResult<()> {
|
||||
self.client.stop(self.guild_id.0).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stops playback and leaves the channel
|
||||
pub async fn stop(&mut self) -> BotResult<()> {
|
||||
self.queue.clear();
|
||||
self.client.stop(self.guild_id.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the lyrics for the currently playing song
|
||||
pub async fn lyrics(&self) -> BotResult<Option<String>> {
|
||||
if let Some(current) = self.queue.current() {
|
||||
let title = current.title();
|
||||
let artist = current.author();
|
||||
get_lyrics(artist, title).await
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Plays the next song in the queue
|
||||
pub async fn play_next(&mut self) -> BotResult<()> {
|
||||
while !self.try_play_next().await? {}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to play the next song
|
||||
pub async fn try_play_next(&mut self) -> BotResult<bool> {
|
||||
let mut next = if let Some(n) = self.queue.next() {
|
||||
log::trace!("Next is {:?}", n);
|
||||
n
|
||||
} else {
|
||||
return Ok(true);
|
||||
};
|
||||
let url = if let Some(url) = next.url().await {
|
||||
url
|
||||
} else {
|
||||
self.send_error_message(format!(
|
||||
"‼️ Could not find a video to play for '{}' by '{}'",
|
||||
next.title(),
|
||||
next.author()
|
||||
))
|
||||
.await?;
|
||||
log::debug!("Could not find playable candidate for song.");
|
||||
return Ok(false);
|
||||
};
|
||||
let query_information = match self.client.auto_search_tracks(url).await {
|
||||
Ok(i) => i,
|
||||
Err(e) => {
|
||||
log::error!("Failed to search for song: {}", e);
|
||||
self.send_error_message(format!(
|
||||
"‼️ Failed to retrieve information for song '{}' by '{}': {:?}",
|
||||
next.title(),
|
||||
next.author(),
|
||||
e
|
||||
))
|
||||
.await?;
|
||||
return Ok(false);
|
||||
}
|
||||
};
|
||||
|
||||
if query_information.tracks.len() == 0 {
|
||||
return Ok(false);
|
||||
}
|
||||
let track = query_information.tracks[0].clone();
|
||||
self.client.play(self.guild_id.0, track).start().await?;
|
||||
self.queue.set_current(next);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Sets the new now playing message of the queue
|
||||
pub async fn set_now_playing(&mut self, message: Arc<RwLock<MessageHandle>>) {
|
||||
let _ = self.delete_now_playing().await;
|
||||
self.now_playing_msg = Some(message)
|
||||
}
|
||||
|
||||
/// Updates the now playing message
|
||||
pub async fn update_now_playing(&self) -> BotResult<()> {
|
||||
if let (Some(current), Some(np)) = (self.queue.current(), &self.now_playing_msg) {
|
||||
update_now_playing_msg(&self.http, np, &mut current.clone(), self.is_paused()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deletes the now playing message
|
||||
pub async fn delete_now_playing(&mut self) -> BotResult<()> {
|
||||
if let Some(np) = mem::take(&mut self.now_playing_msg) {
|
||||
let np = np.read().await;
|
||||
let msg = np.get_message(&self.http).await?;
|
||||
msg.delete(&self.http).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Pauses playback
|
||||
pub async fn toggle_paused(&mut self) -> BotResult<()> {
|
||||
self.paused = !self.paused;
|
||||
self.client.set_pause(self.guild_id.0, self.paused).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns if playback is paused
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.paused
|
||||
}
|
||||
|
||||
/// Returns the now playing message of the player
|
||||
pub fn now_playing_message(&self) -> &Option<Arc<RwLock<MessageHandle>>> {
|
||||
&self.now_playing_msg
|
||||
}
|
||||
|
||||
/// Deletes the now playing message from the player
|
||||
pub fn clear_now_playing(&mut self) {
|
||||
self.now_playing_msg = None;
|
||||
}
|
||||
|
||||
/// Sets the leave flag to the given value
|
||||
pub fn set_leave_flag(&mut self, flag: bool) {
|
||||
self.leave_flag = flag;
|
||||
}
|
||||
|
||||
/// Sends a play error message to the players test channel
|
||||
async fn send_error_message(&self, content: String) -> BotResult<()> {
|
||||
EphemeralMessage::create(&self.http, self.msg_channel, SHORT_TIMEOUT, |m| {
|
||||
m.content(content)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Stats a tokio coroutine to check for player disconnect conditions
|
||||
fn wait_for_disconnect(
|
||||
data: Arc<RwLock<TypeMap>>,
|
||||
player: Arc<Mutex<MusicPlayer>>,
|
||||
manager: Arc<Songbird>,
|
||||
guild_id: GuildId,
|
||||
) {
|
||||
let mut leave_in: i32 = 5;
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||||
if manager.get(guild_id).is_none() {
|
||||
return; // leave when there's no connection to handle
|
||||
}
|
||||
let mut player_lock = player.lock().await;
|
||||
|
||||
if player_lock.leave_flag {
|
||||
log::debug!("Waiting to leave");
|
||||
|
||||
if leave_in <= 0 {
|
||||
log::debug!("Leaving voice channel");
|
||||
|
||||
if let Some(handler) = manager.get(guild_id) {
|
||||
let mut handler_lock = handler.lock().await;
|
||||
let _ = handler_lock.leave().await;
|
||||
}
|
||||
|
||||
let _ = manager.remove(guild_id).await;
|
||||
let mut data = data.write().await;
|
||||
let players = data.get_mut::<MusicPlayers>().unwrap();
|
||||
players.remove(&guild_id.0);
|
||||
let _ = player_lock.stop().await;
|
||||
let _ = player_lock.delete_now_playing().await;
|
||||
log::debug!("Left the voice channel");
|
||||
return;
|
||||
}
|
||||
leave_in -= 1;
|
||||
} else {
|
||||
log::debug!("Resetting leave value");
|
||||
leave_in = 5
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue