Compare commits

..

21 Commits

Author SHA1 Message Date
trivernis 9bfb35dbce
Fix yt-dlp
ci/woodpecker/push/build Pipeline failed Details
ci/woodpecker/tag/container Pipeline was successful Details
11 months ago
trivernis 048d6a5cba
Merge branch 'main' of ssh://git.trivernis.net:22321/Trivernis/2b-rs 11 months ago
trivernis b2b09a3f4f
Update animethemes-rs
ci/woodpecker/push/build Pipeline failed Details
ci/woodpecker/tag/container Pipeline was successful Details
11 months ago
trivernis e3250e1bc6
Add forgejo id to container build task
ci/woodpecker/tag/container Pipeline was successful Details
11 months ago
trivernis 2d869f3ea8
Update dependency to fix build
ci/woodpecker/tag/container Pipeline failed Details
11 months ago
trivernis e6726e41c8
Update README
ci/woodpecker/push/build Pipeline failed Details
ci/woodpecker/tag/container Pipeline failed Details
11 months ago
trivernis bc57733dbf
Fix build workflow
ci/woodpecker/push/build Pipeline failed Details
11 months ago
trivernis 37b22a7d51
Install clippy in build workflow
ci/woodpecker/push/build Pipeline failed Details
11 months ago
trivernis 6c9889e3e1
Increment version
ci/woodpecker/push/build Pipeline failed Details
11 months ago
trivernis 8836f311c9
Add two woodpecker workflows 11 months ago
trivernis 06c8ac2446
Fix docker build 2 years ago
trivernis 84eaf56810
Update serenity additions 2 years ago
trivernis 118832036e
Remove lavalink dependencies and update to new serenity 2 years ago
trivernis 065fc688ad
Run Rustfmt 2 years ago
trivernis 05aadf615d
Update Workflows 2 years ago
trivernis 52a956b2f7
Edit workflows 2 years ago
trivernis 7a70e30c7e
Fix Containerfile and order of migrations
Signed-off-by: trivernis <trivernis@protonmail.com>
2 years ago
Julius Riegel e23a0bd325
Merge pull request #57 from Trivernis/develop
Update animethemes again
2 years ago
trivernis 4089cd2305
Update animethemes again
Signed-off-by: trivernis <trivernis@protonmail.com>
2 years ago
Julius Riegel 6c100ad1e5
Merge pull request #56 from Trivernis/develop
Fix issues with animethemes
2 years ago
trivernis c217e65557
Fix issues with animethemes
Signed-off-by: trivernis <trivernis@protonmail.com>
2 years ago

@ -3,7 +3,8 @@ name: Build Container
on:
workflow_dispatch:
push:
branches: [ main, actions ]
tags:
- "v*"
schedule:
# daily builds to always include patches in the docker image
- cron: '0 4 * * *'

@ -1,4 +1,4 @@
name: Build and Test
name: Check
on:
workflow_dispatch:
@ -31,9 +31,15 @@ jobs:
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Run Rustfmt
uses: actions-rust-lang/rustfmt@v1.0.0
- name: Build
run: cargo build --verbose
- name: Run Clippy
uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Test coreutils
run: cargo test --verbose --package bot-coreutils

