1 Commits

Author SHA1 Message Date
a2ac7050d7 Add additional indexes 2023-10-21 17:58:31 +01:00
9 changed files with 61 additions and 270 deletions

118
Cargo.lock generated
View File

@ -139,55 +139,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf"
dependencies = [
"async-trait",
"axum-core",
"bitflags 1.3.2",
"bytes",
"futures-util",
"http",
"http-body",
"hyper",
"itoa",
"matchit",
"memchr",
"mime",
"percent-encoding",
"pin-project-lite",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c"
dependencies = [
"async-trait",
"bytes",
"futures-util",
"http",
"http-body",
"mime",
"rustversion",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.68" version = "0.3.68"
@ -1129,12 +1080,6 @@ dependencies = [
"regex-automata 0.1.10", "regex-automata 0.1.10",
] ]
[[package]]
name = "matchit"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -1604,27 +1549,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "prometheus"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "449811d15fbdf5ceb5c1144416066429cf82316e2ec8ce0c1f6f8a02e7bbcf8c"
dependencies = [
"cfg-if",
"fnv",
"lazy_static",
"memchr",
"parking_lot 0.12.1",
"protobuf",
"thiserror",
]
[[package]]
name = "protobuf"
version = "2.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.29" version = "1.0.29"
@ -2022,16 +1946,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_path_to_error"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [
"itoa",
"serde",
]
[[package]] [[package]]
name = "serde_repr" name = "serde_repr"
version = "0.1.14" version = "0.1.14"
@ -2215,16 +2129,14 @@ dependencies = [
[[package]] [[package]]
name = "soundfx-rs" name = "soundfx-rs"
version = "1.5.12" version = "1.5.11"
dependencies = [ dependencies = [
"axum",
"dashmap", "dashmap",
"dotenv", "dotenv",
"env_logger", "env_logger",
"lazy_static", "lazy_static",
"log", "log",
"poise", "poise",
"prometheus",
"regex", "regex",
"reqwest", "reqwest",
"serde", "serde",
@ -2425,12 +2337,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.6.0" version = "3.6.0"
@ -2623,28 +2529,6 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"

View File

@ -2,7 +2,7 @@
name = "soundfx-rs" name = "soundfx-rs"
description = "Discord bot for custom sound effects and soundboards" description = "Discord bot for custom sound effects and soundboards"
license = "AGPL-3.0-only" license = "AGPL-3.0-only"
version = "1.5.12" version = "1.5.11"
authors = ["jellywx <judesouthworth@pm.me>"] authors = ["jellywx <judesouthworth@pm.me>"]
edition = "2018" edition = "2018"
@ -20,11 +20,6 @@ serde_json = "1.0"
dashmap = "5.3" dashmap = "5.3"
serde = "1.0" serde = "1.0"
dotenv = "0.15.0" dotenv = "0.15.0"
prometheus = { version = "0.13.3", optional = true }
axum = { version = "0.6.20", optional = true }
[features]
metrics = ["dep:prometheus", "dep:axum"]
[patch."https://github.com/serenity-rs/serenity"] [patch."https://github.com/serenity-rs/serenity"]
serenity = { version = "0.11.6" } serenity = { version = "0.11.6" }

View File

@ -0,0 +1,6 @@
-- Add migration script here
ALTER TABLE `sounds` ADD UNIQUE INDEX `uploader_id_name` (`uploader_id`, `name`);
ALTER TABLE `sounds` ADD INDEX `name` (`name`);
ALTER TABLE `sounds` ADD INDEX `public` (`public`);
ALTER TABLE `sounds` ADD INDEX `uploader_id` (`uploader_id`);
ALTER TABLE `sounds` ADD INDEX `server_id` (`server_id`);

View File

@ -1,8 +1,6 @@
use poise::serenity_prelude::{Attachment, GuildId, RoleId}; use poise::serenity_prelude::{Attachment, GuildId, RoleId};
use tokio::fs::File; use tokio::fs::File;
#[cfg(feature = "metrics")]
use crate::metrics::{DELETE_COUNTER, UPLOAD_COUNTER};
use crate::{ use crate::{
cmds::autocomplete_sound, cmds::autocomplete_sound,
consts::{MAX_SOUNDS, PATREON_GUILD, PATREON_ROLE}, consts::{MAX_SOUNDS, PATREON_GUILD, PATREON_ROLE},
@ -23,9 +21,6 @@ pub async fn upload_new_sound(
#[description = "Name to upload sound to"] name: String, #[description = "Name to upload sound to"] name: String,
#[description = "Sound file (max. 2MB)"] file: Attachment, #[description = "Sound file (max. 2MB)"] file: Attachment,
) -> Result<(), Error> { ) -> Result<(), Error> {
#[cfg(feature = "metrics")]
UPLOAD_COUNTER.inc();
ctx.defer().await?; ctx.defer().await?;
fn is_numeric(s: &String) -> bool { fn is_numeric(s: &String) -> bool {
@ -115,9 +110,6 @@ pub async fn delete_sound(
#[autocomplete = "autocomplete_sound"] #[autocomplete = "autocomplete_sound"]
name: String, name: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
#[cfg(feature = "metrics")]
DELETE_COUNTER.inc();
let pool = ctx.data().database.clone(); let pool = ctx.data().database.clone();
let uid = ctx.author().id.0; let uid = ctx.author().id.0;

View File

@ -1,16 +1,12 @@
use std::time::{SystemTime, UNIX_EPOCH};
use poise::serenity_prelude::{ use poise::serenity_prelude::{
builder::CreateActionRow, model::application::component::ButtonStyle, GuildChannel, builder::CreateActionRow, model::application::component::ButtonStyle, GuildChannel,
ReactionType, ReactionType,
}; };
#[cfg(feature = "metrics")]
use crate::metrics::PLAY_COUNTER;
use crate::{ use crate::{
cmds::autocomplete_sound, cmds::autocomplete_sound,
models::{guild_data::CtxGuildData, sound::SoundCtx}, models::{guild_data::CtxGuildData, sound::SoundCtx},
utils::{join_channel, play_audio, play_from_query, queue_audio}, utils::{join_channel, play_from_query, queue_audio},
Context, Error, Context, Error,
}; };
@ -25,9 +21,6 @@ pub async fn play(
#[channel_types("Voice")] #[channel_types("Voice")]
channel: Option<GuildChannel>, channel: Option<GuildChannel>,
) -> Result<(), Error> { ) -> Result<(), Error> {
#[cfg(feature = "metrics")]
PLAY_COUNTER.inc();
ctx.defer().await?; ctx.defer().await?;
let guild = ctx.guild().unwrap(); let guild = ctx.guild().unwrap();
@ -49,79 +42,6 @@ pub async fn play(
Ok(()) Ok(())
} }
/// Play a random sound from this server
#[poise::command(
slash_command,
rename = "random",
default_member_permissions = "SPEAK",
guild_only = true
)]
pub async fn play_random(
ctx: Context<'_>,
#[description = "Channel to play in (default: your current voice channel)"]
#[channel_types("Voice")]
channel: Option<GuildChannel>,
) -> Result<(), Error> {
ctx.defer().await?;
let guild = ctx.guild().unwrap();
let channel_to_join = channel.map(|c| c.id).or_else(|| {
guild
.voice_states
.get(&ctx.author().id)
.and_then(|voice_state| voice_state.channel_id)
});
match channel_to_join {
Some(channel) => {
let (call_handler, _) =
join_channel(ctx.serenity_context(), guild.clone(), channel).await;
let sounds = ctx.data().guild_sounds(guild.id, None).await?;
if sounds.len() == 0 {
ctx.say("No sounds in this server!").await?;
return Ok(());
}
let ts = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
println!("{}", ts.subsec_micros());
// This is far cheaper and easier than using an RNG. No reason to use a full RNG here
// anyway.
match sounds.get(ts.subsec_micros() as usize % sounds.len()) {
Some(sound) => {
let guild_data = ctx.data().guild_data(guild.id).await.unwrap();
let mut lock = call_handler.lock().await;
play_audio(
sound,
guild_data.read().await.volume,
&mut lock,
&ctx.data().database,
false,
)
.await
.unwrap();
ctx.say(format!("Playing {} (ID {})", sound.name, sound.id))
.await?;
}
None => {
ctx.say("No sounds in this server!").await?;
}
}
}
None => {
ctx.say("You are not in a voice chat!").await?;
}
}
Ok(())
}
/// Play up to 25 sounds on queue /// Play up to 25 sounds on queue
#[poise::command( #[poise::command(
slash_command, slash_command,

View File

@ -1,8 +1,11 @@
use std::{collections::HashMap, env};
use poise::serenity_prelude::{ use poise::serenity_prelude::{
model::{ model::{
application::interaction::{Interaction, InteractionResponseType}, application::interaction::{Interaction, InteractionResponseType},
channel::Channel, channel::Channel,
}, },
utils::shard_id,
ActionRowComponent, Activity, Context, CreateActionRow, CreateComponents, ActionRowComponent, Activity, Context, CreateActionRow, CreateComponents,
}; };
@ -22,6 +25,46 @@ pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> R
poise::Event::Ready { .. } => { poise::Event::Ready { .. } => {
ctx.set_activity(Activity::watching("for /play")).await; ctx.set_activity(Activity::watching("for /play")).await;
} }
poise::Event::GuildCreate { guild, is_new, .. } => {
if *is_new {
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
let shard_count = ctx.cache.shard_count();
let current_shard_id = shard_id(guild.id.as_u64().to_owned(), shard_count);
let guild_count = ctx
.cache
.guilds()
.iter()
.filter(|g| {
shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id
})
.count() as u64;
let mut hm = HashMap::new();
hm.insert("server_count", guild_count);
hm.insert("shard_id", current_shard_id);
hm.insert("shard_count", shard_count);
let response = data
.http
.post(
format!(
"https://top.gg/api/bots/{}/stats",
ctx.cache.current_user_id().as_u64()
)
.as_str(),
)
.header("Authorization", token)
.json(&hm)
.send()
.await;
if let Err(res) = response {
println!("DiscordBots Response: {:?}", res);
}
}
}
}
poise::Event::VoiceStateUpdate { old, new, .. } => { poise::Event::VoiceStateUpdate { old, new, .. } => {
if let Some(past_state) = old { if let Some(past_state) = old {
if let (Some(guild_id), None) = (past_state.guild_id, new.channel_id) { if let (Some(guild_id), None) = (past_state.guild_id, new.channel_id) {
@ -64,7 +107,8 @@ pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> R
" "
SELECT name, id, public, server_id, uploader_id SELECT name, id, public, server_id, uploader_id
FROM sounds FROM sounds
WHERE id = ?", WHERE id = ?
",
join_id join_id
) )
.fetch_one(&data.database) .fetch_one(&data.database)

View File

@ -5,8 +5,6 @@ mod cmds;
mod consts; mod consts;
mod error; mod error;
mod event_handlers; mod event_handlers;
#[cfg(feature = "metrics")]
mod metrics;
mod models; mod models;
mod utils; mod utils;
@ -30,6 +28,7 @@ type Database = MySql;
pub struct Data { pub struct Data {
database: Pool<Database>, database: Pool<Database>,
http: reqwest::Client,
guild_data_cache: DashMap<GuildId, Arc<RwLock<GuildData>>>, guild_data_cache: DashMap<GuildId, Arc<RwLock<GuildData>>>,
join_sound_cache: DashMap<UserId, DashMap<Option<GuildId>, Option<u32>>>, join_sound_cache: DashMap<UserId, DashMap<Option<GuildId>, Option<u32>>>,
} }
@ -86,7 +85,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
cmds::manage::download_file(), cmds::manage::download_file(),
cmds::manage::delete_sound(), cmds::manage::delete_sound(),
cmds::play::play(), cmds::play::play(),
cmds::play::play_random(),
cmds::play::queue_play(), cmds::play::queue_play(),
cmds::play::loop_play(), cmds::play::loop_play(),
cmds::play::soundboard(), cmds::play::soundboard(),
@ -105,6 +103,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
], ],
..cmds::favorite::favorites() ..cmds::favorite::favorites()
}, },
cmds::search::show_random_sounds(),
cmds::search::search_sounds(), cmds::search::search_sounds(),
cmds::stop::stop_playing(), cmds::stop::stop_playing(),
cmds::stop::disconnect(), cmds::stop::disconnect(),
@ -143,12 +142,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
sqlx::migrate!().run(&database).await?; sqlx::migrate!().run(&database).await?;
#[cfg(feature = "metrics")]
{
metrics::init_metrics();
tokio::spawn(async { metrics::serve().await });
}
poise::Framework::builder() poise::Framework::builder()
.token(discord_token) .token(discord_token)
.setup(move |ctx, _bot, framework| { .setup(move |ctx, _bot, framework| {
@ -158,6 +151,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.unwrap(); .unwrap();
Ok(Data { Ok(Data {
http: reqwest::Client::new(),
database, database,
guild_data_cache: Default::default(), guild_data_cache: Default::default(),
join_sound_cache: Default::default(), join_sound_cache: Default::default(),

View File

@ -1,44 +0,0 @@
use std::net::SocketAddr;
use axum::{routing::get, Router};
use lazy_static;
use log::warn;
use prometheus::{register_int_counter, IntCounter, Registry};
lazy_static! {
static ref REGISTRY: Registry = Registry::new();
pub static ref PLAY_COUNTER: IntCounter =
register_int_counter!("play_cmd", "Number of calls to /play").unwrap();
pub static ref UPLOAD_COUNTER: IntCounter =
register_int_counter!("upload_cmd", "Number of calls to /upload").unwrap();
pub static ref DELETE_COUNTER: IntCounter =
register_int_counter!("delete_cmd", "Number of calls to /delete").unwrap();
}
pub fn init_metrics() {
REGISTRY.register(Box::new(PLAY_COUNTER.clone())).unwrap();
}
pub async fn serve() {
let app = Router::new().route("/", get(metrics));
let addr = SocketAddr::from(([127, 0, 0, 1], 31755));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn metrics() -> String {
let encoder = prometheus::TextEncoder::new();
let res_custom = encoder.encode_to_string(&REGISTRY.gather());
match res_custom {
Ok(s) => s,
Err(e) => {
warn!("Error encoding metrics: {:?}", e);
String::new()
}
}
}

View File

@ -106,7 +106,7 @@ pub async fn play_from_query(
user_id: UserId, user_id: UserId,
channel: Option<ChannelId>, channel: Option<ChannelId>,
query: &str, query: &str,
r#loop: bool, loop_: bool,
) -> String { ) -> String {
let guild_id = guild.id; let guild_id = guild.id;
@ -141,7 +141,7 @@ pub async fn play_from_query(
guild_data.read().await.volume, guild_data.read().await.volume,
&mut lock, &mut lock,
&data.database, &data.database,
r#loop, loop_,
) )
.await .await
.unwrap(); .unwrap();