Join sounds per-server

This commit is contained in:
jude
2022-07-31 14:56:38 +01:00
parent 31b6f7a0ab
commit f5acab7440
9 changed files with 300 additions and 127 deletions

View File

@ -1,4 +1,7 @@
use poise::serenity_prelude::{GuildId, User};
use crate::{
cmds::autocomplete_sound,
models::{guild_data::CtxGuildData, join_sound::JoinSoundCtx, sound::SoundCtx},
Context, Error,
};
@ -31,18 +34,44 @@ pub async fn change_volume(
Ok(())
}
/// Manage greet sounds on this server
/// Manage greet sounds
#[poise::command(slash_command, rename = "greet", guild_only = true)]
pub async fn greet_sound(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Set a join sound
/// Manage greet sounds in this server
#[poise::command(slash_command, rename = "server")]
pub async fn guild_greet_sound(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Set a user's guild-specific join sound
#[poise::command(slash_command, rename = "set")]
pub async fn set_greet_sound(
pub async fn set_guild_greet_sound(
ctx: Context<'_>,
#[description = "Name or ID of sound to set as your join sound"] name: String,
#[description = "Name or ID of sound to set as join sound"]
#[autocomplete = "autocomplete_sound"]
name: String,
#[description = "User to set join sound for"] user: User,
) -> Result<(), Error> {
if user.id != ctx.author().id {
let guild = ctx.guild().unwrap();
let permissions = guild
.member_permissions(&ctx.discord(), ctx.author().id)
.await;
if permissions.map_or(true, |p| !p.manage_guild()) {
ctx.send(|b| {
b.ephemeral(true)
.content("Only admins can change other user's greet sounds.")
})
.await?;
return Ok(());
}
}
let sound_vec = ctx
.data()
.search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true)
@ -51,7 +80,7 @@ pub async fn set_greet_sound(
match sound_vec.first() {
Some(sound) => {
ctx.data()
.update_join_sound(ctx.author().id, Some(sound.id))
.update_join_sound(user.id, ctx.guild_id(), Some(sound.id))
.await;
ctx.say(format!(
@ -69,18 +98,104 @@ pub async fn set_greet_sound(
Ok(())
}
/// Set a join sound
/// Unset your global join sound
#[poise::command(slash_command, rename = "unset", guild_only = true)]
pub async fn unset_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
ctx.data().update_join_sound(ctx.author().id, None).await;
pub async fn unset_guild_greet_sound(
ctx: Context<'_>,
#[description = "User to set join sound for"] user: User,
) -> Result<(), Error> {
if user.id != ctx.author().id {
let guild = ctx.guild().unwrap();
let permissions = guild
.member_permissions(&ctx.discord(), ctx.author().id)
.await;
if permissions.map_or(true, |p| !p.manage_guild()) {
ctx.send(|b| {
b.ephemeral(true)
.content("Only admins can change other user's greet sounds.")
})
.await?;
return Ok(());
}
}
ctx.data()
.update_join_sound(user.id, ctx.guild_id(), None)
.await;
ctx.say("Greet sound has been unset").await?;
Ok(())
}
/// Manage your own greet sound
#[poise::command(slash_command, rename = "user")]
pub async fn user_greet_sound(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
/// Set your global join sound
#[poise::command(slash_command, rename = "set")]
pub async fn set_user_greet_sound(
ctx: Context<'_>,
#[description = "Name or ID of sound to set as your join sound"]
#[autocomplete = "autocomplete_sound"]
name: String,
) -> Result<(), Error> {
let sound_vec = ctx
.data()
.search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true)
.await?;
match sound_vec.first() {
Some(sound) => {
ctx.data()
.update_join_sound(ctx.author().id, None::<GuildId>, Some(sound.id))
.await;
ctx.send(|b| {
b.ephemeral(true).content(format!(
"Greet sound has been set to {} (ID {})",
sound.name, sound.id
))
})
.await?;
}
None => {
ctx.send(|b| {
b.ephemeral(true)
.content("Could not find a sound by that name.")
})
.await?;
}
}
Ok(())
}
/// Unset your global join sound
#[poise::command(slash_command, rename = "unset", guild_only = true)]
pub async fn unset_user_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
ctx.data()
.update_join_sound(ctx.author().id, None::<GuildId>, None)
.await;
ctx.send(|b| b.ephemeral(true).content("Greet sound has been unset"))
.await?;
Ok(())
}
/// Disable greet sounds on this server
#[poise::command(slash_command, rename = "disable", guild_only = true)]
#[poise::command(
slash_command,
rename = "disable",
guild_only = true,
required_permissions = "MANAGE_GUILD"
)]
pub async fn disable_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
let guild_data_opt = ctx.guild_data(ctx.guild_id().unwrap()).await;
@ -97,7 +212,12 @@ pub async fn disable_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
}
/// Enable greet sounds on this server
#[poise::command(slash_command, rename = "enable", guild_only = true)]
#[poise::command(
slash_command,
rename = "enable",
guild_only = true,
required_permissions = "MANAGE_GUILD"
)]
pub async fn enable_greet_sound(ctx: Context<'_>) -> Result<(), Error> {
let guild_data_opt = ctx.guild_data(ctx.guild_id().unwrap()).await;

View File

@ -92,7 +92,8 @@ pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> R
}
if allowed_greets {
if let Some(join_id) = data.join_sound(new.user_id).await {
if let Some(join_id) = data.join_sound(new.user_id, new.guild_id).await
{
let mut sound = sqlx::query_as_unchecked!(
Sound,
"

View File

@ -25,14 +25,13 @@ use tokio::sync::RwLock;
use crate::{event_handlers::listener, models::guild_data::GuildData};
// Which database driver are we using?
type Database = MySql;
pub struct Data {
database: Pool<Database>,
http: reqwest::Client,
guild_data_cache: DashMap<GuildId, Arc<RwLock<GuildData>>>,
join_sound_cache: DashMap<UserId, Option<u32>>,
join_sound_cache: DashMap<UserId, DashMap<Option<GuildId>, Option<u32>>>,
}
type Error = Box<dyn std::error::Error + Send + Sync>;
@ -103,10 +102,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
cmds::settings::change_volume(),
poise::Command {
subcommands: vec![
poise::Command {
subcommands: vec![
cmds::settings::set_guild_greet_sound(),
cmds::settings::unset_guild_greet_sound(),
],
..cmds::settings::guild_greet_sound()
},
poise::Command {
subcommands: vec![
cmds::settings::set_user_greet_sound(),
cmds::settings::unset_user_greet_sound(),
],
..cmds::settings::user_greet_sound()
},
cmds::settings::disable_greet_sound(),
cmds::settings::enable_greet_sound(),
cmds::settings::set_greet_sound(),
cmds::settings::unset_greet_sound(),
],
..cmds::settings::greet_sound()
},

View File

@ -1,45 +1,69 @@
use poise::serenity::{async_trait, model::id::UserId};
use poise::{
serenity::{async_trait, model::id::UserId},
serenity_prelude::GuildId,
};
use crate::Data;
#[async_trait]
pub trait JoinSoundCtx {
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32>;
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
async fn join_sound<U: Into<UserId> + Send + Sync, G: Into<GuildId> + Send + Sync>(
&self,
user_id: U,
guild_id: Option<G>,
) -> Option<u32>;
async fn update_join_sound<U: Into<UserId> + Send + Sync, G: Into<GuildId> + Send + Sync>(
&self,
user_id: U,
guild_id: Option<G>,
join_id: Option<u32>,
);
) -> Result<(), sqlx::Error>;
}
#[async_trait]
impl JoinSoundCtx for Data {
async fn join_sound<U: Into<UserId> + Send + Sync>(&self, user_id: U) -> Option<u32> {
async fn join_sound<U: Into<UserId> + Send + Sync, G: Into<GuildId> + Send + Sync>(
&self,
user_id: U,
guild_id: Option<G>,
) -> Option<u32> {
let user_id = user_id.into();
let guild_id = guild_id.map(|g| g.into());
let x = if let Some(join_sound_id) = self.join_sound_cache.get(&user_id) {
join_sound_id.value().clone()
let cached_join_id = self
.join_sound_cache
.get(&user_id)
.map(|d| d.get(&guild_id).map(|i| i.value().clone()))
.flatten();
let x = if let Some(join_sound_id) = cached_join_id {
join_sound_id
} else {
let join_sound_id = {
let join_id_res = sqlx::query!(
"
SELECT join_sound_id
FROM users
FROM join_sounds
WHERE user = ?
AND (guild IS NULL OR guild = ?)
ORDER BY guild IS NULL
",
user_id.as_u64()
user_id.as_u64(),
guild_id.map(|g| g.0)
)
.fetch_one(&self.database)
.await;
if let Ok(row) = join_id_res {
row.join_sound_id
Some(row.join_sound_id)
} else {
None
}
};
self.join_sound_cache.insert(user_id, join_sound_id);
self.join_sound_cache.entry(user_id).and_modify(|d| {
d.insert(guild_id, join_sound_id);
});
join_sound_id
};
@ -47,39 +71,54 @@ SELECT join_sound_id
x
}
async fn update_join_sound<U: Into<UserId> + Send + Sync>(
async fn update_join_sound<U: Into<UserId> + Send + Sync, G: Into<GuildId> + Send + Sync>(
&self,
user_id: U,
guild_id: Option<G>,
join_id: Option<u32>,
) {
) -> Result<(), sqlx::Error> {
let user_id = user_id.into();
let guild_id = guild_id.map(|g| g.into());
self.join_sound_cache.insert(user_id, join_id);
self.join_sound_cache.entry(user_id).and_modify(|d| {
d.insert(guild_id, join_id);
});
let pool = self.database.clone();
let mut transaction = self.database.begin().await?;
let _ = sqlx::query!(
"
INSERT IGNORE INTO users (user)
VALUES (?)
",
user_id.as_u64()
)
.execute(&pool)
.await;
match join_id {
Some(join_id) => {
sqlx::query!(
"DELETE FROM join_sounds WHERE user = ? AND guild <=> ?",
user_id.0,
guild_id.map(|g| g.0)
)
.execute(&mut transaction)
.await?;
let _ = sqlx::query!(
"
UPDATE users
SET
join_sound_id = ?
WHERE
user = ?
",
join_id,
user_id.as_u64()
)
.execute(&pool)
.await;
sqlx::query!(
"INSERT INTO join_sounds (user, join_sound_id, guild) VALUES (?, ?, ?)",
user_id.0,
join_id,
guild_id.map(|g| g.0)
)
.execute(&mut transaction)
.await?;
}
None => {
sqlx::query!(
"DELETE FROM join_sounds WHERE user = ? AND guild <=> ?",
user_id.0,
guild_id.map(|g| g.0)
)
.execute(&mut transaction)
.await?;
}
}
transaction.commit().await?;
Ok(())
}
}