Merge pull request #12 from Trivernis/develop

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

4
Cargo.lock generated

@ -205,7 +205,7 @@ dependencies = [
[[package]]
name = "bot-serenityutils"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"futures",
"log 0.4.14",
@ -2282,7 +2282,7 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tobi-rs"
version = "0.5.1"
version = "0.5.2"
dependencies = [
"aspotify",
"bot-coreutils",

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

@ -68,7 +68,7 @@ dependencies = [
[[package]]
name = "bot-serenityutils"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"futures",
"log",

@ -1,6 +1,6 @@
[package]
name = "bot-serenityutils"
version = "0.2.0"
version = "0.2.1"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"

@ -4,6 +4,12 @@ use serenity::http::Http;
use serenity::model::channel::Message;
use serenity::model::id::{ChannelId, MessageId};
use std::sync::Arc;
use std::time::Duration;
pub static SHORT_TIMEOUT: Duration = Duration::from_secs(5);
pub static MEDIUM_TIMEOUT: Duration = Duration::from_secs(20);
pub static LONG_TIMEOUT: Duration = Duration::from_secs(60);
pub static EXTRA_LONG_TIMEOUT: Duration = Duration::from_secs(600);
pub type BoxedEventDrivenMessage = Box<dyn EventDrivenMessage>;

@ -0,0 +1,56 @@
use crate::core::MessageHandle;
use crate::error::SerenityUtilsResult;
use serenity::builder::CreateMessage;
use serenity::http::Http;
use serenity::model::channel::Message;
use serenity::model::id::ChannelId;
use std::sync::Arc;
use std::time::Duration;
pub struct EphemeralMessage;
impl EphemeralMessage {
/// Ensures that an already existing message is
/// deleted after a certain amount of time
pub async fn create_from_message(
http: &Arc<Http>,
message: &Message,
timeout: Duration,
) -> SerenityUtilsResult<()> {
log::debug!("Creating ephemeral message from existing message");
let handle = MessageHandle::new(message.channel_id, message.id);
let http = Arc::clone(&http);
log::debug!("Starting delete task");
tokio::spawn(async move {
log::debug!("Waiting for timeout to pass");
tokio::time::sleep(timeout).await;
log::debug!("Deleting ephemeral message");
if let Err(e) = http
.delete_message(handle.channel_id, handle.message_id)
.await
{
log::error!("Failed to delete ephemeral message {:?}: {}", handle, e);
}
});
Ok(())
}
/// Creates a new message that is deleted after a certain amount of time
pub async fn create<'a, F>(
http: &Arc<Http>,
channel_id: ChannelId,
timeout: Duration,
f: F,
) -> SerenityUtilsResult<Message>
where
F: for<'b> FnOnce(&'b mut CreateMessage<'a>) -> &'b mut CreateMessage<'a>,
{
log::debug!("Creating new ephemeral message");
let msg = channel_id.send_message(http, f).await?;
Self::create_from_message(http, &msg, timeout).await?;
Ok(msg)
}
}

@ -1,3 +1,5 @@
pub mod core;
pub mod ephemeral_message;
pub mod error;
pub mod macros;
pub mod menu;

@ -0,0 +1,16 @@
/// Forwards the error directly to the user
/// without having to accept it in any handler.
/// Can only be used in async functions that return a Result.
#[macro_export]
macro_rules! forward_error {
($ctx:expr,$channel_id:expr,$result:expr) => {
match $result {
Err(e) => {
use bot_serenityutils::{core::SHORT_TIMEOUT, ephemeral_message::EphemeralMessage};
$channel_id.say($ctx, format!("‼️ {}", e)).await?;
return Ok(());
}
Ok(v) => v,
}
};
}

@ -17,6 +17,8 @@ impl TypeMapKey for EventDrivenMessageContainer {
type Value = EventDrivenMessagesRef;
}
static UPDATE_INTERVAL_SECS: u64 = 5;
/// Starts the loop to handle message updates
pub async fn start_update_loop(ctx: &Context) {
let event_messages = get_listeners_from_context(ctx)
@ -56,7 +58,7 @@ pub async fn start_update_loop(ctx: &Context) {
}
}
log::trace!("Listener unlocked");
tokio::time::sleep(Duration::from_secs(10)).await;
tokio::time::sleep(Duration::from_secs(UPDATE_INTERVAL_SECS)).await;
}
});
}

