Compare commits
20 Commits
v1.5.7
...
jude/index
Author | SHA1 | Date | |
---|---|---|---|
a2ac7050d7 | |||
d3e00247bd | |||
6d324e10cb | |||
8390bf0ec6 | |||
e6f5db1842 | |||
fca080253f | |||
6482af923b | |||
e875038851 | |||
92d8d077df | |||
b861f6f093 | |||
66f45f11f2 | |||
e30a08e019 | |||
80f45a1f5c | |||
1a1b1b8144 | |||
34d5fddf6c | |||
ac41dbce0c | |||
208d169c76 | |||
9cfcb0d09c | |||
cc55b3e1d1 | |||
a9a08e656f |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/target
|
||||
.env
|
||||
.idea
|
||||
|
2
.idea/.gitignore
generated
vendored
2
.idea/.gitignore
generated
vendored
@ -1,2 +0,0 @@
|
||||
# Default ignored files
|
||||
/workspace.xml
|
11
.idea/dataSources.local.xml
generated
11
.idea/dataSources.local.xml
generated
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="dataSourceStorageLocal" created-in="CL-223.8836.42">
|
||||
<data-source name="MySQL for 5.1 - soundfx@localhost" uuid="1067c1d0-1386-4a39-b3f5-6d48d6f279eb">
|
||||
<database-info product="" version="" jdbc-version="" driver-name="" driver-version="" dbms="MYSQL" exact-version="0" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
<user-name>jude</user-name>
|
||||
<schema-mapping />
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
11
.idea/dataSources.xml
generated
11
.idea/dataSources.xml
generated
@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
|
||||
<data-source source="LOCAL" name="MySQL for 5.1 - soundfx@localhost" uuid="1067c1d0-1386-4a39-b3f5-6d48d6f279eb">
|
||||
<driver-ref>mysql</driver-ref>
|
||||
<synchronize>true</synchronize>
|
||||
<jdbc-driver>com.mysql.jdbc.Driver</jdbc-driver>
|
||||
<jdbc-url>jdbc:mysql://localhost:3306/soundfx</jdbc-url>
|
||||
</data-source>
|
||||
</component>
|
||||
</project>
|
7
.idea/dictionaries/jude.xml
generated
7
.idea/dictionaries/jude.xml
generated
@ -1,7 +0,0 @@
|
||||
<component name="ProjectDictionaryState">
|
||||
<dictionary name="jude">
|
||||
<words>
|
||||
<w>reqwest</w>
|
||||
</words>
|
||||
</dictionary>
|
||||
</component>
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
6
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="RsBorrowChecker" enabled="false" level="ERROR" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="ES6" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/soundfx-rs.iml" filepath="$PROJECT_DIR$/.idea/soundfx-rs.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
14
.idea/soundfx-rs.iml
generated
14
.idea/soundfx-rs.iml
generated
@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="CPP_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
6
.idea/sqldialects.xml
generated
6
.idea/sqldialects.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="PROJECT" dialect="MySQL" />
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
858
Cargo.lock
generated
858
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
11
Cargo.toml
11
Cargo.toml
@ -2,13 +2,13 @@
|
||||
name = "soundfx-rs"
|
||||
description = "Discord bot for custom sound effects and soundboards"
|
||||
license = "AGPL-3.0-only"
|
||||
version = "1.5.7"
|
||||
version = "1.5.11"
|
||||
authors = ["jellywx <judesouthworth@pm.me>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
songbird = { version = "0.3", features = ["builtin-queue"] }
|
||||
poise = "0.3"
|
||||
poise = "0.5.5"
|
||||
sqlx = { version = "0.5", default-features = false, features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "migrate"] }
|
||||
tokio = { version = "1", features = ["fs", "process", "io-util"] }
|
||||
lazy_static = "1.4"
|
||||
@ -22,7 +22,7 @@ serde = "1.0"
|
||||
dotenv = "0.15.0"
|
||||
|
||||
[patch."https://github.com/serenity-rs/serenity"]
|
||||
serenity = { version = "0.11.5" }
|
||||
serenity = { version = "0.11.6" }
|
||||
|
||||
[package.metadata.deb]
|
||||
depends = "$auto, ffmpeg"
|
||||
@ -30,7 +30,10 @@ suggests = "mysql-server-8.0"
|
||||
maintainer-scripts = "debian"
|
||||
assets = [
|
||||
["target/release/soundfx-rs", "usr/bin/soundfx-rs", "755"],
|
||||
["conf/default.env", "etc/soundfx-rs/default.env", "600"]
|
||||
["conf/default.env", "etc/soundfx-rs/config.env", "600"]
|
||||
]
|
||||
conf-files = [
|
||||
"/etc/soundfx-rs/config.env",
|
||||
]
|
||||
|
||||
[package.metadata.deb.systemd-units]
|
||||
|
9
Containerfile
Normal file
9
Containerfile
Normal file
@ -0,0 +1,9 @@
|
||||
FROM ubuntu:20.04
|
||||
|
||||
ENV RUSTUP_HOME=/usr/local/rustup \
|
||||
CARGO_HOME=/usr/local/cargo \
|
||||
PATH=/usr/local/cargo/bin:$PATH
|
||||
|
||||
RUN apt update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt install -y gcc gcc-multilib cmake ffmpeg libopus-dev pkg-config libssl-dev curl mysql-client-8.0
|
||||
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path --profile minimal
|
||||
RUN cargo install cargo-deb
|
19
README.md
19
README.md
@ -23,11 +23,26 @@ Options:
|
||||
|
||||
## Building from source
|
||||
|
||||
1. Install build dependencies: `sudo apt install gcc gcc-multilib cmake ffmpeg libopus-dev`
|
||||
When running from source, the config options above can be configured simply as environment variables.
|
||||
|
||||
Two options for building are offered. The first is easier.
|
||||
|
||||
### Build for local platform
|
||||
|
||||
1. Install build dependencies: `sudo apt install gcc gcc-multilib cmake ffmpeg libopus-dev pkg-config libssl-dev`
|
||||
2. Install database server: `sudo apt install mysql-server-8.0`. Create a database called `soundfx`
|
||||
3. Install Cargo and Rust from https://rustup.rs
|
||||
4. Install SQLx CLI: `cargo install sqlx-cli`
|
||||
5. From the source code directory, execute `sqlx migrate run`
|
||||
6. Build with cargo: `cargo build --release`
|
||||
|
||||
When running from source, the config options above can be configured simply as environment variables.
|
||||
### Build for other platform
|
||||
|
||||
By default, this builds targeting Ubuntu 20.04. Modify the Containerfile if you wish to target a different platform. These instructions are written using `podman`, but `docker` should work too.
|
||||
|
||||
1. Install container software: `sudo apt install podman`.
|
||||
2. Install database server: `sudo apt install mysql-server-8.0`. Create a database called `soundfx`
|
||||
3. Install SQLx CLI: `cargo install sqlx-cli`
|
||||
4. From the source code directory, execute `sqlx migrate run`
|
||||
5. Build container image: `podman build -t soundfx .`
|
||||
6. Build with podman: `podman run --rm --network=host -v "$PWD":/mnt -w /mnt -e "DATABASE_URL=mysql://user@localhost/soundfx" soundfx cargo deb`
|
||||
|
9
debian/postinst
vendored
Executable file
9
debian/postinst
vendored
Executable file
@ -0,0 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
id -u soundfx &>/dev/null || useradd -r -M soundfx
|
||||
|
||||
chown soundfx /etc/soundfx-rs/config.env
|
||||
|
||||
#DEBHELPER#
|
7
debian/postrm
vendored
Normal file
7
debian/postrm
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
id -u soundfx &>/dev/null || userdel soundfx
|
||||
|
||||
#DEBHELPER#
|
6
migrations/20230816145151_favorite_sounds.sql
Normal file
6
migrations/20230816145151_favorite_sounds.sql
Normal file
@ -0,0 +1,6 @@
|
||||
CREATE TABLE favorite_sounds (
|
||||
user_id BIGINT UNSIGNED NOT NULL,
|
||||
sound_id INT UNSIGNED NOT NULL,
|
||||
FOREIGN KEY (sound_id) REFERENCES `sounds`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY (user_id, sound_id)
|
||||
);
|
6
migrations/20231021161427_add_indexes.sql
Normal file
6
migrations/20231021161427_add_indexes.sql
Normal 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`);
|
8
scripts/dump-query.sh
Executable file
8
scripts/dump-query.sh
Executable file
@ -0,0 +1,8 @@
|
||||
mysql -D soundfx -N -e "SELECT name, hex(src) FROM sounds" > out
|
||||
split --additional-suffix=.row -l 1 out
|
||||
for filename in *.row; do
|
||||
name=`grep -oP '^(.+)(?=\t)' $filename`
|
||||
col=`awk -F '\t' '{print $2}' "$filename"`
|
||||
echo $col > "$filename.hex"
|
||||
xxd -r -p "$filename.hex" "$name.opus"
|
||||
done
|
94
src/cmds/favorite.rs
Normal file
94
src/cmds/favorite.rs
Normal file
@ -0,0 +1,94 @@
|
||||
use log::warn;
|
||||
|
||||
use crate::{cmds::autocomplete_favorite, models::sound::SoundCtx, Context, Error};
|
||||
|
||||
#[poise::command(slash_command, rename = "favorites", guild_only = true)]
|
||||
pub async fn favorites(_ctx: Context<'_>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a sound as a favorite
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
rename = "add",
|
||||
category = "Favorites",
|
||||
guild_only = true
|
||||
)]
|
||||
pub async fn add_favorite(
|
||||
ctx: Context<'_>,
|
||||
#[description = "Name or ID of sound to favorite"] name: String,
|
||||
) -> Result<(), Error> {
|
||||
let sounds = ctx
|
||||
.data()
|
||||
.search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true)
|
||||
.await;
|
||||
|
||||
match sounds {
|
||||
Ok(sounds) => {
|
||||
let sound = &sounds[0];
|
||||
|
||||
sound
|
||||
.add_favorite(ctx.author().id, &ctx.data().database)
|
||||
.await?;
|
||||
ctx.say(format!(
|
||||
"Sound {} (ID {}) added to favorites.",
|
||||
sound.name, sound.id
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
warn!("Couldn't fetch sounds: {:?}", e);
|
||||
|
||||
ctx.say("Failed to find sound.").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a sound from your favorites
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
rename = "remove",
|
||||
category = "Favorites",
|
||||
guild_only = true
|
||||
)]
|
||||
pub async fn remove_favorite(
|
||||
ctx: Context<'_>,
|
||||
#[description = "Name or ID of sound to favorite"]
|
||||
#[autocomplete = "autocomplete_favorite"]
|
||||
name: String,
|
||||
) -> Result<(), Error> {
|
||||
let sounds = ctx
|
||||
.data()
|
||||
.search_for_sound(&name, ctx.guild_id().unwrap(), ctx.author().id, true)
|
||||
.await;
|
||||
|
||||
match sounds {
|
||||
Ok(sounds) => {
|
||||
let sound = &sounds[0];
|
||||
|
||||
sound
|
||||
.remove_favorite(ctx.author().id, &ctx.data().database)
|
||||
.await?;
|
||||
ctx.say(format!(
|
||||
"Sound {} (ID {}) removed from favorites.",
|
||||
sound.name, sound.id
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
warn!("Couldn't fetch sounds: {:?}", e);
|
||||
|
||||
ctx.say("Failed to find sound.").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
@ -33,6 +33,9 @@ __Library Commands__
|
||||
`/public` - Set a sound as public/private
|
||||
`/list server` - List sounds on this server
|
||||
`/list user` - List your sounds
|
||||
`/favorites add` - Add a favorite
|
||||
`/favorites remove` - Remove a favorite
|
||||
`/list favorites` - List favorites
|
||||
|
||||
__Search Commands__
|
||||
`/search` - Search for public sounds by name
|
||||
@ -57,7 +60,7 @@ __Advanced Commands__
|
||||
/// Get additional information about the bot
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let current_user = ctx.discord().cache.current_user();
|
||||
let current_user = ctx.serenity_context().cache.current_user();
|
||||
|
||||
ctx.send(|m| m.ephemeral(true)
|
||||
.embed(|e| e
|
||||
|
@ -35,7 +35,13 @@ pub async fn upload_new_sound(
|
||||
}
|
||||
|
||||
if !name.is_empty() && name.len() <= 20 {
|
||||
if !is_numeric(&name) {
|
||||
if name.starts_with("@") {
|
||||
ctx.say("Sound names cannot start with an @ symbol. Please choose another name")
|
||||
.await?;
|
||||
} else if is_numeric(&name) {
|
||||
ctx.say("Please ensure the sound name contains a non-numerical character")
|
||||
.await?;
|
||||
} else {
|
||||
// need to check the name is not currently in use by the user
|
||||
let count_name =
|
||||
Sound::count_named_user_sounds(ctx.author().id, &name, &ctx.data().database)
|
||||
@ -50,11 +56,10 @@ pub async fn upload_new_sound(
|
||||
let count = Sound::count_user_sounds(ctx.author().id, &ctx.data().database).await?;
|
||||
let mut permit_upload = true;
|
||||
|
||||
// need to check if user is patreon or nah
|
||||
// need to check if user is Patreon or not
|
||||
if count >= *MAX_SOUNDS {
|
||||
let patreon_guild_member = GuildId(*PATREON_GUILD)
|
||||
.member(ctx.discord(), ctx.author().id)
|
||||
.await;
|
||||
let patreon_guild_member =
|
||||
GuildId(*PATREON_GUILD).member(ctx, ctx.author().id).await;
|
||||
|
||||
if let Ok(member) = patreon_guild_member {
|
||||
permit_upload = member.roles.contains(&RoleId(*PATREON_ROLE));
|
||||
@ -89,9 +94,6 @@ pub async fn upload_new_sound(
|
||||
)).await?;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.say("Please ensure the sound name contains a non-numerical character")
|
||||
.await?;
|
||||
}
|
||||
} else {
|
||||
ctx.say("Usage: `/upload <name>`. Please ensure the name provided is less than 20 characters in length").await?;
|
||||
@ -123,8 +125,8 @@ pub async fn delete_sound(
|
||||
.await?;
|
||||
} else {
|
||||
let has_perms = {
|
||||
if let Ok(member) = ctx.guild_id().unwrap().member(&ctx.discord(), uid).await {
|
||||
if let Ok(perms) = member.permissions(&ctx.discord()) {
|
||||
if let Ok(member) = ctx.guild_id().unwrap().member(&ctx, uid).await {
|
||||
if let Ok(perms) = member.permissions(&ctx) {
|
||||
perms.manage_guild()
|
||||
} else {
|
||||
false
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{models::sound::SoundCtx, Context};
|
||||
|
||||
pub mod favorite;
|
||||
pub mod info;
|
||||
pub mod manage;
|
||||
pub mod play;
|
||||
@ -22,3 +23,19 @@ pub async fn autocomplete_sound(
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub async fn autocomplete_favorite(
|
||||
ctx: Context<'_>,
|
||||
partial: &str,
|
||||
) -> Vec<poise::AutocompleteChoice<String>> {
|
||||
ctx.data()
|
||||
.autocomplete_favorite_sounds(&partial, ctx.author().id)
|
||||
.await
|
||||
.unwrap_or(vec![])
|
||||
.iter()
|
||||
.map(|s| poise::AutocompleteChoice {
|
||||
name: s.name.clone(),
|
||||
value: s.id.to_string(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use poise::serenity_prelude::{
|
||||
builder::CreateActionRow, model::application::component::ButtonStyle, GuildChannel,
|
||||
ReactionType,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -24,13 +25,9 @@ pub async fn play(
|
||||
|
||||
let guild = ctx.guild().unwrap();
|
||||
|
||||
if channel.as_ref().map_or(false, |c| c.is_text_based()) {
|
||||
ctx.say("The channel specified is not a voice channel.")
|
||||
.await?;
|
||||
} else {
|
||||
ctx.say(
|
||||
play_from_query(
|
||||
&ctx.discord(),
|
||||
&ctx.serenity_context(),
|
||||
&ctx.data(),
|
||||
guild,
|
||||
ctx.author().id,
|
||||
@ -41,7 +38,6 @@ pub async fn play(
|
||||
.await,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -142,7 +138,8 @@ pub async fn queue_play(
|
||||
|
||||
match channel_to_join {
|
||||
Some(user_channel) => {
|
||||
let (call_handler, _) = join_channel(ctx.discord(), guild.clone(), user_channel).await;
|
||||
let (call_handler, _) =
|
||||
join_channel(ctx.serenity_context(), guild.clone(), user_channel).await;
|
||||
|
||||
let guild_data = ctx
|
||||
.data()
|
||||
@ -231,7 +228,7 @@ pub async fn loop_play(
|
||||
|
||||
ctx.say(
|
||||
play_from_query(
|
||||
&ctx.discord(),
|
||||
&ctx.serenity_context(),
|
||||
&ctx.data(),
|
||||
guild,
|
||||
ctx.author().id,
|
||||
@ -316,21 +313,6 @@ pub async fn soundboard(
|
||||
#[description = "Name or ID of sound for button 20"]
|
||||
#[autocomplete = "autocomplete_sound"]
|
||||
sound_20: Option<String>,
|
||||
#[description = "Name or ID of sound for button 21"]
|
||||
#[autocomplete = "autocomplete_sound"]
|
||||
sound_21: Option<String>,
|
||||
#[description = "Name or ID of sound for button 22"]
|
||||
#[autocomplete = "autocomplete_sound"]
|
||||
sound_22: Option<String>,
|
||||
#[description = "Name or ID of sound for button 23"]
|
||||
#[autocomplete = "autocomplete_sound"]
|
||||
sound_23: Option<String>,
|
||||
#[description = "Name or ID of sound for button 24"]
|
||||
#[autocomplete = "autocomplete_sound"]
|
||||
sound_24: Option<String>,
|
||||
#[description = "Name or ID of sound for button 25"]
|
||||
#[autocomplete = "autocomplete_sound"]
|
||||
sound_25: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
ctx.defer().await?;
|
||||
|
||||
@ -355,11 +337,6 @@ pub async fn soundboard(
|
||||
sound_18,
|
||||
sound_19,
|
||||
sound_20,
|
||||
sound_21,
|
||||
sound_22,
|
||||
sound_23,
|
||||
sound_24,
|
||||
sound_25,
|
||||
];
|
||||
|
||||
let mut sounds = vec![];
|
||||
@ -392,7 +369,33 @@ pub async fn soundboard(
|
||||
c.add_action_row(action_row);
|
||||
}
|
||||
|
||||
c
|
||||
c.create_action_row(|r| {
|
||||
r.create_button(|b| {
|
||||
b.label("Stop")
|
||||
.emoji(ReactionType::Unicode("⏹".to_string()))
|
||||
.style(ButtonStyle::Danger)
|
||||
.custom_id("#stop")
|
||||
})
|
||||
.create_button(|b| {
|
||||
b.label("Mode:")
|
||||
.style(ButtonStyle::Secondary)
|
||||
.disabled(true)
|
||||
.custom_id("#mode")
|
||||
})
|
||||
.create_button(|b| {
|
||||
b.label("Instant")
|
||||
.emoji(ReactionType::Unicode("▶".to_string()))
|
||||
.style(ButtonStyle::Secondary)
|
||||
.disabled(true)
|
||||
.custom_id("#instant")
|
||||
})
|
||||
.create_button(|b| {
|
||||
b.label("Loop")
|
||||
.emoji(ReactionType::Unicode("🔁".to_string()))
|
||||
.style(ButtonStyle::Secondary)
|
||||
.custom_id("#loop")
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
@ -47,12 +47,14 @@ pub async fn list_sounds(_ctx: Context<'_>) -> Result<(), Error> {
|
||||
enum ListContext {
|
||||
User = 0,
|
||||
Guild = 1,
|
||||
Favorite = 2,
|
||||
}
|
||||
|
||||
impl ListContext {
|
||||
pub fn title(&self) -> &'static str {
|
||||
match self {
|
||||
ListContext::User => "Your sounds",
|
||||
ListContext::Favorite => "Your favorite sounds",
|
||||
ListContext::Guild => "Server sounds",
|
||||
}
|
||||
}
|
||||
@ -86,6 +88,20 @@ pub async fn list_user_sounds(ctx: Context<'_>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Show sounds you have favorited
|
||||
#[poise::command(slash_command, rename = "favorite", guild_only = true)]
|
||||
pub async fn list_favorite_sounds(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let pager = SoundPager {
|
||||
nonce: 0,
|
||||
page: 0,
|
||||
context: ListContext::Favorite,
|
||||
};
|
||||
|
||||
pager.reply(ctx).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SoundPager {
|
||||
nonce: u64,
|
||||
@ -102,6 +118,7 @@ impl SoundPager {
|
||||
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||
match self.context {
|
||||
ListContext::User => data.user_sounds(user_id, Some(self.page)).await,
|
||||
ListContext::Favorite => data.favorite_sounds(user_id, Some(self.page)).await,
|
||||
ListContext::Guild => data.guild_sounds(guild_id, Some(self.page)).await,
|
||||
}
|
||||
}
|
||||
@ -205,6 +222,7 @@ impl SoundPager {
|
||||
let sounds = pager.get_page(data, user_id, guild_id).await?;
|
||||
let count = match pager.context {
|
||||
ListContext::User => data.count_user_sounds(user_id).await?,
|
||||
ListContext::Favorite => data.count_favorite_sounds(user_id).await?,
|
||||
ListContext::Guild => data.count_guild_sounds(guild_id).await?,
|
||||
};
|
||||
|
||||
@ -228,6 +246,7 @@ impl SoundPager {
|
||||
.await?;
|
||||
let count = match self.context {
|
||||
ListContext::User => ctx.data().count_user_sounds(ctx.author().id).await?,
|
||||
ListContext::Favorite => ctx.data().count_favorite_sounds(ctx.author().id).await?,
|
||||
ListContext::Guild => {
|
||||
ctx.data()
|
||||
.count_guild_sounds(ctx.guild_id().unwrap())
|
||||
@ -273,28 +292,3 @@ pub async fn search_sounds(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Show a page of random sounds
|
||||
#[poise::command(slash_command, rename = "random", guild_only = true)]
|
||||
pub async fn show_random_sounds(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let search_results = sqlx::query_as_unchecked!(
|
||||
Sound,
|
||||
"
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE public = 1
|
||||
ORDER BY rand()
|
||||
LIMIT 25
|
||||
"
|
||||
)
|
||||
.fetch_all(&ctx.data().database)
|
||||
.await?;
|
||||
|
||||
ctx.send(|m| {
|
||||
*m = format_search_results(search_results);
|
||||
m
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -61,9 +61,7 @@ pub async fn set_guild_greet_sound(
|
||||
) -> Result<(), Error> {
|
||||
if user.id != ctx.author().id {
|
||||
let guild = ctx.guild().unwrap();
|
||||
let permissions = guild
|
||||
.member_permissions(&ctx.discord(), ctx.author().id)
|
||||
.await;
|
||||
let permissions = guild.member_permissions(&ctx, ctx.author().id).await;
|
||||
|
||||
if permissions.map_or(true, |p| !p.manage_guild()) {
|
||||
ctx.send(|b| {
|
||||
@ -110,9 +108,7 @@ pub async fn unset_guild_greet_sound(
|
||||
) -> Result<(), Error> {
|
||||
if user.id != ctx.author().id {
|
||||
let guild = ctx.guild().unwrap();
|
||||
let permissions = guild
|
||||
.member_permissions(&ctx.discord(), ctx.author().id)
|
||||
.await;
|
||||
let permissions = guild.member_permissions(&ctx, ctx.author().id).await;
|
||||
|
||||
if permissions.map_or(true, |p| !p.manage_guild()) {
|
||||
ctx.send(|b| {
|
||||
|
@ -10,7 +10,7 @@ use crate::{Context, Error};
|
||||
guild_only = true
|
||||
)]
|
||||
pub async fn stop_playing(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let songbird = songbird::get(ctx.discord()).await.unwrap();
|
||||
let songbird = songbird::get(ctx.serenity_context()).await.unwrap();
|
||||
let call_opt = songbird.get(ctx.guild_id().unwrap());
|
||||
|
||||
if let Some(call) = call_opt {
|
||||
@ -27,7 +27,7 @@ pub async fn stop_playing(ctx: Context<'_>) -> Result<(), Error> {
|
||||
/// Disconnect the bot
|
||||
#[poise::command(slash_command, default_member_permissions = "SPEAK", guild_only = true)]
|
||||
pub async fn disconnect(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let songbird = songbird::get(ctx.discord()).await.unwrap();
|
||||
let songbird = songbird::get(ctx.serenity_context()).await.unwrap();
|
||||
let _ = songbird.leave(ctx.guild_id().unwrap()).await;
|
||||
|
||||
ctx.say("👍").await?;
|
||||
|
@ -6,7 +6,7 @@ use poise::serenity_prelude::{
|
||||
channel::Channel,
|
||||
},
|
||||
utils::shard_id,
|
||||
Activity, Context,
|
||||
ActionRowComponent, Activity, Context, CreateActionRow, CreateComponents,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@ -137,6 +137,9 @@ SELECT name, id, public, server_id, uploader_id
|
||||
if let Some(guild_id) = component.guild_id {
|
||||
if let Ok(()) = SoundPager::handle_interaction(ctx, &data, component).await {
|
||||
} else {
|
||||
let mode = component.data.custom_id.as_str();
|
||||
match mode {
|
||||
"#stop" => {
|
||||
component
|
||||
.create_interaction_response(ctx, |r| {
|
||||
r.kind(InteractionResponseType::DeferredUpdateMessage)
|
||||
@ -144,19 +147,111 @@ SELECT name, id, public, server_id, uploader_id
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let songbird = songbird::get(ctx).await.unwrap();
|
||||
let call_opt = songbird.get(guild_id);
|
||||
|
||||
if let Some(call) = call_opt {
|
||||
let mut lock = call.lock().await;
|
||||
|
||||
lock.stop();
|
||||
}
|
||||
}
|
||||
|
||||
"#loop" | "#queue" | "#instant" => {
|
||||
component
|
||||
.create_interaction_response(ctx, |r| {
|
||||
r.kind(InteractionResponseType::UpdateMessage)
|
||||
.interaction_response_data(|d| {
|
||||
let mut c: CreateComponents = Default::default();
|
||||
|
||||
for action_row in &component.message.components {
|
||||
let mut a: CreateActionRow = Default::default();
|
||||
// These are always buttons
|
||||
for component in &action_row.components {
|
||||
match component {
|
||||
ActionRowComponent::Button(button) => {
|
||||
a.create_button(|b| {
|
||||
if let Some(label) =
|
||||
&button.label
|
||||
{
|
||||
b.label(label);
|
||||
}
|
||||
if let Some(emoji) =
|
||||
&button.emoji
|
||||
{
|
||||
b.emoji(emoji.clone());
|
||||
}
|
||||
if let Some(custom_id) =
|
||||
&button.custom_id
|
||||
{
|
||||
if custom_id
|
||||
.starts_with('#')
|
||||
{
|
||||
b.custom_id(custom_id)
|
||||
.disabled(
|
||||
custom_id
|
||||
== "#mode"
|
||||
|| custom_id
|
||||
== mode,
|
||||
);
|
||||
} else {
|
||||
b.custom_id(format!(
|
||||
"{}{}",
|
||||
custom_id
|
||||
.split('#')
|
||||
.next()
|
||||
.unwrap(),
|
||||
mode
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
b.style(button.style);
|
||||
|
||||
b
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
c.add_action_row(a);
|
||||
}
|
||||
|
||||
d.set_components(c)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
id_mode => {
|
||||
component
|
||||
.create_interaction_response(ctx, |r| {
|
||||
r.kind(InteractionResponseType::DeferredUpdateMessage)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut it = id_mode.split('#');
|
||||
let id = it.next().unwrap();
|
||||
let mode = it.next().unwrap_or("instant");
|
||||
|
||||
play_from_query(
|
||||
&ctx,
|
||||
&data,
|
||||
guild_id.to_guild_cached(&ctx).unwrap(),
|
||||
component.user.id,
|
||||
None,
|
||||
&component.data.custom_id,
|
||||
false,
|
||||
id.split('#').next().unwrap(),
|
||||
mode == "loop",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
|
16
src/main.rs
16
src/main.rs
@ -68,8 +68,8 @@ pub async fn register_application_commands(
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
if Path::new("/etc/soundfx-rs/default.env").exists() {
|
||||
dotenv::from_path("/etc/soundfx-rs/default.env").unwrap();
|
||||
if Path::new("/etc/soundfx-rs/config.env").exists() {
|
||||
dotenv::from_path("/etc/soundfx-rs/config.env").unwrap();
|
||||
}
|
||||
|
||||
env_logger::init();
|
||||
@ -92,9 +92,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
subcommands: vec![
|
||||
cmds::search::list_guild_sounds(),
|
||||
cmds::search::list_user_sounds(),
|
||||
cmds::search::list_favorite_sounds(),
|
||||
],
|
||||
..cmds::search::list_sounds()
|
||||
},
|
||||
poise::Command {
|
||||
subcommands: vec![
|
||||
cmds::favorite::add_favorite(),
|
||||
cmds::favorite::remove_favorite(),
|
||||
],
|
||||
..cmds::favorite::favorites()
|
||||
},
|
||||
cmds::search::show_random_sounds(),
|
||||
cmds::search::search_sounds(),
|
||||
cmds::stop::stop_playing(),
|
||||
@ -124,7 +132,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
},
|
||||
],
|
||||
allowed_mentions: None,
|
||||
listener: |ctx, event, _framework, data| Box::pin(listener(ctx, event, data)),
|
||||
event_handler: |ctx, event, _framework, data| Box::pin(listener(ctx, event, data)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -136,7 +144,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
|
||||
poise::Framework::builder()
|
||||
.token(discord_token)
|
||||
.user_data_setup(move |ctx, _bot, framework| {
|
||||
.setup(move |ctx, _bot, framework| {
|
||||
Box::pin(async move {
|
||||
register_application_commands(ctx, framework, None)
|
||||
.await
|
||||
|
@ -37,17 +37,31 @@ pub trait SoundCtx {
|
||||
user_id: U,
|
||||
guild_id: G,
|
||||
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||
async fn autocomplete_favorite_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
query: &str,
|
||||
user_id: U,
|
||||
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||
async fn user_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
user_id: U,
|
||||
page: Option<u64>,
|
||||
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||
async fn favorite_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
user_id: U,
|
||||
page: Option<u64>,
|
||||
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||
async fn guild_sounds<G: Into<u64> + Send>(
|
||||
&self,
|
||||
guild_id: G,
|
||||
page: Option<u64>,
|
||||
) -> Result<Vec<Sound>, sqlx::Error>;
|
||||
async fn count_user_sounds<U: Into<u64> + Send>(&self, user_id: U) -> Result<u64, sqlx::Error>;
|
||||
async fn count_favorite_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
user_id: U,
|
||||
) -> Result<u64, sqlx::Error>;
|
||||
async fn count_guild_sounds<G: Into<u64> + Send>(
|
||||
&self,
|
||||
guild_id: G,
|
||||
@ -91,8 +105,7 @@ SELECT name, id, public, server_id, uploader_id
|
||||
public = 1 OR
|
||||
uploader_id = ? OR
|
||||
server_id = ?
|
||||
)
|
||||
",
|
||||
)",
|
||||
id,
|
||||
user_id,
|
||||
guild_id
|
||||
@ -116,12 +129,21 @@ SELECT name, id, public, server_id, uploader_id
|
||||
uploader_id = ? OR
|
||||
server_id = ?
|
||||
)
|
||||
ORDER BY uploader_id = ? DESC, server_id = ? DESC, public = 1 DESC, rand()
|
||||
",
|
||||
ORDER BY
|
||||
uploader_id = ? DESC,
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM favorite_sounds
|
||||
WHERE sound_id = id AND user_id = ?
|
||||
) DESC,
|
||||
server_id = ? DESC,
|
||||
public = 1 DESC,
|
||||
rand()",
|
||||
name,
|
||||
user_id,
|
||||
guild_id,
|
||||
user_id,
|
||||
user_id,
|
||||
guild_id
|
||||
)
|
||||
.fetch_all(&db_pool)
|
||||
@ -137,12 +159,21 @@ SELECT name, id, public, server_id, uploader_id
|
||||
uploader_id = ? OR
|
||||
server_id = ?
|
||||
)
|
||||
ORDER BY uploader_id = ? DESC, server_id = ? DESC, public = 1 DESC, rand()
|
||||
",
|
||||
ORDER BY
|
||||
uploader_id = ? DESC,
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM favorite_sounds
|
||||
WHERE sound_id = id AND user_id = ?
|
||||
) DESC,
|
||||
server_id = ? DESC,
|
||||
public = 1 DESC,
|
||||
rand()",
|
||||
name,
|
||||
user_id,
|
||||
guild_id,
|
||||
user_id,
|
||||
user_id,
|
||||
guild_id
|
||||
)
|
||||
.fetch_all(&db_pool)
|
||||
@ -160,18 +191,48 @@ SELECT name, id, public, server_id, uploader_id
|
||||
guild_id: G,
|
||||
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||
let db_pool = self.database.clone();
|
||||
let user_id = user_id.into();
|
||||
|
||||
sqlx::query_as_unchecked!(
|
||||
Sound,
|
||||
"
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE name LIKE CONCAT(?, '%') AND (uploader_id = ? OR server_id = ?)
|
||||
LIMIT 25
|
||||
",
|
||||
WHERE name LIKE CONCAT(?, '%') AND (uploader_id = ? OR server_id = ? OR EXISTS(
|
||||
SELECT 1
|
||||
FROM favorite_sounds
|
||||
WHERE sound_id = id AND user_id = ?
|
||||
))
|
||||
LIMIT 25",
|
||||
query,
|
||||
user_id,
|
||||
guild_id.into(),
|
||||
user_id,
|
||||
)
|
||||
.fetch_all(&db_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn autocomplete_favorite_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
query: &str,
|
||||
user_id: U,
|
||||
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||
let db_pool = self.database.clone();
|
||||
|
||||
sqlx::query_as_unchecked!(
|
||||
Sound,
|
||||
"
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE name LIKE CONCAT(?, '%') AND EXISTS(
|
||||
SELECT 1
|
||||
FROM favorite_sounds
|
||||
WHERE sound_id = id AND user_id = ?
|
||||
)
|
||||
LIMIT 25",
|
||||
query,
|
||||
user_id.into(),
|
||||
guild_id.into(),
|
||||
)
|
||||
.fetch_all(&db_pool)
|
||||
.await
|
||||
@ -191,8 +252,7 @@ SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE uploader_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT ?, ?
|
||||
",
|
||||
LIMIT ?, ?",
|
||||
user_id.into(),
|
||||
page * 25,
|
||||
(page + 1) * 25
|
||||
@ -207,8 +267,49 @@ SELECT name, id, public, server_id, uploader_id
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE uploader_id = ?
|
||||
ORDER BY id DESC",
|
||||
user_id.into()
|
||||
)
|
||||
.fetch_all(&self.database)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(sounds)
|
||||
}
|
||||
|
||||
async fn favorite_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
user_id: U,
|
||||
page: Option<u64>,
|
||||
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||
let sounds = match page {
|
||||
Some(page) => {
|
||||
sqlx::query_as_unchecked!(
|
||||
Sound,
|
||||
"
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
INNER JOIN favorite_sounds f ON sounds.id = f.sound_id
|
||||
WHERE f.user_id = ?
|
||||
ORDER BY id DESC
|
||||
",
|
||||
LIMIT ?, ?",
|
||||
user_id.into(),
|
||||
page * 25,
|
||||
(page + 1) * 25
|
||||
)
|
||||
.fetch_all(&self.database)
|
||||
.await?
|
||||
}
|
||||
None => {
|
||||
sqlx::query_as_unchecked!(
|
||||
Sound,
|
||||
"
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
INNER JOIN favorite_sounds f ON sounds.id = f.sound_id
|
||||
WHERE f.user_id = ?
|
||||
ORDER BY id DESC",
|
||||
user_id.into()
|
||||
)
|
||||
.fetch_all(&self.database)
|
||||
@ -233,8 +334,7 @@ SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE server_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT ?, ?
|
||||
",
|
||||
LIMIT ?, ?",
|
||||
guild_id.into(),
|
||||
page * 25,
|
||||
(page + 1) * 25
|
||||
@ -250,8 +350,7 @@ SELECT name, id, public, server_id, uploader_id
|
||||
SELECT name, id, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE server_id = ?
|
||||
ORDER BY id DESC
|
||||
",
|
||||
ORDER BY id DESC",
|
||||
guild_id.into()
|
||||
)
|
||||
.fetch_all(&self.database)
|
||||
@ -272,6 +371,19 @@ SELECT name, id, public, server_id, uploader_id
|
||||
.count as u64)
|
||||
}
|
||||
|
||||
async fn count_favorite_sounds<U: Into<u64> + Send>(
|
||||
&self,
|
||||
user_id: U,
|
||||
) -> Result<u64, sqlx::Error> {
|
||||
Ok(sqlx::query!(
|
||||
"SELECT COUNT(1) as count FROM favorite_sounds WHERE user_id = ?",
|
||||
user_id.into()
|
||||
)
|
||||
.fetch_one(&self.database)
|
||||
.await?
|
||||
.count as u64)
|
||||
}
|
||||
|
||||
async fn count_guild_sounds<G: Into<u64> + Send>(
|
||||
&self,
|
||||
guild_id: G,
|
||||
@ -298,8 +410,7 @@ impl Sound {
|
||||
SELECT src
|
||||
FROM sounds
|
||||
WHERE id = ?
|
||||
LIMIT 1
|
||||
",
|
||||
LIMIT 1",
|
||||
self.id
|
||||
)
|
||||
.fetch_one(db_pool)
|
||||
@ -348,8 +459,7 @@ SELECT src
|
||||
"
|
||||
SELECT COUNT(1) as count
|
||||
FROM sounds
|
||||
WHERE uploader_id = ?
|
||||
",
|
||||
WHERE uploader_id = ?",
|
||||
user_id
|
||||
)
|
||||
.fetch_one(db_pool)
|
||||
@ -372,8 +482,7 @@ SELECT COUNT(1) as count
|
||||
FROM sounds
|
||||
WHERE
|
||||
uploader_id = ? AND
|
||||
name = ?
|
||||
",
|
||||
name = ?",
|
||||
user_id,
|
||||
name
|
||||
)
|
||||
@ -394,8 +503,7 @@ UPDATE sounds
|
||||
SET
|
||||
public = ?
|
||||
WHERE
|
||||
id = ?
|
||||
",
|
||||
id = ?",
|
||||
self.public,
|
||||
self.id
|
||||
)
|
||||
@ -409,12 +517,41 @@ WHERE
|
||||
&self,
|
||||
db_pool: impl Executor<'_, Database = Database>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
sqlx::query!("DELETE FROM sounds WHERE id = ?", self.id)
|
||||
.execute(db_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn add_favorite<U: Into<u64>>(
|
||||
&self,
|
||||
user_id: U,
|
||||
db_pool: impl Executor<'_, Database = Database>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync + Send>> {
|
||||
let user_id = user_id.into();
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE
|
||||
FROM sounds
|
||||
WHERE id = ?
|
||||
",
|
||||
"INSERT INTO favorite_sounds (user_id, sound_id) VALUES (?, ?)",
|
||||
user_id,
|
||||
self.id
|
||||
)
|
||||
.execute(db_pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn remove_favorite<U: Into<u64>>(
|
||||
&self,
|
||||
user_id: U,
|
||||
db_pool: impl Executor<'_, Database = Database>,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync + Send>> {
|
||||
let user_id = user_id.into();
|
||||
|
||||
sqlx::query!(
|
||||
"DELETE FROM favorite_sounds WHERE user_id = ? AND sound_id = ?",
|
||||
user_id,
|
||||
self.id
|
||||
)
|
||||
.execute(db_pool)
|
||||
@ -468,8 +605,7 @@ DELETE
|
||||
match sqlx::query!(
|
||||
"
|
||||
INSERT INTO sounds (name, server_id, uploader_id, public, src)
|
||||
VALUES (?, ?, ?, 1, ?)
|
||||
",
|
||||
VALUES (?, ?, ?, 1, ?)",
|
||||
name,
|
||||
server_id,
|
||||
user_id,
|
||||
|
@ -22,13 +22,13 @@ pub async fn play_audio(
|
||||
volume: u8,
|
||||
call_handler: &mut MutexGuard<'_, Call>,
|
||||
db_pool: impl Executor<'_, Database = Database>,
|
||||
loop_: bool,
|
||||
r#loop: bool,
|
||||
) -> Result<TrackHandle, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let (track, track_handler) = create_player(sound.playable(db_pool).await?.into());
|
||||
|
||||
let _ = track_handler.set_volume(volume as f32 / 100.0);
|
||||
|
||||
if loop_ {
|
||||
if r#loop {
|
||||
let _ = track_handler.enable_loop();
|
||||
} else {
|
||||
let _ = track_handler.disable_loop();
|
||||
|
@ -2,6 +2,7 @@
|
||||
Description=Discord bot for custom sound effects and soundboards
|
||||
|
||||
[Service]
|
||||
User=soundfx
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/soundfx-rs
|
||||
WorkingDirectory=/etc/soundfx-rs
|
||||
|
Reference in New Issue
Block a user