@ -0,0 +1,39 @@
version: 1
when:
- event: [pull_request]
- event: push
branch:
- ${CI_REPO_DEFAULT_BRANCH}
- release/*
- fix/*
steps:
test:
image: rust:alpine
commands:
- apk add --no-cache --force-overwrite \
build-base \
openssl-dev \
libopusenc-dev \
libpq-dev \
curl \
bash
- rustup default stable
- rustup component add clippy --toolchain stable-x86_64-unknown-linux-musl
- cargo clippy
- cargo test --verbose --package bot-coreutils
- cargo test --verbose --package bot-database
- cargo test --verbose
build:
image: rust:alpine
commands:
- apk add --no-cache --force-overwrite \
build-base \
openssl-dev \
libopusenc-dev \
libpq-dev \
curl \
bash
- cargo build
when:
- event: [pull_request]

@ -0,0 +1,20 @@
version: 1
when:
- event: [tag]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
build:
image: woodpeckerci/plugin-docker-buildx
secrets: [forgejo_token]
settings:
dockerfile: Containerfile
tag: ${CI_COMMIT_TAG##v}
repo: git.trivernis.net/trivernis/2b-rs
registry: git.trivernis.net
platforms: linux/amd64
username:
from_secret: forgejo_id
password:
from_secret: forgejo_token

2959
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -3,7 +3,7 @@ members=["bot-coreutils", "bot-database", "bot-database/migration", "."]
[package]
name = "tobi-rs"
version = "0.10.1"
version = "0.11.4"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
@ -15,41 +15,41 @@ panic = 'abort'
[dependencies]
bot-database = {path="./bot-database"}
bot-coreutils = {path="./bot-coreutils"}
serenity = "0.10"
serenity = "0.11.5"
dotenv = "0.15.0"
serde_derive = "1.0.137"
serde = "1.0.137"
thiserror = "1.0.31"
minecraft-data-rs = "0.4.5"
songbird = "0.2.2"
serde_json = "1.0.81"
serde_derive = "1.0.145"
serde = "1.0.145"
thiserror = "1.0.37"
minecraft-data-rs = "0.5.0"
serde_json = "1.0.86"
rand = "0.8.5"
regex = "1.5.6"
regex = "1.6.0"
aspotify = "0.7.1"
lazy_static = "1.4.0"
futures = "0.3.21"
chrono = "0.4.19"
sysinfo = "0.24.2"
reqwest = "0.11.10"
chrono-tz = "0.6.1"
sauce-api = "0.9.3"
futures = "0.3.24"
chrono = "0.4.22"
sysinfo = "0.26.4"
reqwest = "0.11.12"
chrono-tz = "0.6.3"
sauce-api = "1.0.0"
rustc_version_runtime = "0.2.1"
trigram = "0.4.4"
typemap_rev = "0.1.5"
typemap_rev = "0.2.0"
youtube-metadata = "0.2.0"
xkcd-search = "0.1.2"
animethemes-rs = "0.3.0"
build-time = "0.1.1"
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
tracing = "0.1.34"
serenity-rich-interaction= "0.2.6"
animethemes-rs = "0.4.5"
build-time = "0.1.2"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing = "0.1.37"
serenity-additions = "0.3.4"
[dependencies.songbird]
version = "0.3.0"
features = ["yt-dlp"]
[dependencies.tokio]
version = "1.19.2"
version = "1.21.2"
features = ["macros", "rt-multi-thread"]
[dependencies.lavalink-rs]
git = "https://gitlab.com/vicky5124/lavalink-rs/"
rev = "2487c295"
features=["native", "serenity", "songbird", "tracing-log"]
default-features = false
# [patch.crates-io]
# serenity-additions = { path = "../serenity-additions" }

@ -4,7 +4,7 @@ FROM ${BASE_IMAGE} AS build_base
RUN apk update
RUN apk add --no-cache --force-overwrite \
build-base \
openssl3-dev \
openssl-dev \
libopusenc-dev \
libpq-dev \
curl \
@ -22,23 +22,24 @@ COPY Cargo.toml Cargo.lock ./
COPY src ./src
COPY bot-coreutils ./bot-coreutils
COPY bot-database ./bot-database
RUN cargo build --release --verbose
RUN cargo build --release
RUN mkdir /tmp/tobi
RUN cp target/release/tobi-rs /tmp/tobi/
FROM ${BASE_IMAGE} AS runtime-base
RUN apk update
RUN apk add --no-cache --force-overwrite \
openssl3 \
openssl \
libopusenc \
libpq \
python3 \
py3-pip \
qalc \
ffmpeg \
bash
RUN pip3 install youtube-dl
RUN pip3 install yt-dlp --break-system-packages
RUN rm -rf /var/lib/{cache,log}/ /var/cache
FROM runtime-base
COPY --from=builder /tmp/tobi/tobi-rs .
ENTRYPOINT ["/tobi-rs"]
ENTRYPOINT ["/tobi-rs"]

@ -63,4 +63,4 @@ The required values are:
## License
It's GPL 3.0
See LICENSE.md

@ -7,8 +7,8 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = { version = "1.19.2", features = ["process", "io-util"] }
tokio = { version = "1.21.2", features = ["process", "io-util"] }
log = "0.4.17"
url = "2.2.2"
url = "2.3.1"
mime_guess = "2.0.4"
rand = "0.8.5"

@ -11,7 +11,7 @@ pub async fn run_command_async(command: &str, args: &[&str]) -> io::Result<Strin
let stdout = String::from_utf8_lossy(&process_output.stdout[..]);
if stderr.len() != 0 {
log::debug!("STDERR of command {}: {}", command, stderr);
log::trace!("STDERR of command {}: {}", command, stderr);
}
log::trace!("Command output is {}", stdout);

@ -8,12 +8,12 @@ edition = "2018"
[dependencies]
dotenv = "0.15.0"
chrono = "0.4.19"
thiserror = "1.0.31"
tracing = "0.1.34"
chrono = "0.4.22"
thiserror = "1.0.37"
tracing = "0.1.37"
[dependencies.sea-orm]
version = "0.8.0"
version = "0.9.3"
features = ["runtime-tokio-native-tls", "sqlx-postgres"]
[dependencies.migration]

@ -9,4 +9,5 @@ name = "migration"
path = "src/lib.rs"
[dependencies]
sea-orm-migration = "0.8.3"
sea-orm-migration = "0.9.3"
tokio = { version = "1.21.2", features = ["rt", "net", "tracing"] }

@ -9,8 +9,8 @@ pub struct Migrator;
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220029_164527_change_timestamp_format::Migration),
Box::new(m20220101_000001_create_table::Migration),
Box::new(m20220029_164527_change_timestamp_format::Migration),
]
}
}

@ -1,7 +1,7 @@
use migration::Migrator;
use sea_orm_migration::prelude::*;
#[async_std::main]
#[tokio::main]
async fn main() {
cli::run_cli(Migrator).await;
}

@ -10,13 +10,13 @@ use serenity::framework::standard::{CommandResult, DispatchError};
use serenity::framework::StandardFramework;
use serenity::model::channel::Message;
use serenity::model::id::UserId;
use serenity::prelude::GatewayIntents;
use serenity::Client;
use serenity_rich_interaction::RegisterRichInteractions;
use serenity_additions::RegisterAdditions;
use songbird::SerenityInit;
use crate::commands::*;
use crate::handler::{get_raw_event_handler, Handler};
use crate::utils;
use crate::utils::context_data::{
get_database_from_context, DatabaseContainer, MusicPlayers, Store, StoreData,
};
@ -25,24 +25,15 @@ use crate::utils::error::{BotError, BotResult};
pub async fn get_client() -> BotResult<Client> {
let token = env::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?;
let database = get_database().await?;
let client = Client::builder(token)
.register_rich_interactions_with(get_raw_event_handler())
let client = Client::builder(token, GatewayIntents::all())
.register_serenity_additions_with(get_raw_event_handler())
.event_handler(Handler)
.framework(get_framework().await)
.register_songbird()
.type_map_insert::<Store>(StoreData::new())
.type_map_insert::<Store>(StoreData::create().await)
.type_map_insert::<DatabaseContainer>(database)
.type_map_insert::<MusicPlayers>(HashMap::new())
.await?;
let data = client.data.clone();
let current_application = client
.cache_and_http
.http
.get_current_application_info()
.await?;
utils::initialize_lavalink(data, current_application).await?;
Ok(client)
}
@ -124,7 +115,7 @@ async fn before_hook(ctx: &Context, msg: &Message, _: &str) -> bool {
}
#[hook]
async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError) {
async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError, command_name: &str) {
match error {
DispatchError::Ratelimited(info) => {
if info.is_first_try {
@ -140,13 +131,19 @@ async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError) {
DispatchError::OnlyForDM => {
let _ = msg
.channel_id
.say(&ctx.http, "This command only works via DM")
.say(
&ctx.http,
format!("The command {command_name} only works via DM"),
)
.await;
}
DispatchError::OnlyForGuilds => {
let _ = msg
.channel_id
.say(&ctx.http, "This command only works on servers")
.say(
&ctx.http,
format!("The command {command_name} only works on servers"),
)
.await;
}
DispatchError::NotEnoughArguments { min, given } => {

@ -4,8 +4,8 @@ use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[description("Adds media to the database")]

@ -1,11 +1,12 @@
use futures::future::BoxFuture;
use futures::FutureExt;
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;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[description("Clears the chat (maximum 100 messages)")]

@ -19,6 +19,7 @@ pub async fn help(
groups: &[&'static CommandGroup],
owners: HashSet<UserId>,
) -> CommandResult {
tracing::debug!("Help");
let _ = help_commands::with_embeds(ctx, msg, args, help_options, groups, owners).await;
handle_autodelete(ctx, msg).await?;
Ok(())

@ -10,7 +10,6 @@ use pain::PAIN_COMMAND;
use party::PARTY_COMMAND;
use ping::PING_COMMAND;
use qalc::QALC_COMMAND;
use reset_lavalink::RESET_LAVALINK_COMMAND;
use shutdown::SHUTDOWN_COMMAND;
use stats::STATS_COMMAND;
use time::TIME_COMMAND;
@ -28,7 +27,6 @@ mod pain;
mod party;
mod ping;
mod qalc;
mod reset_lavalink;
mod shutdown;
mod stats;
mod time;
@ -37,21 +35,7 @@ mod xkcd;
#[group]
#[commands(
ping,
stats,
shutdown,
time,
timezones,
qalc,
about,
add_media,
media,
pain,
clear,
xkcd,
fuck,
party,
inspirobot,
reset_lavalink
ping, stats, shutdown, time, timezones, qalc, about, add_media, media, pain, clear, xkcd, fuck,
party, inspirobot
)]
pub struct Misc;

@ -1,40 +0,0 @@
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::providers::music::lavalink::Lavalink;
use crate::utils::initialize_lavalink;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use std::mem;
use std::sync::Arc;
#[command]
#[description("Resets the lavalink connection")]
#[aliases("reconnect_lavalink", "reset-lavalink", "reconnect-lavalink")]
#[num_args(0)]
#[owners_only]
async fn reset_lavalink(ctx: &Context, msg: &Message) -> CommandResult {
let app_info = ctx.http.get_current_application_info().await?;
destroy_lavalink(ctx).await;
initialize_lavalink(Arc::clone(&ctx.data), app_info).await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content("Reconnected to lavalink")
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())
}
async fn destroy_lavalink(ctx: &Context) {
let mut data = ctx.data.write().await;
{
let lava_client = data.remove::<Lavalink>().unwrap();
mem::drop(lava_client);
}
}

@ -33,7 +33,7 @@ async fn stats(ctx: &Context, msg: &Message) -> CommandResult {
let uptime = own_process.run_time();
let uptime = ChronoDuration::from_std(Duration::from_secs(uptime)).unwrap();
let total_commands_executed = database.get_total_commands_statistic().await?;
let shard_count = ctx.cache.shard_count().await;
let shard_count = ctx.cache.shard_count();
let discord_info = format!(
r#"

@ -6,8 +6,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -17,7 +17,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[bucket("general")]
#[checks(DJ)]
async fn clear_queue(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Clearing queue for guild {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {

@ -15,7 +15,7 @@ use crate::messages::music::now_playing::create_now_playing_msg;
#[aliases("nowplaying", "np")]
#[bucket("general")]
async fn current(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Displaying current song for queue in {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {

@ -1,62 +0,0 @@
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use serenity_rich_interaction::core::{MEDIUM_TIMEOUT, SHORT_TIMEOUT};
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
#[description("Loads an equalizer preset")]
#[usage("<preset>")]
#[num_args(1)]
#[example("bass")]
#[bucket("general")]
#[checks(DJ)]
async fn equalize(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
tracing::debug!("Changing equalizer for {}", guild.id);
let preset = args.single::<String>().unwrap();
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
player
} else {
return create_no_voicechannel_message(&ctx.http, msg.channel_id)
.await
.map_err(CommandError::from);
};
let bands = match preset.to_lowercase().as_str() {
"metal" => lavalink_rs::EQ_METAL,
"boost" => lavalink_rs::EQ_BOOST,
"base" => lavalink_rs::EQ_BASE,
"piano" => lavalink_rs::EQ_PIANO,
_ => {
EphemeralMessage::create(&ctx.http, msg.channel_id, MEDIUM_TIMEOUT, |m| {
m.content(format!(
"Unknown preset '{}'. Available are 'metal', 'boost', 'base' and 'piano'",
preset
))
})
.await?;
handle_autodelete(ctx, msg).await?;
return Ok(());
}
};
{
let mut player = player.lock().await;
player.equalize_all(bands).await?;
}
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |m| {
m.content(format!("🎛️ Changed equalizer to '{}'", preset))
})
.await?;
handle_autodelete(ctx, msg).await?;
Ok(())
}

@ -1,33 +0,0 @@
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::equalizer::create_equalizer_message;
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
#[command]
#[only_in(guilds)]
#[description("Displays the equalizer for the music player")]
#[usage("")]
#[bucket("general")]
#[checks(DJ)]
async fn equalizer(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
tracing::debug!("Displaying equalizer for guild {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {
player
} else {
return create_no_voicechannel_message(&ctx.http, msg.channel_id)
.await
.map_err(CommandError::from);
};
create_equalizer_message(&ctx, msg.channel_id, player).await?;
handle_autodelete(ctx, msg).await?;
Ok(())
}

@ -7,8 +7,8 @@ use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_channel_for_author, get_music_player_for_guild, is_dj};
use crate::providers::music::player::MusicPlayer;
use serenity::model::id::ChannelId;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -16,7 +16,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[usage("")]
#[bucket("general")]
async fn join(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
let channel_id = if let Ok(arg) = args.single::<u64>() {
if is_dj(ctx, guild.id, &msg.author).await? {
ChannelId(arg)

@ -6,8 +6,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::DJ_CHECK;
use crate::utils::context_data::MusicPlayers;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -17,13 +17,13 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[bucket("general")]
#[checks(DJ)]
async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Leave request received for guild {}", guild.id);
let manager = songbird::get(ctx).await.unwrap();
if let Some(handler) = manager.get(guild.id) {
let mut handler_lock = handler.lock().await;
let _ = handler_lock.leave().await;
handler_lock.leave().await?;
}
let mut data = ctx.data.write().await;

@ -13,7 +13,7 @@ use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
#[usage("")]
#[bucket("general")]
async fn lyrics(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Fetching lyrics for song playing in {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {

@ -18,8 +18,6 @@ use youtube_metadata::get_video_information;
use clear_queue::CLEAR_QUEUE_COMMAND;
use current::CURRENT_COMMAND;
use equalize::EQUALIZE_COMMAND;
use equalizer::EQUALIZER_COMMAND;
use join::JOIN_COMMAND;
use leave::LEAVE_COMMAND;
use lyrics::LYRICS_COMMAND;
@ -43,8 +41,6 @@ use crate::utils::error::{BotError, BotResult};
mod clear_queue;
mod current;
mod equalize;
mod equalizer;
mod join;
mod leave;
mod lyrics;
@ -75,9 +71,7 @@ mod skip;
playlists,
lyrics,
move_song,
remove_song,
equalizer,
equalize
remove_song
)]
pub struct Music;
@ -257,7 +251,6 @@ pub async fn check_dj(
) -> Result<(), Reason> {
let guild = msg
.guild(&ctx.cache)
.await
.ok_or(Reason::Log("Not in a guild".to_string()))?;
if is_dj(ctx, guild.id, &msg.author)

@ -5,8 +5,8 @@ use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[description("Moves a song in the queue from one position to a new one")]
@ -18,7 +18,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[aliases("mvs", "movesong", "move-song")]
#[checks(DJ)]
async fn move_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Moving song for guild {}", guild.id);
let pos1 = args.single::<usize>()?;

@ -6,8 +6,8 @@ use serenity::prelude::*;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -16,7 +16,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[bucket("general")]
#[checks(DJ)]
async fn pause(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Pausing playback for guild {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {

@ -22,7 +22,7 @@ use std::sync::Arc;
async fn play(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query = args.message();
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Play request received for guild {}", guild.id);
let mut player = get_music_player_for_guild(ctx, guild.id).await;

@ -22,7 +22,7 @@ use std::sync::Arc;
async fn play_next(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let query = args.message();
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Playing song as next song for guild {}", guild.id);
let mut player = get_music_player_for_guild(ctx, guild.id).await;

@ -12,7 +12,7 @@ use crate::utils::context_data::get_database_from_context;
#[usage("")]
#[bucket("general")]
async fn playlists(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Displaying playlists for guild {}", guild.id);
let database = get_database_from_context(ctx).await;

@ -16,7 +16,7 @@ use crate::providers::music::queue::Song;
#[aliases("q")]
#[bucket("general")]
async fn queue(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::trace!("Displaying queue for guild {}", guild.id);
let query = args

@ -5,8 +5,8 @@ use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[description("Removes a song from the queue")]
@ -18,7 +18,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[aliases("rms", "removesong", "remove-song")]
#[checks(DJ)]
async fn remove_song(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Moving song for guild {}", guild.id);
let pos = args.single::<usize>()?;

@ -16,7 +16,7 @@ use crate::utils::context_data::get_database_from_context;
#[bucket("general")]
#[checks(DJ)]
async fn save_playlist(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
let name: String = args.single().unwrap();
let url: &str = args.remains().unwrap();

@ -6,8 +6,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -17,7 +17,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[bucket("general")]
#[checks(DJ)]
async fn shuffle(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Shuffling queue for guild {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {

@ -6,8 +6,8 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{get_music_player_for_guild, DJ_CHECK};
use crate::messages::music::no_voicechannel::create_no_voicechannel_message;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[only_in(guilds)]
@ -17,7 +17,7 @@ use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
#[bucket("general")]
#[checks(DJ)]
async fn skip(ctx: &Context, msg: &Message) -> CommandResult {
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Skipping song for guild {}", guild.id);
let player = if let Some(player) = get_music_player_for_guild(ctx, guild.id).await {

@ -17,7 +17,7 @@ use crate::utils::context_data::get_database_from_context;
#[bucket("general")]
async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let database = get_database_from_context(ctx).await;
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
tracing::debug!("Displaying guild setting for guild {}", guild.id);
if let Some(key) = args.single::<String>().ok() {

@ -28,7 +28,7 @@ async fn set(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
return Ok(());
}
let database = get_database_from_context(ctx).await;
let guild = msg.guild(&ctx.cache).await.unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
if let Ok(value) = args.single::<String>() {
database

@ -1,4 +1,4 @@
use sauce_api::Sauce;
use sauce_api::source::Source;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
@ -59,15 +59,20 @@ async fn sauce(ctx: &Context, msg: &Message) -> CommandResult {
);
let data = ctx.data.read().await;
let store_data = data.get::<Store>().unwrap();
let sources = store_data
.sauce_nao
.check_sauces(&attachment_urls[..])
.await?;
tracing::trace!("Sources are {:?}", sources);
let mut outputs = Vec::new();
tracing::debug!("Creating menu...");
for attachment in attachment_urls {
let output = store_data
.sauce_nao
.check(&attachment)
.await
.map_err(|_| "failed to retrieve source")?;
tracing::trace!("Output is {:?}", output);
outputs.push(output)
}
show_sauce_menu(ctx, msg, sources).await?;
tracing::debug!("Creating menu...");
show_sauce_menu(ctx, msg, outputs).await?;
tracing::debug!("Menu created");
Ok(())

@ -1,12 +1,12 @@
use crate::messages::theme::create_theme_menu;
use animethemes_rs::client::AnimeThemesClient;
use animethemes_rs::includes;
use animethemes_rs::includes::{AnimeInclude, SearchIncludes};
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use serenity_rich_interaction::core::MEDIUM_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::MEDIUM_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
#[command]
#[description("Query for the opening/ending/insert song of an anime")]
@ -20,12 +20,14 @@ async fn theme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let search_results = client
.search(
query,
&[includes::ANIME],
&[
"animethemes",
"animethemes.animethemeentries",
"animethemes.animethemeentries.videos",
],
&["anime"],
SearchIncludes {
anime: AnimeInclude::default()
.themes()
.themes_entries()
.themes_entries_videos(),
..Default::default()
},
)
.await?;
if let Some(anime) = search_results.anime {

@ -4,7 +4,7 @@ use serenity::model::channel::GuildChannel;
use serenity::model::event::ResumedEvent;
use serenity::model::gateway::{Activity, Ready};
use serenity::model::guild::Member;
use serenity::model::id::{ChannelId, GuildId};
use serenity::model::id::ChannelId;
use serenity::model::voice::VoiceState;
use serenity::prelude::*;
@ -12,8 +12,8 @@ use crate::commands::music::get_music_player_for_guild;
use crate::utils::context_data::MusicPlayers;
use crate::utils::delete_messages_from_database;
use serenity::model::event;
use serenity_rich_interaction::events::RichEventHandler;
use serenity_rich_interaction::Result;
use serenity_additions::events::RichEventHandler;
use serenity_additions::Result;
/// Returns the raw event handler built from a rich event handler
pub fn get_raw_event_handler() -> RichEventHandler {
@ -34,8 +34,9 @@ async fn ready(ctx: &Context, _: &Ready) -> Result<()> {
tracing::info!("Ready");
delete_messages_from_database(&ctx).await?;
let prefix = std::env::var("BOT_PREFIX").unwrap_or("~!".to_string());
ctx.set_activity(Activity::listening(format!("{}help", prefix).as_str()))
ctx.set_activity(Activity::listening(format!("{prefix}help").as_str()))
.await;
tracing::info!("Fully initialized. Listening to {prefix}help");
Ok(())
}
@ -50,13 +51,12 @@ impl EventHandler for Handler {
async fn voice_state_update(
&self,
ctx: Context,
guild_id: Option<GuildId>,
old_state: Option<VoiceState>,
new_state: VoiceState,
) {
let mut member_count = None;
let guild_id = if let Some(gid) = guild_id {
let guild_id = if let Some(gid) = new_state.guild_id {
gid
} else {
return;
@ -81,7 +81,7 @@ impl EventHandler for Handler {
}
// handle disconnects
if let (Some(state), None) = (old_state, new_state.channel_id) {
let current_user = ctx.cache.current_user().await;
let current_user = ctx.cache.current_user();
if state.user_id == current_user.id {
let mut data = ctx.data.write().await;
@ -101,7 +101,7 @@ impl EventHandler for Handler {
async fn get_own_channel_member_count(ctx: &Context, channel_id: ChannelId) -> Option<usize> {
let guild_channel = get_guild_channel(ctx, channel_id).await?;
let current_user = ctx.cache.current_user().await;
let current_user = ctx.cache.current_user();
let members = guild_channel.members(&ctx).await.ok()?;
let own_channel = members
@ -119,7 +119,7 @@ async fn get_own_channel_member_count(ctx: &Context, channel_id: ChannelId) -> O
/// Returns the guild channel for a guild ID
async fn get_guild_channel(ctx: &Context, channel_id: ChannelId) -> Option<GuildChannel> {
if let Some(channel) = ctx.cache.channel(channel_id).await {
if let Some(channel) = ctx.cache.channel(channel_id) {
return channel.guild();
}
let channel = ctx.http.get_channel(channel_id.0).await.ok()?;

@ -3,7 +3,7 @@ use bot_database::models::Media;
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::{ChannelId, UserId};
use serenity_rich_interaction::menu::{MenuBuilder, Page};
use serenity_additions::menu::{MenuBuilder, Page};
use std::time::Duration;
/// Creates a new gifs embed

@ -3,10 +3,8 @@ use crate::utils::error::BotResult;
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::{ChannelId, UserId};
use serenity_rich_interaction::core::EXTRA_LONG_TIMEOUT;
use serenity_rich_interaction::menu::{
close_menu, display_page, MenuBuilder, Page, CLOSE_MENU_EMOJI,
};
use serenity_additions::core::EXTRA_LONG_TIMEOUT;
use serenity_additions::menu::{close_menu, display_page, MenuBuilder, Page, CLOSE_MENU_EMOJI};
static REFRESH_EMOJI: &str = "🔄";
@ -30,7 +28,7 @@ pub async fn create_inspirobot_menu(
Box::pin(async {
let message = create_inspirobot_page()
.await
.map_err(|e| serenity_rich_interaction::Error::Msg(format!("{}", e)))?;
.map_err(|e| serenity_additions::Error::Msg(format!("{}", e)))?;
Ok(message)
})
}))

@ -1,16 +1,16 @@
use crate::utils::context_data::get_database_from_context;
use crate::utils::error::BotResult;
use serenity::client::Context;
use serenity_rich_interaction::core::MessageHandle;
use serenity_additions::core::MessageHandle;
use std::time::{Duration, SystemTime};
pub mod gifs;
pub mod inspirobot;
pub mod minecraft;
pub mod music;
pub mod sauce;
pub mod theme;
pub mod xkcd;
pub mod inspirobot;
/// Adds an ephemeral message to the database
pub async fn add_ephemeral_handle_to_database(

@ -1,262 +0,0 @@
use crate::commands::music::is_dj;
use crate::providers::music::player::MusicPlayer;
use crate::utils::error::BotResult;
use serenity::builder::{CreateEmbed, CreateMessage};
use serenity::client::Context;
use serenity::model::channel::Reaction;
use serenity::model::id::ChannelId;
use serenity_rich_interaction::core::EXTRA_LONG_TIMEOUT;
use serenity_rich_interaction::menu::{display_page, Menu, MenuBuilder, Page};
use serenity_rich_interaction::Result as SerenityUtilsResult;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use tokio::sync::Mutex;
use typemap_rev::TypeMapKey;
static DELETE_BUTTON: &str = "🗑️";
static NEXT_BAND_BUTTON: &str = "➡️";
static PREVIOUS_BAND_BUTTON: &str = "⬅️";
static ADD_BUTTON: &str = "";
static SUB_BUTTON: &str = "";
struct SelectedBand;
impl TypeMapKey for SelectedBand {
type Value = Arc<AtomicU8>;
}
struct Player;
impl TypeMapKey for Player {
type Value = Arc<Mutex<MusicPlayer>>;
}
/// Creates a new equalizer message
pub async fn create_equalizer_message(
ctx: &Context,
channel_id: ChannelId,
player: Arc<Mutex<MusicPlayer>>,
) -> BotResult<()> {
let selected_band = Arc::new(AtomicU8::new(0));
let selected_band_clone = Arc::clone(&selected_band);
let player_clone = Arc::clone(&player);
MenuBuilder::default()
.add_page(Page::new_builder(move || {
let player = Arc::clone(&player_clone);
let selected_band = Arc::clone(&selected_band_clone);
Box::pin(async move {
let mut page = CreateMessage::default();
let mut embed = CreateEmbed::default();
create_equalizer_embed(selected_band.load(Ordering::Relaxed), &mut embed, &player)
.await;
page.embed(|e| {
e.0.clone_from(&embed.0);
e
});
Ok(page)
})
}))
.add_control(-1, DELETE_BUTTON, |c, m, r| Box::pin(delete_menu(c, m, r)))
.add_help(DELETE_BUTTON, "Deletes this message.")
.add_control(0, PREVIOUS_BAND_BUTTON, |c, m, r| {
Box::pin(previous_band(c, m, r))
})
.add_help(PREVIOUS_BAND_BUTTON, "Selects the previous band.")
.add_control(1, NEXT_BAND_BUTTON, |c, m, r| Box::pin(next_band(c, m, r)))
.add_help(NEXT_BAND_BUTTON, "Selects the next band.")
.add_control(3, ADD_BUTTON, |c, m, r| Box::pin(add_to_band(c, m, r)))
.add_help(ADD_BUTTON, "Adds to the selected band.")
.add_control(2, SUB_BUTTON, |c, m, r| {
Box::pin(subtract_from_band(c, m, r))
})
.add_help(SUB_BUTTON, "Subtracts from the selected band")
.show_help()
.add_data::<SelectedBand>(selected_band)
.add_data::<Player>(player)
.timeout(EXTRA_LONG_TIMEOUT)
.build(ctx, channel_id)
.await?;
Ok(())
}
/// Creates a new equalizer embed
async fn create_equalizer_embed<'a>(
selected_band: u8,
embed: &'a mut CreateEmbed,
player: &Arc<Mutex<MusicPlayer>>,
) -> &'a mut CreateEmbed {
let mut description = String::new();
let bands = {
let player = player.lock().await;
player.get_equalizer().clone()
};
for i in 0..bands.len() {
if i as u8 == selected_band {
description += "⤋"
} else {
description += " ";
}
}
description += "\n";
for i in (0..11).rev() {
let eq_value = (i as f64) / 20.0 - 0.25;
for band in &bands {
if (eq_value > 0. && band >= &eq_value) || (eq_value < 0. && band <= &eq_value) {
description += "█";
} else if eq_value == 0. {
description += format!("-").as_str();
} else {
description += " ";
}
}
description += "\n";
}
for i in 0..bands.len() {
if i as u8 == selected_band {
description += "⤊"
} else {
description += " ";
}
}
embed
.title("Equalizer")
.description(format!("```\n{}\n```", description));
embed
}
/// Selects the previous band
async fn next_band(
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
let selected_band = menu.data.get::<SelectedBand>().unwrap();
if selected_band.load(Ordering::SeqCst) >= 14 {
selected_band.store(0, Ordering::SeqCst);
} else {
selected_band.fetch_add(1, Ordering::SeqCst);
}
display_page(ctx, menu).await?;
Ok(())
}
/// Selects the previous band
async fn previous_band(
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
let selected_band = menu.data.get::<SelectedBand>().unwrap();
if selected_band.load(Ordering::SeqCst) <= 0 {
selected_band.store(14, Ordering::SeqCst);
} else {
selected_band.fetch_sub(1, Ordering::SeqCst);
}
display_page(ctx, menu).await?;
Ok(())
}
/// Adds to the selected band
async fn add_to_band(
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
{
let selected_band = menu
.data
.get::<SelectedBand>()
.unwrap()
.load(Ordering::Relaxed);
let player = menu.data.get::<Player>().unwrap();
let mut player = player.lock().await;
let equalizer = player.get_equalizer();
let current_value = equalizer[selected_band as usize];
if current_value < 0.25 {
player.equalize(selected_band, current_value + 0.05).await?;
}
}
display_page(ctx, menu).await?;
Ok(())
}
/// Substracts from the selected band
async fn subtract_from_band(
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
{
let selected_band = menu
.data
.get::<SelectedBand>()
.unwrap()
.load(Ordering::Relaxed);
let player = menu.data.get::<Player>().unwrap();
let mut player = player.lock().await;
let equalizer = player.get_equalizer();
let current_value = equalizer[selected_band as usize];
if current_value > -0.25 {
player.equalize(selected_band, current_value - 0.05).await?;
}
}
display_page(ctx, menu).await?;
Ok(())
}
/// Deletes the menu
async fn delete_menu(
ctx: &Context,
menu: &mut Menu<'_>,
reaction: Reaction,
) -> SerenityUtilsResult<()> {
let guild_id = reaction.guild_id.unwrap();
let user = reaction.user(&ctx).await?;
if !is_dj(ctx, guild_id, &user).await? {
return Ok(());
}
let handle = menu.message.read().await;
ctx.http
.delete_message(handle.channel_id, handle.message_id)
.await?;
Ok(())
}

@ -1,4 +1,3 @@
pub mod equalizer;
pub mod no_voicechannel;
pub mod now_playing;
pub mod queue;

@ -1,8 +1,8 @@
use crate::utils::error::BotResult;
use serenity::http::Http;
use serenity::model::prelude::ChannelId;
use serenity_rich_interaction::core::SHORT_TIMEOUT;
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::SHORT_TIMEOUT;
use serenity_additions::ephemeral_message::EphemeralMessage;
use std::sync::Arc;
/// Creates a not in a voicechannel message

@ -14,9 +14,9 @@ use crate::utils::error::*;
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::channel::Reaction;
use serenity_rich_interaction::core::MessageHandle;
use serenity_rich_interaction::menu::{Menu, MenuBuilder, Page};
use serenity_rich_interaction::Result as SerenityUtilsResult;
use serenity_additions::core::MessageHandle;
use serenity_additions::menu::{Menu, MenuBuilder, Page};
use serenity_additions::Result as SerenityUtilsResult;
use std::env;
use std::time::Duration;
use tokio::sync::{Mutex, RwLock};

@ -3,7 +3,7 @@ use crate::utils::error::BotResult;
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::ChannelId;
use serenity_rich_interaction::menu::{MenuBuilder, Page};
use serenity_additions::menu::{MenuBuilder, Page};
use std::time::Duration;
/// Creates a new queue menu

@ -1,6 +1,5 @@
use std::cmp::Ordering;
use sauce_api::{SauceItem, SauceResult};
use serenity::builder::CreateMessage;
use serenity::{model::channel::Message, prelude::*};
@ -8,7 +7,7 @@ use bot_coreutils::url::get_domain_for_url;
use crate::utils::error::BotResult;
use rand::prelude::SliceRandom;
use serenity_rich_interaction::menu::{MenuBuilder, Page};
use serenity_additions::menu::{MenuBuilder, Page};
use std::time::Duration;
static MAX_RESULTS: usize = 6;
@ -24,7 +23,7 @@ static SAUCE_MESSAGES: &[&str] = &[
pub async fn show_sauce_menu(
ctx: &Context,
msg: &Message,
sources: Vec<SauceResult>,
sources: Vec<sauce_api::source::Output>,
) -> BotResult<()> {
let pages: Vec<Page> = sources.into_iter().map(create_sauce_page).collect();
@ -47,7 +46,7 @@ pub async fn show_sauce_menu(
}
/// Creates a single sauce page
fn create_sauce_page<'a>(mut result: SauceResult) -> Page<'a> {
fn create_sauce_page<'a>(mut result: sauce_api::source::Output) -> Page<'a> {
let mut message = CreateMessage::default();
let mut description_lines = Vec::new();
let original = result.original_url;
@ -65,7 +64,7 @@ fn create_sauce_page<'a>(mut result: SauceResult) -> Page<'a> {
});
// display with descending similarity
result.items.reverse();
let items: Vec<(usize, SauceItem)> = result
let items: Vec<(usize, sauce_api::source::Item)> = result
.items
.into_iter()
.filter(|i| i.similarity >= MIN_SIMILARITY)

@ -3,8 +3,8 @@ use animethemes_rs::models::{Anime, ThemeEntry, ThemeType};
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::{ChannelId, UserId};
use serenity_rich_interaction::core::EXTRA_LONG_TIMEOUT;
use serenity_rich_interaction::menu::{MenuBuilder, Page};
use serenity_additions::core::EXTRA_LONG_TIMEOUT;
use serenity_additions::menu::{MenuBuilder, Page};
/// Creates a new Anime Theme Menu
pub async fn create_theme_menu(
@ -55,6 +55,11 @@ fn create_theme_pages(anime_entries: Vec<Anime>, nsfw: bool) -> Vec<Page<'static
}
}
}
if pages.is_empty() {
let mut message = CreateMessage::default();
message.embed(|e| e.description("No themes found!"));
pages.push(Page::Static(message));
}
pages
}

@ -2,8 +2,8 @@ use crate::utils::error::BotResult;
use serenity::builder::CreateMessage;
use serenity::client::Context;
use serenity::model::id::{ChannelId, UserId};
use serenity_rich_interaction::core::LONG_TIMEOUT;
use serenity_rich_interaction::menu::{MenuBuilder, Page};
use serenity_additions::core::LONG_TIMEOUT;
use serenity_additions::menu::{MenuBuilder, Page};
use xkcd_search::Comic;
/// Creates a new xkcd menu

@ -1,4 +1,4 @@
pub(crate) mod minecraft;
pub(crate) mod music;
pub(crate) mod qalc;
pub(crate) mod settings;
pub(crate) mod minecraft;

@ -1,53 +0,0 @@
use crate::utils::context_data::MusicPlayers;
use lavalink_rs::gateway::LavalinkEventHandler;
use lavalink_rs::model::{PlayerUpdate, Stats, 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) {
tracing::info!("Track started!\nGuild: {}", event.guild_id);
}
async fn track_finish(&self, _: LavalinkClient, event: TrackFinish) {
tracing::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.0).cloned()
};
if let Some(player) = player {
let mut player = player.lock().await;
if let Err(e) = player.play_next().await {
tracing::error!("Failed to play next song: {:?}", e);
}
if let Err(e) = player.update_now_playing().await {
tracing::error!("Failed to update now playing embed: {:?}", e);
}
}
}
async fn player_update(&self, _: LavalinkClient, event: PlayerUpdate) {
tracing::debug!("Received player update event: {:?}", event);
}
async fn stats(&self, _: LavalinkClient, event: Stats) {
tracing::debug!("Received stats event: {:?}", event);
}
}
pub struct Lavalink;
impl TypeMapKey for Lavalink {
type Value = Arc<LavalinkClient>;
}

@ -8,9 +8,9 @@ use responses::VideoInformation;
use youtube_dl::search_video_information;
pub mod inspirobot;
pub mod lavalink;
pub mod lyrics;
pub mod player;
pub mod player_events;
pub mod queue;
pub mod responses;
pub mod spotify;

@ -1,26 +1,27 @@
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::{BotError, BotResult};
use lavalink_rs::LavalinkClient;
use serenity::prelude::TypeMap;
use serenity::{
client::Context,
http::Http,
model::id::{ChannelId, GuildId},
};
use serenity_rich_interaction::core::{MessageHandle, SHORT_TIMEOUT};
use serenity_rich_interaction::ephemeral_message::EphemeralMessage;
use serenity_additions::core::{MessageHandle, SHORT_TIMEOUT};
use serenity_additions::ephemeral_message::EphemeralMessage;
use songbird::tracks::TrackHandle;
use songbird::Songbird;
use std::mem;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::{Mutex, RwLock};
use super::player_events::register_player_events;
pub struct MusicPlayer {
client: Arc<LavalinkClient>,
manager: Arc<Songbird>,
http: Arc<Http>,
queue: MusicQueue,
guild_id: GuildId,
@ -28,19 +29,19 @@ pub struct MusicPlayer {
msg_channel: ChannelId,
leave_flag: bool,
paused: bool,
equalizer: [f64; 15],
current_track: Option<TrackHandle>,
}
impl MusicPlayer {
/// Creates a new music player
pub fn new(
client: Arc<LavalinkClient>,
manager: Arc<Songbird>,
http: Arc<Http>,
guild_id: GuildId,
msg_channel: ChannelId,
) -> Self {
Self {
client,
manager,
http,
guild_id,
queue: MusicQueue::new(),
@ -48,7 +49,7 @@ impl MusicPlayer {
now_playing_msg: None,
leave_flag: false,
paused: false,
equalizer: [0f64; 15],
current_track: None,
}
}
@ -60,20 +61,12 @@ impl MusicPlayer {
msg_channel_id: ChannelId,
) -> BotResult<Arc<Mutex<MusicPlayer>>> {
let manager = songbird::get(ctx).await.unwrap();
let (handler, connection) = manager.join_gateway(guild_id, voice_channel_id).await;
let connection = connection?;
{
let mut handler = handler.lock().await;
handler.deafen(true).await?;
}
let (handler, _) = manager.join(guild_id, voice_channel_id).await;
let player = {
let mut data = ctx.data.write().await;
let client = data.get::<Lavalink>().unwrap();
client.create_session_with_songbird(&connection).await?;
let player = MusicPlayer::new(
Arc::clone(client),
Arc::clone(&manager),
Arc::clone(&ctx.http),
guild_id,
msg_channel_id,
@ -84,6 +77,12 @@ impl MusicPlayer {
player
};
{
let mut handler = handler.lock().await;
handler.deafen(true).await?;
register_player_events(player.clone(), &mut handler);
}
wait_for_disconnect(
Arc::clone(&ctx.data),
Arc::clone(&player),
@ -101,7 +100,9 @@ impl MusicPlayer {
/// Skips to the next song
pub async fn skip(&mut self) -> BotResult<()> {
self.client.stop(self.guild_id.0).await?;
if let Some(track) = self.current_track.take() {
track.stop()?;
}
Ok(())
}
@ -109,7 +110,9 @@ impl MusicPlayer {
/// Stops playback and leaves the channel
pub async fn stop(&mut self) -> BotResult<()> {
self.queue.clear();
self.client.stop(self.guild_id.0).await?;
if let Some(track) = self.current_track.take() {
track.stop()?;
}
Ok(())
}
@ -128,7 +131,9 @@ impl MusicPlayer {
pub async fn play_next(&mut self) -> BotResult<()> {
while !self.try_play_next().await? {}
if self.paused {
self.client.pause(self.guild_id).await?;
if let Some(track) = self.current_track.as_ref() {
track.pause()?;
}
}
Ok(())
@ -154,8 +159,8 @@ impl MusicPlayer {
tracing::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,
let source = match songbird::ytdl(url).await {
Ok(s) => s,
Err(e) => {
tracing::error!("Failed to search for song: {}", e);
self.send_error_message(format!(
@ -168,12 +173,16 @@ impl MusicPlayer {
return Ok(false);
}
};
if query_information.tracks.len() == 0 {
return Ok(false);
let handler_lock = self
.manager
.get(self.guild_id.0)
.ok_or(BotError::MissingSongbirdClient)?;
{
let mut handler = handler_lock.lock().await;
let track_handle = handler.play_source(source);
self.current_track = Some(track_handle);
}
let track = query_information.tracks[0].clone();
self.client.play(self.guild_id.0, track).start().await?;
self.queue.set_current(next);
Ok(true)
@ -208,7 +217,13 @@ impl MusicPlayer {
/// 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?;
if let Some(track) = self.current_track.as_ref() {
if self.paused {
track.pause()?;
} else {
track.play()?;
}
}
Ok(())
}
@ -242,37 +257,6 @@ impl MusicPlayer {
Ok(())
}
/// Returns the equalizer
pub fn get_equalizer(&self) -> &[f64; 15] {
&self.equalizer
}
/// Equalizes a specified band
pub async fn equalize(&mut self, band: u8, value: f64) -> BotResult<()> {
if band > 15 {
return Err(BotError::from("Invalid Equalizer band"));
}
if value < -0.25 || value > 0.25 {
return Err(BotError::from("Invalid Equalizer value"));
}
self.equalizer[band as usize] = value;
self.client
.equalize_all(self.guild_id, self.equalizer)
.await?;
Ok(())
}
/// Equalizes all bands at the same time
pub async fn equalize_all(&mut self, bands: [f64; 15]) -> BotResult<()> {
self.equalizer = bands;
self.client
.equalize_all(self.guild_id, self.equalizer)
.await?;
Ok(())
}
}
/// Stats a tokio coroutine to check for player disconnect conditions
@ -295,8 +279,12 @@ fn wait_for_disconnect(
tracing::debug!("Waiting to leave");
if leave_in <= 0 {
tracing::debug!("Leaving voice channel");
tracing::info!("Leaving voice channel");
if let Some(track) = player_lock.current_track.take() {
player_lock.queue().clear();
let _ = track.stop();
}
if let Some(handler) = manager.get(guild_id) {
let mut handler_lock = handler.lock().await;
let _ = handler_lock.leave().await;

@ -0,0 +1,30 @@
use serenity::async_trait;
use std::sync::Arc;
use serenity::prelude::Mutex;
use songbird::{Call, Event, EventContext, EventHandler, TrackEvent};
use super::player::MusicPlayer;
pub fn register_player_events(player: Arc<Mutex<MusicPlayer>>, handler: &mut Call) {
handler.add_global_event(Event::Track(TrackEvent::End), TrackEndHandler { player });
}
struct TrackEndHandler {
player: Arc<Mutex<MusicPlayer>>,
}
#[async_trait]
impl EventHandler for TrackEndHandler {
#[tracing::instrument(level = "debug", skip_all)]
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
let mut player = self.player.lock().await;
if let Err(e) = player.play_next().await {
tracing::error!("Failed to play next song: {:?}", e);
}
if let Err(e) = player.update_now_playing().await {
tracing::error!("Failed to update now playing embed: {:?}", e);
}
None
}
}

@ -12,7 +12,6 @@ use bot_database::models::YoutubeSong;
pub struct MusicQueue {
inner: VecDeque<Song>,
current: Option<Song>,
pub leave_flag: bool,
}
impl MusicQueue {
@ -20,7 +19,6 @@ impl MusicQueue {
Self {
inner: VecDeque::new(),
current: None,
leave_flag: false,
}
}

@ -70,7 +70,7 @@ async fn youtube_dl(args: &[&str]) -> BotResult<String> {
}
THREAD_LOCK.fetch_add(1, Ordering::Relaxed);
let output = run_command_async("youtube-dl", args).await.map_err(|e| {
let output = run_command_async("yt-dlp", args).await.map_err(|e| {
THREAD_LOCK.fetch_sub(1, Ordering::Relaxed);
e
})?;

@ -3,7 +3,8 @@ use std::env;
use std::sync::Arc;
use bot_database::Database;
use sauce_api::prelude::SauceNao;
use sauce_api::source::saucenao::SauceNao;
use sauce_api::source::Source;
use serenity::client::Context;
use serenity::prelude::TypeMapKey;
use tokio::sync::Mutex;
@ -16,15 +17,17 @@ pub struct Store;
pub struct StoreData {
pub minecraft_data_api: minecraft_data_rs::api::Api,
pub spotify_api: SpotifyApi,
pub sauce_nao: SauceNao<'static>,
pub sauce_nao: SauceNao,
}
impl StoreData {
pub fn new() -> StoreData {
let mut sauce_nao = SauceNao::new();
sauce_nao.set_api_key(
pub async fn create() -> StoreData {
let sauce_nao = SauceNao::create(
env::var("SAUCENAO_API_KEY").expect("No SAUCENAO_API_KEY key in environment."),
);
)
.await
.unwrap();
Self {
minecraft_data_api: minecraft_data_rs::api::Api::latest().unwrap(),
spotify_api: SpotifyApi::new(),

@ -1,5 +1,4 @@
use lavalink_rs::error::LavalinkError;
use serenity_rich_interaction::Error as SerenityUtilsError;
use serenity_additions::Error as SerenityUtilsError;
use thiserror::Error;
pub type BotResult<T> = Result<T, BotError>;
@ -34,7 +33,7 @@ pub enum BotError {
CliInject,
#[error("Serenity Utils Error: {0}")]
SerenityUtils(#[from] serenity_rich_interaction::Error),
SerenityUtils(#[from] serenity_additions::Error),
#[error("Track Error: {0}")]
TrackError(#[from] songbird::error::TrackError),
@ -45,8 +44,8 @@ pub enum BotError {
#[error("YouTube Error: {0}")]
YoutubeError(#[from] youtube_metadata::error::Error),
#[error("Lavalink Error: {0}")]
LavalinkError(#[from] LavalinkError),
#[error("No songbird client for current guild")]
MissingSongbirdClient,
#[error("{0}")]
Msg(String),

@ -1,17 +1,15 @@
use chrono::{DateTime, FixedOffset, Local};
use std::env;
use std::ops::Add;
use std::sync::Arc;
use std::time::SystemTime;
use lavalink_rs::LavalinkClient;
use serenity::client::Context;
use serenity::model::application::CurrentApplicationInfo;
use serenity::model::channel::Message;
use serenity::prelude::{RwLock, TypeMap};
use tokio::time::Instant;
use crate::providers::music::lavalink::{Lavalink, LavalinkHandler};
use crate::utils::context_data::get_database_from_context;
use crate::utils::error::BotResult;
@ -92,28 +90,3 @@ pub async fn delete_messages_from_database(ctx: &Context) -> BotResult<()> {
Ok(())
}
pub async fn initialize_lavalink(
data: Arc<RwLock<TypeMap>>,
current_application: CurrentApplicationInfo,
) -> BotResult<()> {
let lava_client = LavalinkClient::builder(current_application.id.0)
.set_host(env::var("LAVALINK_HOST").unwrap_or("172.0.0.1".to_string()))
.set_password(env::var("LAVALINK_PASSWORD").expect("Missing lavalink password"))
.set_port(
env::var("LAVALINK_PORT")
.ok()
.and_then(|s| s.parse().ok())
.expect("Missing lavalink port"),
)
.build(LavalinkHandler {
data: Arc::clone(&data),
})
.await?;
{
let mut data = data.write().await;
data.insert::<Lavalink>(Arc::new(lava_client));
}
Ok(())
}

Loading…
Cancel
Save