@ -189,10 +189,6 @@ impl<'a> EventDrivenMessage for Menu<'a> {
Ok(())
}
async fn on_deleted(&mut self, _: &Context) -> SerenityUtilsResult<()> {
Ok(())
}
async fn on_reaction_add(
&mut self,
ctx: &Context,
@ -216,10 +212,6 @@ impl<'a> EventDrivenMessage for Menu<'a> {
Ok(())
}
async fn on_reaction_remove(&mut self, _: &Context, _: Reaction) -> SerenityUtilsResult<()> {
Ok(())
}
}
/// A builder for messages

@ -6,25 +6,35 @@ use serenity::{async_trait, model::prelude::*};
#[async_trait]
pub trait EventDrivenMessage: Send + Sync {
/// Returns if a message has been frozen and won't handle any further events
fn is_frozen(&self) -> bool;
fn is_frozen(&self) -> bool {
false
}
/// Fired periodically
async fn update(&mut self, http: &Http) -> SerenityUtilsResult<()>;
async fn update(&mut self, _http: &Http) -> SerenityUtilsResult<()> {
Ok(())
}
/// Fired when the message was deleted
async fn on_deleted(&mut self, ctx: &Context) -> SerenityUtilsResult<()>;
async fn on_deleted(&mut self, _ctx: &Context) -> SerenityUtilsResult<()> {
Ok(())
}
/// Fired when a reaction was added to the message
async fn on_reaction_add(
&mut self,
ctx: &Context,
reaction: Reaction,
) -> SerenityUtilsResult<()>;
_ctx: &Context,
_reaction: Reaction,
) -> SerenityUtilsResult<()> {
Ok(())
}
/// Fired when a reaction was removed from the message
async fn on_reaction_remove(
&mut self,
ctx: &Context,
reaction: Reaction,
) -> SerenityUtilsResult<()>;
_ctx: &Context,
_reaction: Reaction,
) -> SerenityUtilsResult<()> {
Ok(())
}
}

@ -1,5 +1,7 @@
use crate::utils::context_data::get_database_from_context;
use bot_coreutils::url;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
@ -25,7 +27,11 @@ async fn add_gif(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
let database = get_database_from_context(&ctx).await;
database.add_gif(&url, category, name).await?;
msg.reply(ctx, "Gif added to database").await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |c| {
c.reference_message(msg)
.content("Gif added to the database.")
})
.await?;
Ok(())
}

@ -17,13 +17,13 @@ async fn time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let second_timezone = args.single::<String>().ok();
let from_timezone: Tz = if let Some(first) = &first_timezone {
first.parse::<Tz>()?
forward_error!(ctx, msg.channel_id, first.parse::<Tz>())
} else {
Tz::UTC
};
let to_timezone = if let Some(second) = &second_timezone {
second.parse::<Tz>()?
forward_error!(ctx, msg.channel_id, second.parse::<Tz>())
} else {
Tz::UTC
};
@ -33,18 +33,25 @@ async fn time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
} else {
let now = Utc::now();
if second_timezone.is_some() {
from_timezone.datetime_from_str(
&format!("{} {}:00", now.format("%Y-%m-%d"), &*when),
"%Y-%m-%d %H:%M:%S",
)?
forward_error!(
ctx,
msg.channel_id,
from_timezone.datetime_from_str(
&format!("{} {}:00", now.format("%Y-%m-%d"), &*when),
"%Y-%m-%d %H:%M:%S",
)
)
} else {
let timezone: Tz = "UTC".parse().unwrap();
timezone
.datetime_from_str(
forward_error!(
ctx,
msg.channel_id,
timezone.datetime_from_str(
&format!("{} {}:00", now.format("%Y-%m-%d"), &*when),
"%Y-%m-%d %H:%M:%S",
)?
.with_timezone(&from_timezone)
)
)
.with_timezone(&from_timezone)
}
};

