diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-06 21:34:53 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-06 21:34:53 +0100 |
| commit | f471d4caefff51d49b69551412f4859d99f674a2 (patch) | |
| tree | 1b48ef33957c39522eaaa599324e1d01ac85178f | |
| parent | tests: skip the piper smoke test when models are absent (diff) | |
| download | senator-sperma-f471d4caefff51d49b69551412f4859d99f674a2.tar.gz senator-sperma-f471d4caefff51d49b69551412f4859d99f674a2.zip | |
revert required permissionsmain
Signed-off-by: salmonik <salmonik@tuta.io>
| -rw-r--r-- | src/commands/cron.rs | 21 | ||||
| -rw-r--r-- | src/commands/record.rs | 2 | ||||
| -rw-r--r-- | src/commands/trigger.rs | 12 | ||||
| -rw-r--r-- | src/commands/utilities.rs | 62 |
4 files changed, 58 insertions, 39 deletions
diff --git a/src/commands/cron.rs b/src/commands/cron.rs index ec5b702..809d1a4 100644 --- a/src/commands/cron.rs +++ b/src/commands/cron.rs | |||
| @@ -12,7 +12,6 @@ type Context<'a> = poise::Context<'a, Data, Error>; | |||
| 12 | #[poise::command( | 12 | #[poise::command( |
| 13 | slash_command, | 13 | slash_command, |
| 14 | guild_only, | 14 | guild_only, |
| 15 | required_permissions = "MANAGE_GUILD", | ||
| 16 | subcommands("dodaj_audio", "dodaj_wiadomość", "pokaż", "usuń", "pauza", "wznów",) | 15 | subcommands("dodaj_audio", "dodaj_wiadomość", "pokaż", "usuń", "pauza", "wznów",) |
| 17 | )] | 16 | )] |
| 18 | pub async fn cron(_ctx: Context<'_>) -> Result<()> { | 17 | pub async fn cron(_ctx: Context<'_>) -> Result<()> { |
| @@ -20,7 +19,7 @@ pub async fn cron(_ctx: Context<'_>) -> Result<()> { | |||
| 20 | } | 19 | } |
| 21 | 20 | ||
| 22 | /// Schedule audio playback | 21 | /// Schedule audio playback |
| 23 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 22 | #[poise::command(slash_command, guild_only)] |
| 24 | pub async fn dodaj_audio( | 23 | pub async fn dodaj_audio( |
| 25 | ctx: Context<'_>, | 24 | ctx: Context<'_>, |
| 26 | #[description = "Cron expression (e.g. \"37 21 * * *\" = every day at 21:37)"] cron_expression: String, | 25 | #[description = "Cron expression (e.g. \"37 21 * * *\" = every day at 21:37)"] cron_expression: String, |
| @@ -75,7 +74,7 @@ pub async fn dodaj_audio( | |||
| 75 | } | 74 | } |
| 76 | 75 | ||
| 77 | /// Schedule a periodic message | 76 | /// Schedule a periodic message |
| 78 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 77 | #[poise::command(slash_command, guild_only)] |
| 79 | pub async fn dodaj_wiadomość( | 78 | pub async fn dodaj_wiadomość( |
| 80 | ctx: Context<'_>, | 79 | ctx: Context<'_>, |
| 81 | #[description = "Cron expression (e.g. \"37 21 * * *\" = every day at 21:37)"] cron_expression: String, | 80 | #[description = "Cron expression (e.g. \"37 21 * * *\" = every day at 21:37)"] cron_expression: String, |
| @@ -109,7 +108,7 @@ pub async fn dodaj_wiadomość( | |||
| 109 | } | 108 | } |
| 110 | 109 | ||
| 111 | /// List all cron jobs | 110 | /// List all cron jobs |
| 112 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 111 | #[poise::command(slash_command, guild_only)] |
| 113 | pub async fn pokaż(ctx: Context<'_>) -> Result<()> { | 112 | pub async fn pokaż(ctx: Context<'_>) -> Result<()> { |
| 114 | let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; | 113 | let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; |
| 115 | let jobs = database::list_cron_jobs(&ctx.data().db_pool, guild_id.get() as i64).await?; | 114 | let jobs = database::list_cron_jobs(&ctx.data().db_pool, guild_id.get() as i64).await?; |
| @@ -144,7 +143,7 @@ pub async fn pokaż(ctx: Context<'_>) -> Result<()> { | |||
| 144 | } | 143 | } |
| 145 | 144 | ||
| 146 | /// Remove a cron job | 145 | /// Remove a cron job |
| 147 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 146 | #[poise::command(slash_command, guild_only)] |
| 148 | pub async fn usuń( | 147 | pub async fn usuń( |
| 149 | ctx: Context<'_>, | 148 | ctx: Context<'_>, |
| 150 | #[description = "ID pracy do usunięcia"] job_id: i64, | 149 | #[description = "ID pracy do usunięcia"] job_id: i64, |
| @@ -159,13 +158,15 @@ pub async fn usuń( | |||
| 159 | } | 158 | } |
| 160 | 159 | ||
| 161 | /// Pause a cron job | 160 | /// Pause a cron job |
| 162 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 161 | #[poise::command(slash_command, guild_only)] |
| 163 | pub async fn pauza( | 162 | pub async fn pauza( |
| 164 | ctx: Context<'_>, | 163 | ctx: Context<'_>, |
| 165 | #[description = "ID pracy do zapauzowania"] job_id: i64, | 164 | #[description = "ID pracy do zapauzowania"] job_id: i64, |
| 166 | ) -> Result<()> { | 165 | ) -> Result<()> { |
| 167 | let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; | 166 | let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; |
| 168 | if database::set_cron_job_active(&ctx.data().db_pool, job_id, guild_id.get() as i64, false).await? { | 167 | if database::set_cron_job_active(&ctx.data().db_pool, job_id, guild_id.get() as i64, false) |
| 168 | .await? | ||
| 169 | { | ||
| 169 | ctx.say(lang::fmt_cron_paused(job_id)).await?; | 170 | ctx.say(lang::fmt_cron_paused(job_id)).await?; |
| 170 | } else { | 171 | } else { |
| 171 | ctx.say(lang::fmt_cron_not_found(job_id)).await?; | 172 | ctx.say(lang::fmt_cron_not_found(job_id)).await?; |
| @@ -174,13 +175,15 @@ pub async fn pauza( | |||
| 174 | } | 175 | } |
| 175 | 176 | ||
| 176 | /// Resume a paused cron job | 177 | /// Resume a paused cron job |
| 177 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 178 | #[poise::command(slash_command, guild_only)] |
| 178 | pub async fn wznów( | 179 | pub async fn wznów( |
| 179 | ctx: Context<'_>, | 180 | ctx: Context<'_>, |
| 180 | #[description = "ID pracy cron do wznowienia"] job_id: i64, | 181 | #[description = "ID pracy cron do wznowienia"] job_id: i64, |
| 181 | ) -> Result<()> { | 182 | ) -> Result<()> { |
| 182 | let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; | 183 | let guild_id = ctx.guild_id().ok_or_else(lang::err_not_in_guild)?; |
| 183 | if database::set_cron_job_active(&ctx.data().db_pool, job_id, guild_id.get() as i64, true).await? { | 184 | if database::set_cron_job_active(&ctx.data().db_pool, job_id, guild_id.get() as i64, true) |
| 185 | .await? | ||
| 186 | { | ||
| 184 | ctx.say(lang::fmt_cron_resumed(job_id)).await?; | 187 | ctx.say(lang::fmt_cron_resumed(job_id)).await?; |
| 185 | } else { | 188 | } else { |
| 186 | ctx.say(lang::fmt_cron_not_found(job_id)).await?; | 189 | ctx.say(lang::fmt_cron_not_found(job_id)).await?; |
diff --git a/src/commands/record.rs b/src/commands/record.rs index dfe736d..c5cecd3 100644 --- a/src/commands/record.rs +++ b/src/commands/record.rs | |||
| @@ -13,7 +13,7 @@ type Context<'a> = poise::Context<'a, crate::Data, crate::error::Error>; | |||
| 13 | /// connected to a voice channel, if `RECORDING_ENABLED=true`. This command is a | 13 | /// connected to a voice channel, if `RECORDING_ENABLED=true`. This command is a |
| 14 | /// read-only snapshot — the buffer keeps recording afterwards. Only callable by | 14 | /// read-only snapshot — the buffer keeps recording afterwards. Only callable by |
| 15 | /// someone in the same voice channel as the bot. | 15 | /// someone in the same voice channel as the bot. |
| 16 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 16 | #[poise::command(slash_command, guild_only)] |
| 17 | pub async fn record(ctx: Context<'_>) -> Result<()> { | 17 | pub async fn record(ctx: Context<'_>) -> Result<()> { |
| 18 | ctx.defer().await?; | 18 | ctx.defer().await?; |
| 19 | let data = ctx.data(); | 19 | let data = ctx.data(); |
diff --git a/src/commands/trigger.rs b/src/commands/trigger.rs index 5d481e4..01fda18 100644 --- a/src/commands/trigger.rs +++ b/src/commands/trigger.rs | |||
| @@ -20,7 +20,7 @@ pub async fn trigger(_ctx: Context<'_>) -> Result<()> { | |||
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | /// Create a new trigger group | 22 | /// Create a new trigger group |
| 23 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 23 | #[poise::command(slash_command, guild_only)] |
| 24 | pub async fn create( | 24 | pub async fn create( |
| 25 | ctx: Context<'_>, | 25 | ctx: Context<'_>, |
| 26 | #[description = "Logical group name"] | 26 | #[description = "Logical group name"] |
| @@ -41,7 +41,7 @@ pub async fn create( | |||
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | /// Add keywords and/or responses to a trigger group | 43 | /// Add keywords and/or responses to a trigger group |
| 44 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 44 | #[poise::command(slash_command, guild_only)] |
| 45 | pub async fn add( | 45 | pub async fn add( |
| 46 | ctx: Context<'_>, | 46 | ctx: Context<'_>, |
| 47 | #[description = "Existing group name"] | 47 | #[description = "Existing group name"] |
| @@ -83,7 +83,7 @@ pub async fn add( | |||
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | /// Remove keyword or response from a group | 85 | /// Remove keyword or response from a group |
| 86 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 86 | #[poise::command(slash_command, guild_only)] |
| 87 | pub async fn remove( | 87 | pub async fn remove( |
| 88 | ctx: Context<'_>, | 88 | ctx: Context<'_>, |
| 89 | #[description = "Group name"] | 89 | #[description = "Group name"] |
| @@ -115,7 +115,7 @@ pub async fn remove( | |||
| 115 | } | 115 | } |
| 116 | 116 | ||
| 117 | /// Delete an entire trigger group | 117 | /// Delete an entire trigger group |
| 118 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 118 | #[poise::command(slash_command, guild_only)] |
| 119 | pub async fn delete( | 119 | pub async fn delete( |
| 120 | ctx: Context<'_>, | 120 | ctx: Context<'_>, |
| 121 | #[description = "Group name"] | 121 | #[description = "Group name"] |
| @@ -134,7 +134,7 @@ pub async fn delete( | |||
| 134 | } | 134 | } |
| 135 | 135 | ||
| 136 | /// List keywords and responses for a group | 136 | /// List keywords and responses for a group |
| 137 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 137 | #[poise::command(slash_command, guild_only)] |
| 138 | pub async fn list( | 138 | pub async fn list( |
| 139 | ctx: Context<'_>, | 139 | ctx: Context<'_>, |
| 140 | #[description = "Group name"] | 140 | #[description = "Group name"] |
| @@ -185,7 +185,7 @@ pub async fn list( | |||
| 185 | } | 185 | } |
| 186 | 186 | ||
| 187 | /// Display trigger command usage info | 187 | /// Display trigger command usage info |
| 188 | #[poise::command(slash_command, guild_only, required_permissions = "MANAGE_GUILD")] | 188 | #[poise::command(slash_command, guild_only)] |
| 189 | pub async fn help(ctx: Context<'_>) -> Result<()> { | 189 | pub async fn help(ctx: Context<'_>) -> Result<()> { |
| 190 | let help = [ | 190 | let help = [ |
| 191 | "**Trigger Commands**", | 191 | "**Trigger Commands**", |
diff --git a/src/commands/utilities.rs b/src/commands/utilities.rs index f43b30e..f585d83 100644 --- a/src/commands/utilities.rs +++ b/src/commands/utilities.rs | |||
| @@ -4,11 +4,11 @@ use poise::serenity_prelude as serenity; | |||
| 4 | use tracing::warn; | 4 | use tracing::warn; |
| 5 | 5 | ||
| 6 | use super::autocomplete::any_file; | 6 | use super::autocomplete::any_file; |
| 7 | use crate::music::sanitize_yt_url; | ||
| 8 | use crate::Data; | 7 | use crate::Data; |
| 9 | use crate::copyparty::{html_decode, percent_decode, redact_url}; | 8 | use crate::copyparty::{html_decode, percent_decode, redact_url}; |
| 10 | use crate::error::{Error, Result}; | 9 | use crate::error::{Error, Result}; |
| 11 | use crate::lang; | 10 | use crate::lang; |
| 11 | use crate::music::sanitize_yt_url; | ||
| 12 | 12 | ||
| 13 | type Context<'a> = poise::Context<'a, Data, Error>; | 13 | type Context<'a> = poise::Context<'a, Data, Error>; |
| 14 | 14 | ||
| @@ -90,19 +90,12 @@ fn safe_basename(name: &str) -> String { | |||
| 90 | /// Create a unique per-operation directory and return | 90 | /// Create a unique per-operation directory and return |
| 91 | /// `(directory, input_path, output_path)`. Concurrent commands can never | 91 | /// `(directory, input_path, output_path)`. Concurrent commands can never |
| 92 | /// overwrite or delete each other's files. | 92 | /// overwrite or delete each other's files. |
| 93 | async fn temp_op( | 93 | async fn temp_op(original: &str, output_name: &str) -> std::io::Result<(String, String, String)> { |
| 94 | original: &str, | ||
| 95 | output_name: &str, | ||
| 96 | ) -> std::io::Result<(String, String, String)> { | ||
| 97 | let safe = safe_basename(original); | 94 | let safe = safe_basename(original); |
| 98 | let ext = ext_of(&safe) | 95 | let ext = ext_of(&safe) |
| 99 | .map(|ext| format!(".{ext}")) | 96 | .map(|ext| format!(".{ext}")) |
| 100 | .unwrap_or_default(); | 97 | .unwrap_or_default(); |
| 101 | let dir = format!( | 98 | let dir = format!("{}/op_{}", crate::config::temp_path(), uuid::Uuid::new_v4()); |
| 102 | "{}/op_{}", | ||
| 103 | crate::config::temp_path(), | ||
| 104 | uuid::Uuid::new_v4() | ||
| 105 | ); | ||
| 106 | tokio::fs::create_dir_all(&dir).await?; | 99 | tokio::fs::create_dir_all(&dir).await?; |
| 107 | let input_path = format!("{dir}/input{ext}"); | 100 | let input_path = format!("{dir}/input{ext}"); |
| 108 | let output_path = format!("{dir}/{}", safe_basename(output_name)); | 101 | let output_path = format!("{dir}/{}", safe_basename(output_name)); |
| @@ -303,7 +296,10 @@ async fn compress_attachment_bytes( | |||
| 303 | /// Compress audio bytes for playback using Vorbis (symphonia-decodable). | 296 | /// Compress audio bytes for playback using Vorbis (symphonia-decodable). |
| 304 | /// Uses Vorbis instead of Opus because symphonia 0.6 lacks an Opus decoder. | 297 | /// Uses Vorbis instead of Opus because symphonia 0.6 lacks an Opus decoder. |
| 305 | /// Returns the compressed .ogg bytes on success. | 298 | /// Returns the compressed .ogg bytes on success. |
| 306 | pub(crate) async fn compress_audio_bytes(input_bytes: &[u8], original_name: &str) -> Option<Vec<u8>> { | 299 | pub(crate) async fn compress_audio_bytes( |
| 300 | input_bytes: &[u8], | ||
| 301 | original_name: &str, | ||
| 302 | ) -> Option<Vec<u8>> { | ||
| 307 | let safe_name = safe_basename(original_name); | 303 | let safe_name = safe_basename(original_name); |
| 308 | let stem = stem_of(&safe_name).to_string(); | 304 | let stem = stem_of(&safe_name).to_string(); |
| 309 | let out_filename = format!("compressed_{stem}.ogg"); | 305 | let out_filename = format!("compressed_{stem}.ogg"); |
| @@ -378,9 +374,28 @@ pub(crate) fn extract_urls(content: &str) -> Vec<String> { | |||
| 378 | b.is_ascii_alphanumeric() | 374 | b.is_ascii_alphanumeric() |
| 379 | || matches!( | 375 | || matches!( |
| 380 | b, | 376 | b, |
| 381 | b'-' | b'.' | b'_' | b'~' | b':' | b'/' | b'?' | b'#' | b'[' | b']' | b'@' | 377 | b'-' | b'.' |
| 382 | | b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | 378 | | b'_' |
| 383 | | b'%' | b'=' | 379 | | b'~' |
| 380 | | b':' | ||
| 381 | | b'/' | ||
| 382 | | b'?' | ||
| 383 | | b'#' | ||
| 384 | | b'[' | ||
| 385 | | b']' | ||
| 386 | | b'@' | ||
| 387 | | b'!' | ||
| 388 | | b'$' | ||
| 389 | | b'&' | ||
| 390 | | b'\'' | ||
| 391 | | b'(' | ||
| 392 | | b')' | ||
| 393 | | b'*' | ||
| 394 | | b'+' | ||
| 395 | | b',' | ||
| 396 | | b';' | ||
| 397 | | b'%' | ||
| 398 | | b'=' | ||
| 384 | ) | 399 | ) |
| 385 | } | 400 | } |
| 386 | let bytes = content.as_bytes(); | 401 | let bytes = content.as_bytes(); |
| @@ -445,9 +460,10 @@ async fn find_downloaded_file(dir: &str) -> Option<(String, String)> { | |||
| 445 | Err(_) => continue, | 460 | Err(_) => continue, |
| 446 | }; | 461 | }; |
| 447 | if let Some((t, _)) = &newest | 462 | if let Some((t, _)) = &newest |
| 448 | && *t >= mtime { | 463 | && *t >= mtime |
| 449 | continue; | 464 | { |
| 450 | } | 465 | continue; |
| 466 | } | ||
| 451 | newest = Some((mtime, entry.path().to_string_lossy().into_owned())); | 467 | newest = Some((mtime, entry.path().to_string_lossy().into_owned())); |
| 452 | } | 468 | } |
| 453 | newest.map(|(_, p)| { | 469 | newest.map(|(_, p)| { |
| @@ -540,8 +556,10 @@ async fn run_download( | |||
| 540 | Ok(result) => result.map_err(|e| Error::Custom(lang::fmt_ytdlp_err(&e)))?, | 556 | Ok(result) => result.map_err(|e| Error::Custom(lang::fmt_ytdlp_err(&e)))?, |
| 541 | Err(_) => { | 557 | Err(_) => { |
| 542 | let _ = tokio::fs::remove_dir_all(&dl_dir).await; | 558 | let _ = tokio::fs::remove_dir_all(&dl_dir).await; |
| 543 | ctx.say(lang::fmt_download_failed("download timed out after 10 minutes")) | 559 | ctx.say(lang::fmt_download_failed( |
| 544 | .await?; | 560 | "download timed out after 10 minutes", |
| 561 | )) | ||
| 562 | .await?; | ||
| 545 | return Ok(()); | 563 | return Ok(()); |
| 546 | } | 564 | } |
| 547 | }; | 565 | }; |
| @@ -1043,7 +1061,7 @@ pub async fn message_download(ctx: Context<'_>, message: serenity::Message) -> R | |||
| 1043 | } | 1061 | } |
| 1044 | 1062 | ||
| 1045 | /// Echo text to channel | 1063 | /// Echo text to channel |
| 1046 | #[poise::command(slash_command, required_permissions = "MANAGE_MESSAGES")] | 1064 | #[poise::command(slash_command)] |
| 1047 | pub async fn say(ctx: Context<'_>, #[description = "Message to send"] text: String) -> Result<()> { | 1065 | pub async fn say(ctx: Context<'_>, #[description = "Message to send"] text: String) -> Result<()> { |
| 1048 | // Acknowledge the interaction with an invisible ephemeral reply, delete it, | 1066 | // Acknowledge the interaction with an invisible ephemeral reply, delete it, |
| 1049 | // then post the actual text as a normal channel message (not a reply to the | 1067 | // then post the actual text as a normal channel message (not a reply to the |
| @@ -1059,9 +1077,7 @@ pub async fn say(ctx: Context<'_>, #[description = "Message to send"] text: Stri | |||
| 1059 | warn!("say: failed to delete ephemeral acknowledgement: {e}"); | 1077 | warn!("say: failed to delete ephemeral acknowledgement: {e}"); |
| 1060 | } | 1078 | } |
| 1061 | 1079 | ||
| 1062 | ctx.channel_id() | 1080 | ctx.channel_id().say(ctx.serenity_context(), &text).await?; |
| 1063 | .say(ctx.serenity_context(), &text) | ||
| 1064 | .await?; | ||
| 1065 | Ok(()) | 1081 | Ok(()) |
| 1066 | } | 1082 | } |
| 1067 | 1083 | ||
