Merge pull request #11 from Trivernis/develop

Develop
pull/16/head v0.5.1
Trivernis 3 years ago committed by GitHub
commit 3d02241642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

2
Cargo.lock generated

@ -2282,7 +2282,7 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tobi-rs"
version = "0.5.0"
version = "0.5.1"
dependencies = [
"aspotify",
"bot-coreutils",

@ -1,6 +1,6 @@
[package]
name = "tobi-rs"
version = "0.5.0"
version = "0.5.1"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"

@ -1,241 +0,0 @@
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::PoolConnection;
use diesel::dsl::count;
use diesel::prelude::*;
use diesel::{delete, insert_into};
use std::any;
use std::fmt::Debug;
use std::str::FromStr;
use std::time::SystemTime;
use tokio_diesel::*;
#[derive(Clone)]
pub struct Database {
pool: PoolConnection,
}
unsafe impl Send for Database {}
unsafe impl Sync for Database {}
impl Database {
pub fn new(pool: PoolConnection) -> Self {
Self { pool }
}
/// Returns a guild setting from the database
pub async fn get_guild_setting<T: 'static>(
&self,
guild_id: u64,
key: String,
) -> DatabaseResult<Option<T>>
where
T: FromStr,
{
use guild_settings::dsl;
log::debug!("Retrieving setting '{}' for guild {}", key, guild_id);
let entries: Vec<GuildSetting> = dsl::guild_settings
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::key.eq(key))
.load_async::<GuildSetting>(&self.pool)
.await?;
log::trace!("Result is {:?}", entries);
if let Some(first) = entries.first() {
if any::TypeId::of::<T>() == any::TypeId::of::<bool>() {
Ok(first
.value
.clone()
.unwrap_or("false".to_string())
.parse::<T>()
.ok())
} else {
Ok(first.value.clone().and_then(|v| v.parse::<T>().ok()))
}
} else {
return Ok(None);
}
}
/// Upserting a guild setting
pub async fn set_guild_setting<T>(
&self,
guild_id: u64,
key: String,
value: T,
) -> DatabaseResult<()>
where
T: ToString + Debug,
{
use guild_settings::dsl;
log::debug!("Setting '{}' to '{:?}' for guild {}", key, value, guild_id);
insert_into(dsl::guild_settings)
.values(GuildSettingInsert {
guild_id: guild_id as i64,
key: key.to_string(),
value: value.to_string(),
})
.on_conflict((dsl::guild_id, dsl::key))
.do_update()
.set(dsl::value.eq(value.to_string()))
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Deletes a guild setting
pub async fn delete_guild_setting(&self, guild_id: u64, key: String) -> DatabaseResult<()> {
use guild_settings::dsl;
delete(dsl::guild_settings)
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::key.eq(key))
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Returns a list of all guild playlists
pub async fn get_guild_playlists(&self, guild_id: u64) -> DatabaseResult<Vec<GuildPlaylist>> {
use guild_playlists::dsl;
log::debug!("Retrieving guild playlists for guild {}", guild_id);
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists
.filter(dsl::guild_id.eq(guild_id as i64))
.load_async::<GuildPlaylist>(&self.pool)
.await?;
Ok(playlists)
}
/// Returns a guild playlist by name
pub async fn get_guild_playlist(
&self,
guild_id: u64,
name: String,
) -> DatabaseResult<Option<GuildPlaylist>> {
use guild_playlists::dsl;
log::debug!("Retriving guild playlist '{}' for guild {}", name, guild_id);
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::name.eq(name))
.load_async::<GuildPlaylist>(&self.pool)
.await?;
Ok(playlists.into_iter().next())
}
/// Adds a new playlist to the database overwriting the old one
pub async fn add_guild_playlist(
&self,
guild_id: u64,
name: String,
url: String,
) -> DatabaseResult<()> {
use guild_playlists::dsl;
log::debug!("Inserting guild playlist '{}' for guild {}", name, guild_id);
insert_into(dsl::guild_playlists)
.values(GuildPlaylistInsert {
guild_id: guild_id as i64,
name: name.clone(),
url: url.clone(),
})
.on_conflict((dsl::guild_id, dsl::name))
.do_update()
.set(dsl::url.eq(url))
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Returns a list of all gifs in the database
pub async fn get_all_gifs(&self) -> DatabaseResult<Vec<Gif>> {
use gifs::dsl;
log::debug!("Loading all gifs from the database");
let gifs: Vec<Gif> = dsl::gifs.load_async::<Gif>(&self.pool).await?;
Ok(gifs)
}
/// Returns a list of gifs by assigned category
pub async fn get_gifs_by_category(&self, category: &str) -> DatabaseResult<Vec<Gif>> {
use gifs::dsl;
log::debug!("Searching for gifs in category '{}'", category);
let gifs: Vec<Gif> = dsl::gifs
.filter(dsl::category.eq(category))
.load_async::<Gif>(&self.pool)
.await?;
Ok(gifs)
}
/// Adds a gif to the database
pub async fn add_gif(
&self,
url: &str,
category: Option<String>,
name: Option<String>,
) -> DatabaseResult<()> {
use gifs::dsl;
log::debug!(
"Inserting gif with url '{}' and name {:?} and category {:?}",
url,
name,
category
);
insert_into(dsl::gifs)
.values(GifInsert {
url: url.to_string(),
name,
category,
})
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Adds a command statistic to the database
pub async fn add_statistic(
&self,
version: &str,
command: &str,
executed_at: SystemTime,
success: bool,
error_msg: Option<String>,
) -> DatabaseResult<()> {
use statistics::dsl;
log::trace!("Adding statistic to database");
insert_into(dsl::statistics)
.values(StatisticInsert {
version: version.to_string(),
command: command.to_string(),
executed_at,
success,
error_msg,
})
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Returns the total number of commands executed
pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> {
use statistics::dsl;
log::trace!("Querying total number of commands");
let total_count: i64 = dsl::statistics
.select(count(dsl::id))
.first_async::<i64>(&self.pool)
.await?;
Ok(total_count as u64)
}
}

@ -0,0 +1,57 @@
use diesel::insert_into;
use diesel::prelude::*;
use tokio_diesel::*;
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::Database;
impl Database {
/// Returns a list of all gifs in the database
pub async fn get_all_gifs(&self) -> DatabaseResult<Vec<Gif>> {
use gifs::dsl;
log::debug!("Loading all gifs from the database");
let gifs: Vec<Gif> = dsl::gifs.load_async::<Gif>(&self.pool).await?;
Ok(gifs)
}
/// Returns a list of gifs by assigned category
pub async fn get_gifs_by_category(&self, category: &str) -> DatabaseResult<Vec<Gif>> {
use gifs::dsl;
log::debug!("Searching for gifs in category '{}'", category);
let gifs: Vec<Gif> = dsl::gifs
.filter(dsl::category.eq(category))
.load_async::<Gif>(&self.pool)
.await?;
Ok(gifs)
}
/// Adds a gif to the database
pub async fn add_gif(
&self,
url: &str,
category: Option<String>,
name: Option<String>,
) -> DatabaseResult<()> {
use gifs::dsl;
log::debug!(
"Inserting gif with url '{}' and name {:?} and category {:?}",
url,
name,
category
);
insert_into(dsl::gifs)
.values(GifInsert {
url: url.to_string(),
name,
category,
})
.execute_async(&self.pool)
.await?;
Ok(())
}
}

@ -0,0 +1,66 @@
use diesel::insert_into;
use diesel::prelude::*;
use tokio_diesel::*;
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::Database;
impl Database {
/// Returns a list of all guild playlists
pub async fn get_guild_playlists(&self, guild_id: u64) -> DatabaseResult<Vec<GuildPlaylist>> {
use guild_playlists::dsl;
log::debug!("Retrieving guild playlists for guild {}", guild_id);
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists
.filter(dsl::guild_id.eq(guild_id as i64))
.load_async::<GuildPlaylist>(&self.pool)
.await?;
Ok(playlists)
}
/// Returns a guild playlist by name
pub async fn get_guild_playlist(
&self,
guild_id: u64,
name: String,
) -> DatabaseResult<Option<GuildPlaylist>> {
use guild_playlists::dsl;
log::debug!("Retriving guild playlist '{}' for guild {}", name, guild_id);
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::name.eq(name))
.load_async::<GuildPlaylist>(&self.pool)
.await?;
Ok(playlists.into_iter().next())
}
/// Adds a new playlist to the database overwriting the old one
pub async fn add_guild_playlist(
&self,
guild_id: u64,
name: String,
url: String,
) -> DatabaseResult<()> {
use guild_playlists::dsl;
log::debug!("Inserting guild playlist '{}' for guild {}", name, guild_id);
insert_into(dsl::guild_playlists)
.values(GuildPlaylistInsert {
guild_id: guild_id as i64,
name: name.clone(),
url: url.clone(),
})
.on_conflict((dsl::guild_id, dsl::name))
.do_update()
.set(dsl::url.eq(url))
.execute_async(&self.pool)
.await?;
Ok(())
}
}

@ -0,0 +1,89 @@
use std::any;
use std::fmt::Debug;
use std::str::FromStr;
use diesel::prelude::*;
use diesel::{delete, insert_into};
use tokio_diesel::*;
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::Database;
impl Database {
/// Returns a guild setting from the database
pub async fn get_guild_setting<T: 'static>(
&self,
guild_id: u64,
key: String,
) -> DatabaseResult<Option<T>>
where
T: FromStr,
{
use guild_settings::dsl;
log::debug!("Retrieving setting '{}' for guild {}", key, guild_id);
let entries: Vec<GuildSetting> = dsl::guild_settings
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::key.eq(key))
.load_async::<GuildSetting>(&self.pool)
.await?;
log::trace!("Result is {:?}", entries);
if let Some(first) = entries.first() {
if any::TypeId::of::<T>() == any::TypeId::of::<bool>() {
Ok(first
.value
.clone()
.unwrap_or("false".to_string())
.parse::<T>()
.ok())
} else {
Ok(first.value.clone().and_then(|v| v.parse::<T>().ok()))
}
} else {
return Ok(None);
}
}
/// Upserting a guild setting
pub async fn set_guild_setting<T>(
&self,
guild_id: u64,
key: String,
value: T,
) -> DatabaseResult<()>
where
T: ToString + Debug,
{
use guild_settings::dsl;
log::debug!("Setting '{}' to '{:?}' for guild {}", key, value, guild_id);
insert_into(dsl::guild_settings)
.values(GuildSettingInsert {
guild_id: guild_id as i64,
key: key.to_string(),
value: value.to_string(),
})
.on_conflict((dsl::guild_id, dsl::key))
.do_update()
.set(dsl::value.eq(value.to_string()))
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Deletes a guild setting
pub async fn delete_guild_setting(&self, guild_id: u64, key: String) -> DatabaseResult<()> {
use guild_settings::dsl;
delete(dsl::guild_settings)
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::key.eq(key))
.execute_async(&self.pool)
.await?;
Ok(())
}
}