@ -5,6 +5,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -21,15 +23,20 @@ async fn clear_queue(ctx: &Context, msg: &Message) -> CommandResult {
}
log::debug!("Clearing queue for guild {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
{
let mut queue_lock = queue.lock().await;
queue_lock.clear();
}
msg.channel_id
.say(ctx, "The queue has been cleared")
.await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("🧹 The queue has been cleared")
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())

@ -19,7 +19,11 @@ async fn current(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Displaying current song for queue in {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let current = {
let queue_lock = queue.lock().await;

@ -5,6 +5,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_channel_for_author, is_dj, join_channel};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::model::id::ChannelId;
#[command]
@ -18,13 +20,25 @@ async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if is_dj(ctx, guild.id, &msg.author).await? {
ChannelId(arg)
} else {
get_channel_for_author(&msg.author.id, &guild)?
forward_error!(
ctx,
msg.channel_id,
get_channel_for_author(&msg.author.id, &guild)
)
}
} else {
get_channel_for_author(&msg.author.id, &guild)?
forward_error!(
ctx,
msg.channel_id,
get_channel_for_author(&msg.author.id, &guild)
)
};
log::debug!("Joining channel {} for guild {}", channel_id, guild.id);
join_channel(ctx, channel_id, guild.id).await;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("🎤 Joined the Voice Channel")
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())

@ -5,6 +5,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, get_voice_manager, is_dj};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -21,7 +23,11 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
}
let manager = get_voice_manager(ctx).await;
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let queue_lock = queue.lock().await;
let handler = manager.get(guild.id);
@ -29,16 +35,22 @@ async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
let mut handler_lock = handler.lock().await;
handler_lock.remove_all_global_events();
}
if let Some(current) = queue_lock.current() {
current.stop()?;
}
if manager.get(guild.id).is_some() {
if let Some(current) = queue_lock.current() {
current.stop()?;
}
manager.remove(guild.id).await?;
msg.channel_id.say(ctx, "Left the voice channel").await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("👋 Left the Voice Channel")
})
.await?;
log::debug!("Left the voice channel");
} else {
msg.channel_id.say(ctx, "Not in a voice channel").await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("‼️ I'm not in a Voice Channel")
})
.await?;
log::debug!("Not in a voice channel");
}
handle_autodelete(ctx, msg).await?;

