From 21d637c5b466801e6c65e63545b8348a12bc95b8 Mon Sep 17 00:00:00 2001 From: DrewSC13 Date: Wed, 3 Jun 2026 12:42:11 -0400 Subject: [PATCH 1/2] feat(api): add showcase endpoint --- .env.example | 4 +-- doc/VARIABLES.md | 5 +-- src/api/mod.rs | 1 + src/api/routes/mod.rs | 4 +++ src/api/routes/showcase.rs | 68 ++++++++++++++++++++++++++++++++++++++ src/secrets.rs | 6 ++++ 6 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/api/routes/showcase.rs diff --git a/.env.example b/.env.example index 488c552..e74f904 100644 --- a/.env.example +++ b/.env.example @@ -4,9 +4,9 @@ GUILD_ID = "Server ID" # Config # Channel Ids -CHANNEL_DAILY = "Channel id for Daily Challenges" CHANNEL_DAILY = "1219703076944871616" CHANNEL_SUGGEST = "824695624665923594" +CHANNEL_SHOWCASE = "Channel id for Showcase" TEMPORAL_WAIT = "" TEMPORAL_CATEGORY = "" TEMPORAL_LOGS = "" @@ -17,4 +17,4 @@ BOT_API_ADDR = 0.0.0.0 BOT_APIKEY = "PermitidoHacerCosas123" # AI features (remove to disable) -GEMINI_KEY = " " +GEMINI_KEY = " " \ No newline at end of file diff --git a/doc/VARIABLES.md b/doc/VARIABLES.md index 8c1aab0..06f5a95 100644 --- a/doc/VARIABLES.md +++ b/doc/VARIABLES.md @@ -9,7 +9,7 @@ Para ejecutar el bot, crea un archivo `.env` con las siguientes variables: - `DISCORD_TOKEN`: Obtenlo desde el [Token del bot](https://discord.com/developers/applications) - `GUILD_ID`: Activa el Modo Desarrollador en Discord, haz clic derecho en tu servidor y selecciona 'Copiar ID' - `BOT_APIKEY`: Autorizacion para canal cifrado entre el bot y el servidor (Puede contener cualquier texto) -- `CHANNEL_DAILY` & `CHANNEL_SUGGEST`: Haz clic derecho en el canal de Discord y selecciona 'Copiar ID' +- `CHANNEL_DAILY`, `CHANNEL_SUGGEST` & `CHANNEL_SHOWCASE`: Haz clic derecho en el canal de Discord y selecciona 'Copiar ID' - `LAVALINK_PASSWORD`: Contraseña Lavalink **Opcionales** @@ -24,4 +24,5 @@ GUILD_ID = "Server ID" BOT_APIKEY = "API key for secure communication" CHANNEL_DAILY = "Channel ID for daily challenges" CHANNEL_SUGGEST = "Channel ID for suggestions" -``` +CHANNEL_SHOWCASE = "Channel ID for showcase forum" +``` \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index ffa2fb4..ae31d96 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -21,6 +21,7 @@ pub fn build_router(secrets: &CangrebotSecrets, ctx: Arc) -> Router { axum::routing::post(routes::daily_challenge), ) .route("/send_message", axum::routing::post(routes::send_message)) + .route("/showcase", axum::routing::post(routes::showcase)) .layer(axum::middleware::from_fn_with_state( secrets.clone(), auth::middleware, diff --git a/src/api/routes/mod.rs b/src/api/routes/mod.rs index 5521e50..55eb37e 100644 --- a/src/api/routes/mod.rs +++ b/src/api/routes/mod.rs @@ -2,9 +2,13 @@ mod daily_challenge; use axum::http::StatusCode; use axum::response::IntoResponse; pub use daily_challenge::daily_challenge; + mod send_message; pub use send_message::send_message; +mod showcase; +pub use showcase::showcase; + pub mod send_stats; pub async fn healthcheck() -> impl IntoResponse { diff --git a/src/api/routes/showcase.rs b/src/api/routes/showcase.rs new file mode 100644 index 0000000..be84380 --- /dev/null +++ b/src/api/routes/showcase.rs @@ -0,0 +1,68 @@ +use axum::extract::State; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::Json; +use serde::{Deserialize, Serialize}; +use tracing::info; + +use crate::api::RouteState; +use crate::serenity::builder::{CreateForumPost, CreateMessage}; +use crate::serenity::model::prelude::ChannelId; + +#[derive(Deserialize, Serialize)] +pub struct ShowcaseRequest { + name: String, + desc: String, + #[serde(default)] + tags: Vec, + url: String, +} + +pub async fn showcase( + State((secrets, ctx)): State, + Json(ShowcaseRequest { + name, + desc, + tags, + url, + }): Json, +) -> impl IntoResponse { + info!("Running showcase creation from API"); + + let msg_channel = ChannelId::new(secrets.channel_showcase); + + let Ok(channel) = msg_channel.to_channel(&ctx).await else { + return ( + StatusCode::INTERNAL_SERVER_ERROR, + "Cannot convert to channel".to_string(), + ); + }; + + let Some(forum) = channel.guild() else { + return (StatusCode::NOT_FOUND, "Guild channel not found".to_string()); + }; + + let mut forum_post = CreateForumPost::new( + name, + CreateMessage::new().content(format!("{desc}\n\n{url}")), + ); + + for tag_name in tags { + let Some(tag) = forum.available_tags.iter().find(|tag| tag.name == tag_name) else { + return (StatusCode::NOT_FOUND, format!("Tag '{tag_name}' not found")); + }; + + forum_post = forum_post.add_applied_tag(tag.id); + } + + match msg_channel.create_forum_post(&ctx, forum_post).await { + Ok(_) => (StatusCode::OK, "Ok".to_string()), + Err(err) => { + tracing::error!("Cannot create showcase forum post: {err:?}"); + ( + StatusCode::INTERNAL_SERVER_ERROR, + "Cannot create showcase forum post".to_string(), + ) + } + } +} diff --git a/src/secrets.rs b/src/secrets.rs index ba06a15..a89210f 100644 --- a/src/secrets.rs +++ b/src/secrets.rs @@ -9,6 +9,8 @@ pub struct CangrebotSecrets { pub channel_daily: u64, /// Channel id for Suggest pub channel_suggest: u64, + /// Channel id for Showcase + pub channel_showcase: u64, /// Waiting channel id for temporal voice chats pub temporal_wait: u64, /// Category id for temporal voice chats @@ -41,6 +43,10 @@ impl CangrebotSecrets { .expect("'CHANNEL_SUGGEST' was not found") .parse() .expect("Cannot parse 'CHANNEL_SUGGEST'"), + channel_showcase: secrets("CHANNEL_SHOWCASE") + .expect("'CHANNEL_SHOWCASE' was not found") + .parse() + .expect("Cannot parse 'CHANNEL_SHOWCASE'"), temporal_wait: secrets("TEMPORAL_WAIT") .expect("'TEMPORAL_WAIT' was not found") .parse() From 8baa35dace11b22b57c62f9245992377645349f5 Mon Sep 17 00:00:00 2001 From: DrewSC13 Date: Wed, 3 Jun 2026 12:52:05 -0400 Subject: [PATCH 2/2] style: format tts regex tests --- src/bot/commands/tts/tts_regex.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/bot/commands/tts/tts_regex.rs b/src/bot/commands/tts/tts_regex.rs index e1589f8..5507d23 100644 --- a/src/bot/commands/tts/tts_regex.rs +++ b/src/bot/commands/tts/tts_regex.rs @@ -11,20 +11,18 @@ pub const MULTI_LINE_DOUBLE_CODE_BLOCK_REGEX: &str = r"``([^`\n]+)``"; pub const MULTI_LINE_TRIPLE_CODE_BLOCK_REGEX: &str = r"```([\s\S]*?)```"; pub const CORRECTION_REGEX: &str = r"^\w+\*$"; - #[cfg(test)] mod tests { use super::*; use regex::Regex; - fn aux_match(regex: &str,haystack: &str) -> bool { + fn aux_match(regex: &str, haystack: &str) -> bool { match Regex::new(regex) { Ok(re) => re.is_match(haystack), Err(_) => false, } } - #[test] fn regex_definition() { assert!(Regex::new(LINK_REGEX).is_ok()); @@ -52,13 +50,13 @@ mod tests { #[test] fn links_not_matchs() { - let cases = [ + let cases = [ "ftp://b32.i2p/", "https://grüße.tld", - "https://example.موقع" + "https://example.موقع", ]; for case in cases { - assert!(! aux_match(LINK_REGEX, case)); + assert!(!aux_match(LINK_REGEX, case)); } } }