@ -0,0 +1,26 @@
pub use gifs::*;
pub use guild_playlists::*;
pub use guild_playlists::*;
pub use statistics::*;
use crate::PoolConnection;
mod gifs;
mod guild_playlists;
mod guild_settings;
mod statistics;
#[derive(Clone)]
pub struct Database {
pool: PoolConnection,
}
unsafe impl Send for Database {}
unsafe impl Sync for Database {}
impl Database {
pub fn new(pool: PoolConnection) -> Self {
Self { pool }
}
}

@ -0,0 +1,50 @@
use std::time::SystemTime;
use diesel::dsl::count;
use diesel::insert_into;
use diesel::prelude::*;
use tokio_diesel::*;
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::Database;
impl Database {
/// Adds a command statistic to the database
pub async fn add_statistic(
&self,
version: &str,
command: &str,
executed_at: SystemTime,
success: bool,
error_msg: Option<String>,
) -> DatabaseResult<()> {
use statistics::dsl;
log::trace!("Adding statistic to database");
insert_into(dsl::statistics)
.values(StatisticInsert {
version: version.to_string(),
command: command.to_string(),
executed_at,
success,
error_msg,
})
.execute_async(&self.pool)
.await?;
Ok(())
}
/// Returns the total number of commands executed
pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> {
use statistics::dsl;
log::trace!("Querying total number of commands");
let total_count: i64 = dsl::statistics
.select(count(dsl::id))
.first_async::<i64>(&self.pool)
.await?;
Ok(total_count as u64)
}
}