@ -16,7 +16,11 @@ async fn lyrics(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
log::debug!("Fetching lyrics for song playing in {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let queue_lock = queue.lock().await;
if let Some(current) = queue_lock.current() {

@ -194,7 +194,7 @@ fn get_channel_for_author(author_id: &UserId, guild: &Guild) -> BotResult<Channe
.voice_states
.get(author_id)
.and_then(|voice_state| voice_state.channel_id)
.ok_or(BotError::from("Not in a voice channel."))
.ok_or(BotError::from("You're not in a Voice Channel"))
}
/// Returns the voice manager from the context
@ -216,7 +216,7 @@ pub(crate) async fn get_queue_for_guild(
let queue = store
.music_queues
.get(guild_id)
.ok_or(BotError::from("No queue for server"))?
.ok_or(BotError::from("I'm not in a Voice Channel"))?
.clone();
Ok(queue)
}
@ -257,8 +257,14 @@ async fn play_next_in_queue(
let track = handler_lock.play_only_source(source);
log::trace!("Track is {:?}", track);
if queue_lock.paused() {
let _ = track.pause();
}
if let Some(np) = &queue_lock.now_playing_msg {
if let Err(e) = update_now_playing_msg(http, np, track.metadata(), false).await {
if let Err(e) =
update_now_playing_msg(http, np, track.metadata(), queue_lock.paused()).await
{
log::error!("Failed to update now playing message: {:?}", e);
}
}

@ -1,4 +1,7 @@
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
@ -25,16 +28,22 @@ async fn move_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
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?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content(format!(
"↕ Moved Song `{}` to new position `{}`",
pos1, pos2
))
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())
}

@ -6,6 +6,8 @@ use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use crate::messages::music::now_playing::update_now_playing_msg;
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -20,21 +22,31 @@ async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
return Ok(());
}
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let mut queue_lock = queue.lock().await;
if let Some(_) = queue_lock.current() {
queue_lock.pause();
if queue_lock.paused() {
log::debug!("Paused");
msg.channel_id.say(ctx, "Paused playback").await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("⏸️ Paused playback")
})
.await?;
if let (Some(menu), Some(current)) = (&queue_lock.now_playing_msg, queue_lock.current())
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;
}
} else {
log::debug!("Resumed");
msg.channel_id.say(ctx, "Resumed playback").await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("▶ Resumed playback")
})
.await?;
if let (Some(menu), Some(current)) = (&queue_lock.now_playing_msg, queue_lock.current())
{
update_now_playing_msg(&ctx.http, menu, current.metadata(), true).await?;

@ -33,7 +33,11 @@ async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
handler = Some(join_channel(ctx, channel_id, guild.id).await);
}
let handler_lock = handler.ok_or(CommandError::from("Not in a voice channel"))?;
let handler_lock = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let songs = get_songs_for_query(&ctx, msg, query).await?;

@ -35,7 +35,11 @@ async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
handler = Some(join_channel(ctx, channel_id, guild.id).await);
}
let handler = handler.ok_or(CommandError::from("Not in a voice channel"))?;
let handler = forward_error!(
ctx,
msg.channel_id,
handler.ok_or(CommandError::from("I'm not in a voice channel"))
);
let mut songs = get_songs_for_query(&ctx, msg, query).await?;

@ -23,7 +23,11 @@ async fn queue(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
.map(|s| s.unwrap().to_lowercase())
.collect::<Vec<String>>();
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let queue_lock = queue.lock().await;
let songs: Vec<(usize, Song)> = queue_lock
.entries()

@ -1,4 +1,7 @@
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
@ -24,14 +27,20 @@ async fn remove_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
return Ok(());
}
{
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
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?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content(format!("🗑️ Removed Song at `{}`", pos))
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())
}

@ -5,6 +5,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -20,15 +22,20 @@ async fn shuffle(ctx: &Context, msg: &Message) -> CommandResult {
msg.channel_id.say(ctx, "Requires DJ permissions").await?;
return Ok(());
}
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
{
let mut queue_lock = queue.lock().await;
queue_lock.shuffle();
}
msg.channel_id
.say(ctx, "The queue has been shuffled")
.await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("🔀 The queue has been shuffled")
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())

@ -5,6 +5,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_queue_for_guild, is_dj};
use bot_serenityutils::core::SHORT_TIMEOUT;
use bot_serenityutils::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -19,14 +21,21 @@ async fn skip(ctx: &Context, msg: &Message) -> CommandResult {
return Ok(());
}
log::debug!("Skipping song for guild {}", guild.id);
let queue = get_queue_for_guild(ctx, &guild.id).await?;
let queue = forward_error!(
ctx,
msg.channel_id,
get_queue_for_guild(ctx, &guild.id).await
);
let queue_lock = queue.lock().await;
if let Some(current) = queue_lock.current() {
current.stop()?;
}
msg.channel_id.say(ctx, "Skipped to the next song").await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("⏭ Skipped to the next song")
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())

@ -1,6 +1,9 @@
use crate::client::get_client;
use crate::utils::logging::init_logger;
#[macro_use]
extern crate bot_serenityutils;
pub mod client;
mod commands;
pub mod handler;

Loading…
Cancel
Save