@ -7,6 +7,7 @@ use serenity::model::channel::Reaction;
/// Shows the next page in the menu
pub async fn next_page(ctx: &Context, menu: &mut Menu<'_>, _: Reaction) -> SerenityUtilsResult<()> {
log::debug!("Showing next page");
menu.current_page = (menu.current_page + 1) % menu.pages.len();
display_page(ctx, menu).await?;
@ -19,6 +20,7 @@ pub async fn previous_page(
menu: &mut Menu<'_>,
_: Reaction,
) -> SerenityUtilsResult<()> {
log::debug!("Showing previous page");
if menu.current_page == 0 {
menu.current_page = menu.pages.len() - 1;
} else {
@ -35,6 +37,7 @@ pub async fn close_menu(
menu: &mut Menu<'_>,
_: Reaction,
) -> SerenityUtilsResult<()> {
log::debug!("Closing menu");
menu.close(ctx.http()).await?;
let listeners = get_listeners_from_context(&ctx).await?;
let mut listeners_lock = listeners.lock().await;
@ -46,6 +49,7 @@ pub async fn close_menu(
/// Displays the menu page
async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult<()> {
log::debug!("Displaying page {}", menu.current_page);
let page = menu
.pages
.get(menu.current_page)
@ -59,6 +63,7 @@ async fn display_page(ctx: &Context, menu: &mut Menu<'_>) -> SerenityUtilsResult
e
})
.await?;
log::debug!("Page displayed");
Ok(())
}

@ -8,6 +8,7 @@ use serenity::model::channel::Message;
#[command]
#[description("Displays a list of all gifs used by the bot")]
#[bucket("general")]
#[only_in(guilds)]
async fn gifs(ctx: &Context, msg: &Message) -> CommandResult {
let database = get_database_from_context(ctx).await;
let gifs = database.get_all_gifs().await?;

@ -3,6 +3,7 @@ use serenity::framework::standard::macros::group;
use about::ABOUT_COMMAND;
use add_gif::ADD_GIF_COMMAND;
use gifs::GIFS_COMMAND;
use pain::PAIN_COMMAND;
use ping::PING_COMMAND;
use qalc::QALC_COMMAND;
use shutdown::SHUTDOWN_COMMAND;
@ -14,6 +15,7 @@ mod about;
mod add_gif;
mod gifs;
pub(crate) mod help;
mod pain;
mod ping;
mod qalc;
mod shutdown;
@ -22,5 +24,7 @@ mod time;
mod timezones;
#[group]
#[commands(ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs)]
#[commands(
ping, stats, shutdown, time, timezones, qalc, about, add_gif, gifs, pain
)]
pub struct Misc;

@ -0,0 +1,42 @@
use crate::utils::context_data::get_database_from_context;
use crate::utils::error::BotError;
use rand::prelude::IteratorRandom;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
static CATEGORY_PREFIX: &str = "pain-";
static NOT_FOUND_PAIN: &str = "404";
#[command]
#[description("Various types of pain (pain-peko)")]
#[usage("<pain-type>")]
#[example("peko")]
#[min_args(1)]
#[max_args(1)]
#[bucket("general")]
async fn pain(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
log::debug!("Got pain command");
let pain_type = args.message().to_lowercase();
let database = get_database_from_context(ctx).await;
let mut gifs = database
.get_gifs_by_category(format!("{}{}", CATEGORY_PREFIX, pain_type).as_str())
.await?;
if gifs.is_empty() {
log::debug!("No gif found for pain {}. Using 404", pain_type);
gifs = database
.get_gifs_by_category(format!("{}{}", CATEGORY_PREFIX, NOT_FOUND_PAIN).as_str())
.await?;
}
let gif = gifs
.into_iter()
.choose(&mut rand::thread_rng())
.ok_or(BotError::from("No gifs found."))?;
log::trace!("Gif for pain is {:?}", gif);
msg.reply(ctx, gif.url).await?;
Ok(())
}

@ -10,7 +10,7 @@ use crate::commands::music::{get_queue_for_guild, is_dj};
#[only_in(guilds)]
#[description("Clears the queue")]
#[usage("")]
#[aliases("cl")]
#[aliases("cq", "clear-queue", "clearqueue")]
#[bucket("general")]
async fn clear_queue(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();

@ -7,7 +7,7 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::get_queue_for_guild;
use crate::messages::music::create_now_playing_msg;
use crate::messages::music::now_playing::create_now_playing_msg;
#[command]
#[only_in(guilds)]

@ -22,16 +22,18 @@ use current::CURRENT_COMMAND;
use join::JOIN_COMMAND;
use leave::LEAVE_COMMAND;
use lyrics::LYRICS_COMMAND;
use move_song::MOVE_SONG_COMMAND;
use pause::PAUSE_COMMAND;
use play::PLAY_COMMAND;
use play_next::PLAY_NEXT_COMMAND;
use playlists::PLAYLISTS_COMMAND;
use queue::QUEUE_COMMAND;
use remove_song::REMOVE_SONG_COMMAND;
use save_playlist::SAVE_PLAYLIST_COMMAND;
use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND;
use crate::messages::music::update_now_playing_msg;
use crate::messages::music::now_playing::update_now_playing_msg;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::youtube_dl;
use crate::providers::settings::{get_setting, Setting};
@ -43,11 +45,13 @@ mod current;
mod join;
mod leave;
mod lyrics;
mod move_song;
mod pause;
mod play;
mod play_next;
mod playlists;
mod queue;
mod remove_song;
mod save_playlist;
mod shuffle;
mod skip;
@ -66,7 +70,9 @@ mod skip;
pause,
save_playlist,
playlists,
lyrics
lyrics,
move_song,
remove_song
)]
pub struct Music;

@ -0,0 +1,40 @@
use crate::commands::music::{get_queue_for_guild, is_dj};
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
#[command]
#[description("Moves a song in the queue from one position to a new one")]
#[usage("<old-pos> <new-pos>")]
#[example("102 2")]
#[min_args(2)]
#[max_args(2)]
#[bucket("general")]
#[only_in(guilds)]
#[aliases("mvs", "movesong", "move-song")]
async fn move_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Moving song for guild {}", guild.id);
let pos1 = args.single::<usize>()?;
let pos2 = args.single::<usize>()?;
if !is_dj(ctx, guild.id, &msg.author).await? {
msg.channel_id.say(ctx, "Requires DJ permissions").await?;
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let mut queue_lock = queue.lock().await;
queue_lock.move_position(pos1, pos2);
}
msg.channel_id
.say(
ctx,
format!("Moved Song `{}` to new position `{}`", pos1, pos2),
)
.await?;
Ok(())
}

@ -5,7 +5,7 @@ use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use crate::messages::music::update_now_playing_msg;
use crate::messages::music::now_playing::update_now_playing_msg;
#[command]
#[only_in(guilds)]

@ -1,30 +1,49 @@
use std::cmp::min;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::get_queue_for_guild;
use crate::messages::music::queue::create_queue_menu;
use crate::providers::music::queue::Song;
#[command]
#[only_in(guilds)]
#[description("Shows the song queue")]
#[usage("")]
#[usage("(<query...>)")]
#[aliases("q")]
#[bucket("general")]
async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
async fn queue(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::trace!("Displaying queue for guild {}", guild.id);
let query = args
.iter::<String>()
.map(|s| s.unwrap().to_lowercase())
.collect::<Vec<String>>();
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue_lock = queue.lock().await;
let songs: Vec<(usize, String)> = queue_lock
let songs: Vec<(usize, Song)> = queue_lock
.entries()
.into_iter()
.map(|s| s.title().clone())
.enumerate()
.filter(|(i, s)| {
if query.is_empty() {
return true;
}
for kw in &query {
if s.title().to_lowercase().contains(kw)
|| s.author().to_lowercase().contains(kw)
|| &i.to_string() == kw
{
return true;
}
}
false
})
.map(|(i, s)| (i, s.clone()))
.collect();
log::trace!("Songs are {:?}", songs);
@ -37,26 +56,8 @@ async fn queue(ctx: &Context, msg: &Message) -> CommandResult {
return Ok(());
}
create_queue_menu(ctx, msg.channel_id, songs).await?;
let mut song_list = Vec::new();
for i in 0..min(10, songs.len() - 1) {
song_list.push(format!("{:0>3} - {}", songs[i].0 + 1, songs[i].1))
}
if songs.len() > 10 {
song_list.push("...".to_string());
let last = songs.last().unwrap();
song_list.push(format!("{:0>3} - {}", last.0 + 1, last.1))
}
log::trace!("Song list is {:?}", song_list);
msg.channel_id
.send_message(ctx, |m| {
m.embed(|e| {
e.title("Queue")
.description(format!("```\n{}\n```", song_list.join("\n")))
})
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())

@ -0,0 +1,37 @@
use crate::commands::music::{get_queue_for_guild, is_dj};
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
#[command]
#[description("Removes a song from the queue")]
#[usage("<pos>")]
#[example("102")]
#[min_args(1)]
#[max_args(1)]
#[bucket("general")]
#[only_in(guilds)]
#[aliases("rms", "removesong", "remove-song")]
async fn remove_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Moving song for guild {}", guild.id);
let pos = args.single::<usize>()?;
if !is_dj(ctx, guild.id, &msg.author).await? {
msg.channel_id.say(ctx, "Requires DJ permissions").await?;
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let mut queue_lock = queue.lock().await;
queue_lock.remove(pos);
}
msg.channel_id
.say(ctx, format!("Removed Song at `{}`", pos))
.await?;
Ok(())
}

@ -0,0 +1,2 @@
pub mod now_playing;
pub mod queue;

@ -0,0 +1,45 @@
use crate::providers::music::queue::Song;
use crate::utils::error::BotResult;
use bot_serenityutils::menu::{MenuBuilder, Page};
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::ChannelId;
use std::time::Duration;
/// Creates a new queue menu
pub async fn create_queue_menu(
ctx: &Context,
channel_id: ChannelId,
songs: Vec<(usize, Song)>,
) -> BotResult<()> {
let page_count = (songs.len() as f32 / 10.0).ceil() as usize;
let pages: Vec<Page<'static>> = songs
.chunks(10)
.enumerate()
.map(|(i, entries)| create_songs_page(page_count, i + 1, entries.to_vec()))
.collect();
MenuBuilder::new_paginator()
.add_pages(pages)
.timeout(Duration::from_secs(120))
.build(ctx, channel_id)
.await?;
Ok(())
}
/// Creates a new page with songs
fn create_songs_page(total_pages: usize, page: usize, songs: Vec<(usize, Song)>) -> Page<'static> {
let mut message = CreateMessage::default();
let description_entries: Vec<String> = songs
.into_iter()
.map(|(i, s)| format!("{:0>3}. {} - {}", i, s.author(), s.title()))
.collect();
message.embed(|e| {
e.title("Queue")
.description(format!("```md\n{}\n```", description_entries.join("\n")))
.footer(|f| f.text(format!("Page {} of {}", page, total_pages)))
});
Page::new_static(message)
}

@ -76,6 +76,18 @@ impl MusicQueue {
self.inner.clear();
}
/// Moves a song to a new position
pub fn move_position(&mut self, index: usize, new_index: usize) {
if let Some(song) = self.inner.remove(index) {
self.inner.insert(new_index, song);
}
}
/// Removes a song from the queue
pub fn remove(&mut self, index: usize) {
self.inner.remove(index);
}
/// Toggles pause
pub fn pause(&mut self) {
if let Some(current) = &self.current {

Loading…
Cancel
Save