From 66163b765a82974490d552b322f7ebab512cd2ec Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Wed, 15 Jan 2025 13:44:09 -0500 Subject: [PATCH 01/21] My awesome changes --- .env.example | 3 + src/endpoints/mod_versions.rs | 58 +++++- src/endpoints/mods.rs | 30 ++- src/forum.rs | 359 ++++++++++++++++++++++++++++++++++ src/main.rs | 73 ++++++- 5 files changed, 515 insertions(+), 8 deletions(-) create mode 100644 src/forum.rs diff --git a/.env.example b/.env.example index ed6fd0b..4a54d27 100644 --- a/.env.example +++ b/.env.example @@ -13,6 +13,9 @@ GITHUB_CLIENT_SECRET= # Discord DISCORD_WEBHOOK_URL= +DISCORD_BOT_TOKEN= +DISCORD_GUILD_ID= +DISCORD_CHANNEL_ID= # Config diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 23f5a2d..db279f2 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -5,8 +5,7 @@ use serde::Deserialize; use sqlx::{types::ipnetwork::IpNetwork, Acquire}; use crate::{ - extractors::auth::Auth, - types::{ + extractors::auth::Auth, forum::create_or_update_thread, types::{ api::{ApiError, ApiResponse}, mod_json::{split_version_and_compare, ModJson}, models::{ @@ -17,9 +16,7 @@ use crate::{ mod_version::{self, ModVersion}, mod_version_status::ModVersionStatusEnum, }, - }, - webhook::send_webhook, - AppData, + }, webhook::send_webhook, AppData }; #[derive(Deserialize)] @@ -324,6 +321,28 @@ pub async fn create_version( .commit() .await .or(Err(ApiError::TransactionError))?; + + if !dev.verified { + tokio::spawn(async move { + let m = fetched_mod.unwrap(); + let v_res = ModVersion::get_one(&path.id, &json.version, true, false, &mut pool).await; + if v_res.is_err() { + return; + } + let v = v_res.unwrap(); + create_or_update_thread( + None, + data.guild_id, + data.channel_id, + data.bot_token.clone(), + m, + v, + None, + data.app_url.clone(), + ).await; + }); + } + Ok(HttpResponse::NoContent()) } @@ -409,5 +428,34 @@ pub async fn update_version( .await; } + if payload.status == ModVersionStatusEnum::Accepted || payload.status == ModVersionStatusEnum::Rejected { + tokio::spawn(async move { + let m_res_res = Mod::get_one(&path.id, false, &mut pool).await; + if m_res_res.is_err() { + return; + } + let m_res = m_res_res.unwrap(); + if m_res.is_none() { + return; + } + let m = m_res.unwrap(); + let v_res = ModVersion::get_one(&path.id, &path.version, true, false, &mut pool).await; + if v_res.is_err() { + return; + } + let v = v_res.unwrap(); + create_or_update_thread( + None, + data.guild_id, + data.channel_id, + data.bot_token.clone(), + m, + v, + Some(dev.clone()), + data.app_url.clone(), + ).await; + }); + } + Ok(HttpResponse::NoContent()) } diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 66ebec0..597e385 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -2,6 +2,7 @@ use actix_web::{get, post, put, web, HttpResponse, Responder}; use serde::Deserialize; use sqlx::Acquire; +use crate::forum::create_or_update_thread; use crate::extractors::auth::Auth; use crate::types::api::{create_download_link, ApiError, ApiResponse}; use crate::types::mod_json::ModJson; @@ -9,8 +10,8 @@ use crate::types::models::developer::Developer; use crate::types::models::incompatibility::Incompatibility; use crate::types::models::mod_entity::{download_geode_file, Mod, ModUpdate}; use crate::types::models::mod_gd_version::{GDVersionEnum, VerPlatform}; +use crate::types::models::mod_version::ModVersion; use crate::types::models::mod_version_status::ModVersionStatusEnum; -use crate::webhook::send_webhook; use crate::AppData; #[derive(Deserialize, Default)] @@ -134,6 +135,33 @@ pub async fn create( .await .or(Err(ApiError::TransactionError))?; + tokio::spawn(async move { + let m_res_res = Mod::get_one(&json.id, false, &mut pool).await; + if m_res_res.is_err() { + return; + } + let m_res = m_res_res.unwrap(); + if m_res.is_none() { + return; + } + let m = m_res.unwrap(); + let v_res = ModVersion::get_one(&json.id, &json.version, true, false, &mut pool).await; + if v_res.is_err() { + return; + } + let v = v_res.unwrap(); + create_or_update_thread( + None, + data.guild_id, + data.channel_id, + data.bot_token.clone(), + m, + v, + None, + data.app_url.clone(), + ).await; + }); + Ok(HttpResponse::NoContent()) } diff --git a/src/forum.rs b/src/forum.rs new file mode 100644 index 0000000..2042c97 --- /dev/null +++ b/src/forum.rs @@ -0,0 +1,359 @@ +use serde_json::{json, Value}; + +use crate::types::models::dependency::DependencyImportance; +use crate::types::models::developer::FetchedDeveloper; +use crate::types::models::incompatibility::IncompatibilityImportance; +use crate::types::models::mod_entity::Mod; +use crate::types::models::mod_gd_version::GDVersionEnum; +use crate::types::models::mod_version::ModVersion; +use crate::types::models::mod_version_status::ModVersionStatusEnum; + +fn gd_to_string(gd: Option) -> String { + match gd { + Some(GDVersionEnum::All) => "*", + Some(GDVersionEnum::GD2113) => "2.113", + Some(GDVersionEnum::GD2200) => "2.200", + Some(GDVersionEnum::GD2204) => "2.204", + Some(GDVersionEnum::GD2205) => "2.205", + Some(GDVersionEnum::GD2206) => "2.206", + Some(GDVersionEnum::GD2207) => "2.207", + Some(GDVersionEnum::GD22071) => "2.2071", + Some(GDVersionEnum::GD22072) => "2.2072", + Some(GDVersionEnum::GD22073) => "2.2073", + Some(GDVersionEnum::GD22074) => "2.2074", + None => "N/A", + }.to_string() +} + +fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { + let deps = v.dependencies.unwrap_or_default().into_iter().map(|d| { + format!("`{} {} ({})`", d.mod_id, d.version, match d.importance { + DependencyImportance::Required => "required", + DependencyImportance::Recommended => "recommended", + DependencyImportance::Suggested => "suggested", + }) + }).collect::>().join("\n"); + let incompats = v.incompatibilities.unwrap_or_default().into_iter().map(|i| { + format!("`{} {} ({})`", i.mod_id, i.version, match i.importance { + IncompatibilityImportance::Breaking => "breaking", + IncompatibilityImportance::Conflicting => "conflicting", + IncompatibilityImportance::Superseded => "superseded", + }) + }).collect::>().join("\n"); + let tags = m.tags.into_iter().map(|t| format!("`{}`", t)).collect::>().join(", "); + json!({ + "title": format!("{}{}", if m.featured { + "⭐️ " + } else { + "" + }, v.name), + "description": v.description, + "url": format!("https://geode-sdk.org/mods/{}?version={}", m.id, v.version), + "thumbnail": { + "url": format!("{}/v1/mods/{}/logo", base_url, m.id) + }, + "fields": [ + { + "name": "ID", + "value": m.id, + "inline": true + }, + { + "name": "Version", + "value": v.version, + "inline": true + }, + { + "name": "Geode", + "value": v.geode, + "inline": true + }, + { + "name": "Early Load", + "value": v.early_load, + "inline": true + }, + { + "name": "API", + "value": v.api, + "inline": true + }, + { + "name": "Developers", + "value": m.developers.into_iter().map(|d| { + if d.is_owner { + format!("**[{}](https://geode-sdk.org/mods?developer={})**", d.display_name, d.username) + } else { + format!("[{}](https://geode-sdk.org/mods?developer={})", d.display_name, d.username) + } + }).collect::>().join(", "), + "inline": true + }, + { + "name": "Geometry Dash", + "value": format!("Windows: {}\nAndroid (32-bit): {}\nAndroid (64-bit): {}\nmacOS (Intel): {}\nmacOS (ARM): {}", + gd_to_string(v.gd.win), gd_to_string(v.gd.android32), gd_to_string(v.gd.android64), + gd_to_string(v.gd.mac_intel), gd_to_string(v.gd.mac_arm)), + "inline": false + }, + { + "name": "Dependencies", + "value": if !deps.is_empty() { + deps + } else { + "None".to_string() + }, + "inline": false + }, + { + "name": "Incompatibilities", + "value": if !incompats.is_empty() { + incompats + } else { + "None".to_string() + }, + "inline": false + }, + { + "name": "Source", + "value": if m.links.is_some() { + m.links.clone().unwrap().source.unwrap_or("N/A".to_string()) + } else { + m.repository.unwrap_or("N/A".to_string()) + }, + "inline": true + }, + { + "name": "Community", + "value": if m.links.is_some() { + m.links.clone().unwrap().community.unwrap_or("N/A".to_string()) + } else { + "N/A".to_string() + }, + "inline": true + }, + { + "name": "Homepage", + "value": if m.links.is_some() { + m.links.clone().unwrap().homepage.unwrap_or("N/A".to_string()) + } else { + "N/A".to_string() + }, + "inline": true + }, + { + "name": "Hash", + "value": v.hash, + "inline": true + }, + { + "name": "Download", + "value": v.download_link, + "inline": true + }, + { + "name": "Tags", + "value": if !tags.is_empty() { + tags + } else { + "None".to_string() + }, + "inline": true + } + ] + }) +} + +pub async fn get_threads( + guild_id: u64, channel_id: u64, + token: String +) -> Vec { + let client = reqwest::Client::new(); + let res = client + .get(format!("https://discord.com/api/v10/guilds/{}/threads/active", guild_id)) + .header("Authorization", format!("Bot {}", token)) + .send() + .await; + if res.is_err() { + return vec![]; + } + let res = res.unwrap(); + if !res.status().is_success() { + return vec![]; + } + let res = res.json::().await; + if res.is_err() { + return vec![]; + } + let res = res.unwrap()["threads"].clone(); + if !res.is_array() { + return vec![]; + } + + let vec1 = res.as_array().unwrap().clone(); + + let res2 = client + .get(format!("https://discord.com/api/v10/channels/{}/threads/archived/public", channel_id)) + .header("Authorization", format!("Bot {}", token)) + .send() + .await; + if res2.is_err() { + return vec1; + } + let res2 = res2.unwrap(); + if !res2.status().is_success() { + return vec1; + } + let res2 = res2.json::().await; + if res2.is_err() { + return vec1; + } + let res2 = res2.unwrap()["threads"].clone(); + if !res2.is_array() { + return vec1; + } + + let vec2 = res2.as_array().unwrap().clone(); + + [vec1, vec2].concat().into_iter() + .filter(|t| t["thread_metadata"]["locked"].is_boolean() && !t["thread_metadata"]["locked"].as_bool().unwrap()) + .collect::>() +} + +pub async fn create_or_update_thread( + threads: Option>, + guild_id: u64, channel_id: u64, + token: String, + m: Mod, + v: ModVersion, + admin: Option, + base_url: String +) { + let thread_vec = if threads.is_some() { + threads.unwrap() + } else { + get_threads(guild_id, channel_id, token.clone()).await + }; + + let thread = thread_vec.iter().find(|t| { + t["name"].as_str().unwrap_or("").contains(format!("({})", m.id).as_str()) + }); + + let client = reqwest::Client::new(); + if thread.is_none() { + if v.status != ModVersionStatusEnum::Pending { + return; + } + + let _ = client + .post(format!("https://discord.com/api/v10/channels/{}/threads", channel_id)) + .header("Authorization", format!("Bot {}", token)) + .json(&json!({ + "name": format!("🕓 {} ({}) {}", v.name, m.id, v.version), + "message": { + "embeds": [mod_embed(m, v, base_url)], + "components": [ + { + "type": 1, + "components": [ + { + "type": 2, + "style": 3, + "label": "Accept", + "emoji": { + "id": Value::Null, + "name": "✅" + }, + "custom_id": "index/admin/accept:forum" + }, + { + "type": 2, + "style": 4, + "label": "Reject", + "emoji": { + "id": Value::Null, + "name": "❌" + }, + "custom_id": "index-admin/reject:forum" + } + ] + } + ] + } + })) + .send() + .await; + return; + } + + let thread_id = thread.unwrap()["id"].as_str().unwrap_or(""); + if thread_id.is_empty() { + return; + } + + if thread.unwrap()["name"].as_str().unwrap_or("").ends_with(format!("{} ({}) {}", v.name, m.id, v.version).as_str()) { + if v.status == ModVersionStatusEnum::Pending { + return; + } + + let _ = client + .post(format!("https://discord.com/api/v10/channels/{}/messages", thread_id)) + .header("Authorization", format!("Bot {}", token)) + .json(&json!({ + "content": format!("{}{}{}", match v.status { + ModVersionStatusEnum::Accepted => "Accepted", + ModVersionStatusEnum::Rejected => "Rejected", + _ => "", + }, if admin.is_some() { + format!(" by {}", admin.unwrap().display_name) + } else { + "".to_string() + }, if v.info.is_some() { + format!(": `{}`", v.info.clone().unwrap()) + } else { + "".to_string() + }), + "message_reference": { + "message_id": thread_id, + "fail_if_not_exists": false + } + })) + .send() + .await; + + let _ = client + .patch(format!("https://discord.com/api/v10/channels/{}", thread_id)) + .header("Authorization", format!("Bot {}", token)) + .json(&json!({ + "name": match v.status { + ModVersionStatusEnum::Accepted => format!("✅ {} ({}) {}", v.name, m.id, v.version), + ModVersionStatusEnum::Rejected => format!("❌ {} ({}) {}", v.name, m.id, v.version), + _ => format!("🕓 {} ({}) {}", v.name, m.id, v.version), + }, + "locked": true, + "archived": true + })) + .send() + .await; + + return; + } + + let _ = client + .patch(format!("https://discord.com/api/v10/channels/{}", thread_id)) + .header("Authorization", format!("Bot {}", token)) + .json(&json!({ + "name": format!("🕓 {} ({}) {}", v.name, m.id, v.version) + })) + .send() + .await; + + let _ = client + .patch(format!("https://discord.com/api/v10/channels/{}/messages/{}", thread_id, thread_id)) + .header("Authorization", format!("Bot {}", token)) + .json(&json!({ + "embeds": [mod_embed(m, v, base_url)] + })) + .send() + .await; +} diff --git a/src/main.rs b/src/main.rs index 3655732..ac68878 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,11 @@ use actix_web::{ App, HttpServer, Responder, }; use clap::Parser; +use endpoints::mods::{IndexQueryParams, IndexSortType}; use env_logger::Env; +use forum::{create_or_update_thread, get_threads}; use log::info; +use types::models::{mod_entity::Mod, mod_version::ModVersion, mod_version_status::ModVersionStatusEnum}; use crate::types::api; use crate::types::api::ApiError; @@ -17,6 +20,7 @@ mod endpoints; mod extractors; mod jobs; mod types; +mod forum; mod webhook; #[derive(Clone)] @@ -26,6 +30,9 @@ pub struct AppData { github_client_id: String, github_client_secret: String, webhook_url: String, + bot_token: String, + guild_id: u64, + channel_id: u64, disable_downloads: bool, max_download_mb: u32, } @@ -67,8 +74,10 @@ async fn main() -> anyhow::Result<()> { let github_client = dotenvy::var("GITHUB_CLIENT_ID").unwrap_or("".to_string()); let github_secret = dotenvy::var("GITHUB_CLIENT_SECRET").unwrap_or("".to_string()); let webhook_url = dotenvy::var("DISCORD_WEBHOOK_URL").unwrap_or("".to_string()); - let disable_downloads = - dotenvy::var("DISABLE_DOWNLOAD_COUNTS").unwrap_or("0".to_string()) == "1"; + let bot_token = dotenvy::var("DISCORD_BOT_TOKEN").unwrap_or("".to_string()); + let guild_id = dotenvy::var("DISCORD_GUILD_ID").unwrap_or("0".to_string()).parse::().unwrap_or(0); + let channel_id = dotenvy::var("DISCORD_CHANNEL_ID").unwrap_or("0".to_string()).parse::().unwrap_or(0); + let disable_downloads = dotenvy::var("DISABLE_DOWNLOAD_COUNTS").unwrap_or("0".to_string()) == "1"; let max_downloadmb = dotenvy::var("MAX_MOD_FILESIZE_MB") .unwrap_or("250".to_string()) .parse::() @@ -80,6 +89,9 @@ async fn main() -> anyhow::Result<()> { github_client_id: github_client.clone(), github_client_secret: github_secret.clone(), webhook_url: webhook_url.clone(), + bot_token: bot_token.clone(), + guild_id: guild_id.clone(), + channel_id: channel_id.clone(), disable_downloads, max_download_mb: max_downloadmb, }; @@ -138,6 +150,63 @@ async fn main() -> anyhow::Result<()> { }) .bind((addr, port))?; + tokio::spawn(async move { + log::info!("Starting forum thread creation job"); + let pool_res = pool.clone().acquire().await; + if pool_res.is_err() { + return; + } + let mut pool = pool_res.unwrap(); + let query = IndexQueryParams { + page: None, + per_page: Some(100), + query: None, + gd: None, + platforms: None, + sort: IndexSortType::Downloads, + geode: None, + developer: None, + tags: None, + featured: None, + status: Some(ModVersionStatusEnum::Pending), + }; + let results = Mod::get_index(&mut pool, query).await; + if results.is_err() { + return; + } + + let threads = get_threads(guild_id, channel_id, bot_token.clone()).await; + let threads_res = Some(threads); + let mut i = 0; + for m in results.unwrap().data { + let v_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await; + if v_res.is_err() { + continue; + } + + let v = v_res.unwrap(); + + if i != 0 && i % 10 == 0 { + tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; + } + + log::info!("Creating thread for mod {}", m.id); + + create_or_update_thread( + threads_res.clone(), + guild_id, + channel_id, + bot_token.clone(), + m, + v, + None, + app_url.clone() + ).await; + + i += 1; + } + }); + if debug { info!("Running in debug mode, using 1 thread."); server.workers(1).run().await?; From 46559a29d998ddf572514658deea214bfab9089f Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Wed, 15 Jan 2025 14:04:42 -0500 Subject: [PATCH 02/21] These are probably the last ones --- src/endpoints/mod_versions.rs | 6 ++++-- src/forum.rs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index db279f2..2cadb56 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -298,7 +298,9 @@ pub async fn create_version( return Err(e); } - if dev.verified { + let accepted_count = ModVersion::get_accepted_count(&json.id, &mut transaction).await?; + + if dev.verified && accepted_count > 0 { send_webhook( json.id.clone(), json.name.clone(), @@ -322,7 +324,7 @@ pub async fn create_version( .await .or(Err(ApiError::TransactionError))?; - if !dev.verified { + if !dev.verified || accepted_count == 0 { tokio::spawn(async move { let m = fetched_mod.unwrap(); let v_res = ModVersion::get_one(&path.id, &json.version, true, false, &mut pool).await; diff --git a/src/forum.rs b/src/forum.rs index 2042c97..459a275 100644 --- a/src/forum.rs +++ b/src/forum.rs @@ -308,7 +308,7 @@ pub async fn create_or_update_thread( format!(" by {}", admin.unwrap().display_name) } else { "".to_string() - }, if v.info.is_some() { + }, if v.info.is_some() && !v.info.clone().unwrap().is_empty() { format!(": `{}`", v.info.clone().unwrap()) } else { "".to_string() From c0185736aaf157c73a7f9d0a2da5b6855a8dac52 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:29:10 -0500 Subject: [PATCH 03/21] Fix a few bugs --- src/forum.rs | 6 ++++-- src/main.rs | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/forum.rs b/src/forum.rs index 459a275..3313da9 100644 --- a/src/forum.rs +++ b/src/forum.rs @@ -143,7 +143,7 @@ fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { }, { "name": "Hash", - "value": v.hash, + "value": format!("`{}`", v.hash), "inline": true }, { @@ -190,7 +190,9 @@ pub async fn get_threads( return vec![]; } - let vec1 = res.as_array().unwrap().clone(); + let vec1 = res.as_array().unwrap().clone().into_iter() + .filter(|t| t["parent_id"].as_str().unwrap_or("").to_string().parse::().unwrap_or(0) == channel_id) + .collect::>(); let res2 = client .get(format!("https://discord.com/api/v10/channels/{}/threads/archived/public", channel_id)) diff --git a/src/main.rs b/src/main.rs index ac68878..10bcfb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -181,6 +181,7 @@ async fn main() -> anyhow::Result<()> { for m in results.unwrap().data { let v_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await; if v_res.is_err() { + i += 1; continue; } From af6122cf0587760d77dfa211d149d59db1d72bb3 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:08:07 -0500 Subject: [PATCH 04/21] improved three lines --- src/endpoints/mod_versions.rs | 6 +----- src/endpoints/mods.rs | 6 +----- src/forum.rs | 17 ++--------------- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 2cadb56..5c4a5d3 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -432,11 +432,7 @@ pub async fn update_version( if payload.status == ModVersionStatusEnum::Accepted || payload.status == ModVersionStatusEnum::Rejected { tokio::spawn(async move { - let m_res_res = Mod::get_one(&path.id, false, &mut pool).await; - if m_res_res.is_err() { - return; - } - let m_res = m_res_res.unwrap(); + let m_res = Mod::get_one(&path.id, false, &mut pool).await.ok().flatten(); if m_res.is_none() { return; } diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 597e385..8865861 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -136,11 +136,7 @@ pub async fn create( .or(Err(ApiError::TransactionError))?; tokio::spawn(async move { - let m_res_res = Mod::get_one(&json.id, false, &mut pool).await; - if m_res_res.is_err() { - return; - } - let m_res = m_res_res.unwrap(); + let m_res = Mod::get_one(&json.id, false, &mut pool).await.ok().flatten(); if m_res.is_none() { return; } diff --git a/src/forum.rs b/src/forum.rs index 3313da9..69bf969 100644 --- a/src/forum.rs +++ b/src/forum.rs @@ -1,4 +1,4 @@ -use serde_json::{json, Value}; +use serde_json::{json, to_string, Value}; use crate::types::models::dependency::DependencyImportance; use crate::types::models::developer::FetchedDeveloper; @@ -9,20 +9,7 @@ use crate::types::models::mod_version::ModVersion; use crate::types::models::mod_version_status::ModVersionStatusEnum; fn gd_to_string(gd: Option) -> String { - match gd { - Some(GDVersionEnum::All) => "*", - Some(GDVersionEnum::GD2113) => "2.113", - Some(GDVersionEnum::GD2200) => "2.200", - Some(GDVersionEnum::GD2204) => "2.204", - Some(GDVersionEnum::GD2205) => "2.205", - Some(GDVersionEnum::GD2206) => "2.206", - Some(GDVersionEnum::GD2207) => "2.207", - Some(GDVersionEnum::GD22071) => "2.2071", - Some(GDVersionEnum::GD22072) => "2.2072", - Some(GDVersionEnum::GD22073) => "2.2073", - Some(GDVersionEnum::GD22074) => "2.2074", - None => "N/A", - }.to_string() + gd.map(|x| to_string(&x).ok()).flatten().unwrap_or_else(|| "N/A".to_string()).replace("\"", "") } fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { From 72169fc9a0894dbc40851a3ecd11155a5b782668 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:29:39 -0500 Subject: [PATCH 05/21] add empty checks --- src/endpoints/mod_versions.rs | 10 ++++++++++ src/endpoints/mods.rs | 5 +++++ src/forum.rs | 5 +++++ src/main.rs | 5 +++++ 4 files changed, 25 insertions(+) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 5c4a5d3..f3a7561 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -326,6 +326,11 @@ pub async fn create_version( if !dev.verified || accepted_count == 0 { tokio::spawn(async move { + if data.guild_id == 0 || data.channel_id == 0 || data.bot_token.is_empty() { + log::error!("Discord configuration is not set up. Not creating forum threads."); + return; + } + let m = fetched_mod.unwrap(); let v_res = ModVersion::get_one(&path.id, &json.version, true, false, &mut pool).await; if v_res.is_err() { @@ -432,6 +437,11 @@ pub async fn update_version( if payload.status == ModVersionStatusEnum::Accepted || payload.status == ModVersionStatusEnum::Rejected { tokio::spawn(async move { + if data.guild_id == 0 || data.channel_id == 0 || data.bot_token.is_empty() { + log::error!("Discord configuration is not set up. Not creating forum threads."); + return; + } + let m_res = Mod::get_one(&path.id, false, &mut pool).await.ok().flatten(); if m_res.is_none() { return; diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 8865861..004a89a 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -136,6 +136,11 @@ pub async fn create( .or(Err(ApiError::TransactionError))?; tokio::spawn(async move { + if data.guild_id == 0 || data.channel_id == 0 || data.bot_token.is_empty() { + log::error!("Discord configuration is not set up. Not creating forum threads."); + return; + } + let m_res = Mod::get_one(&json.id, false, &mut pool).await.ok().flatten(); if m_res.is_none() { return; diff --git a/src/forum.rs b/src/forum.rs index 69bf969..a5d7fc6 100644 --- a/src/forum.rs +++ b/src/forum.rs @@ -218,6 +218,11 @@ pub async fn create_or_update_thread( admin: Option, base_url: String ) { + if guild_id == 0 || channel_id == 0 || token.is_empty() { + log::error!("Discord configuration is not set up. Not creating forum threads."); + return; + } + let thread_vec = if threads.is_some() { threads.unwrap() } else { diff --git a/src/main.rs b/src/main.rs index 10bcfb3..02ddfe6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -151,6 +151,11 @@ async fn main() -> anyhow::Result<()> { .bind((addr, port))?; tokio::spawn(async move { + if guild_id == 0 || channel_id == 0 || bot_token.is_empty() { + log::error!("Discord configuration is not set up. Not creating forum threads."); + return; + } + log::info!("Starting forum thread creation job"); let pool_res = pool.clone().acquire().await; if pool_res.is_err() { From 4bc40620e6129b3f6d482baed898e9c2a08e41c9 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Fri, 17 Jan 2025 13:52:03 -0500 Subject: [PATCH 06/21] fix a bit more --- src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index f309620..503c1a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,7 @@ use actix_web::{ }; use clap::Parser; use endpoints::mods::{IndexQueryParams, IndexSortType}; -use env_logger::Env; use forum::{create_or_update_thread, get_threads}; -use log::info; use types::models::{mod_entity::Mod, mod_version::ModVersion, mod_version_status::ModVersionStatusEnum}; use crate::types::api; @@ -77,12 +75,12 @@ async fn main() -> anyhow::Result<()> { .unwrap_or(250); let app_data = AppData { - db: pool, - app_url, + db: pool.clone(), + app_url: app_url.clone(), github_client_id: github_client, github_client_secret: github_secret, webhook_url, - bot_token, + bot_token: bot_token.clone(), guild_id, channel_id, disable_downloads, From 1bb6fe46f93d6960ac5c1ce4dba64c9e8a958c60 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Sat, 18 Jan 2025 14:43:36 -0500 Subject: [PATCH 07/21] clone things before refactoring --- src/endpoints/mod_versions.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 02a9cb7..1ef47b0 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -423,7 +423,7 @@ pub async fn update_version( name: version.name.clone(), version: version.version.clone(), owner, - verified_by: dev, + verified_by: dev.clone(), base_url: data.app_url.clone(), } .to_discord_webhook() @@ -434,7 +434,7 @@ pub async fn update_version( name: version.name.clone(), version: version.version.clone(), owner, - verified: NewModVersionVerification::Admin(dev), + verified: NewModVersionVerification::Admin(dev.clone()), base_url: data.app_url.clone(), } .to_discord_webhook() From 71be8faf000fdbe91c165500d6945c8883ed8077 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Sat, 18 Jan 2025 18:59:35 -0500 Subject: [PATCH 08/21] Okay, maybe I'll test these --- src/endpoints/mod_versions.rs | 38 +++++------ src/endpoints/mods.rs | 22 +++---- src/{forum.rs => forum/discord.rs} | 100 +++++++++++------------------ src/forum/mod.rs | 1 + src/main.rs | 27 ++++---- 5 files changed, 75 insertions(+), 113 deletions(-) rename src/{forum.rs => forum/discord.rs} (77%) create mode 100644 src/forum/mod.rs diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 1ef47b0..b1a29b1 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -10,7 +10,7 @@ use crate::events::mod_created::{ }; use crate::webhook::discord::DiscordWebhook; use crate::{ - extractors::auth::Auth, forum::create_or_update_thread, types::{ + extractors::auth::Auth, forum::discord::create_or_update_thread, types::{ api::{ApiError, ApiResponse}, mod_json::{split_version_and_compare, ModJson}, models::{ @@ -330,21 +330,19 @@ pub async fn create_version( return; } - let m = fetched_mod.unwrap(); - let v_res = ModVersion::get_one(&path.id, &json.version, true, false, &mut pool).await; - if v_res.is_err() { + let version_res = ModVersion::get_one(&path.id, &json.version, true, false, &mut pool).await; + if version_res.is_err() { return; } - let v = v_res.unwrap(); create_or_update_thread( None, data.guild_id, data.channel_id, - data.bot_token.clone(), - m, - v, - None, - data.app_url.clone(), + &data.bot_token, + &fetched_mod.unwrap(), + &version_res.unwrap(), + "", + &data.app_url, ).await; }); } @@ -449,25 +447,23 @@ pub async fn update_version( return; } - let m_res = Mod::get_one(&path.id, false, &mut pool).await.ok().flatten(); - if m_res.is_none() { + let mod_res = Mod::get_one(&path.id, false, &mut pool).await.ok().flatten(); + if mod_res.is_none() { return; } - let m = m_res.unwrap(); - let v_res = ModVersion::get_one(&path.id, &path.version, true, false, &mut pool).await; - if v_res.is_err() { + let version_res = ModVersion::get_one(&path.id, &path.version, true, false, &mut pool).await; + if version_res.is_err() { return; } - let v = v_res.unwrap(); create_or_update_thread( None, data.guild_id, data.channel_id, - data.bot_token.clone(), - m, - v, - Some(dev.clone()), - data.app_url.clone(), + &data.bot_token, + &mod_res.unwrap(), + &version_res.unwrap(), + &dev.display_name, + &data.app_url, ).await; }); } diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 342cf34..b1db1ff 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -6,7 +6,7 @@ use crate::database::repository::developers; use crate::database::repository::mods; use crate::events::mod_feature::ModFeaturedEvent; use crate::extractors::auth::Auth; -use crate::forum::create_or_update_thread; +use crate::forum::discord::create_or_update_thread; use crate::types::api::{create_download_link, ApiError, ApiResponse}; use crate::types::mod_json::ModJson; use crate::types::models::developer::Developer; @@ -145,25 +145,23 @@ pub async fn create( return; } - let m_res = Mod::get_one(&json.id, false, &mut pool).await.ok().flatten(); - if m_res.is_none() { + let mod_res = Mod::get_one(&json.id, false, &mut pool).await.ok().flatten(); + if mod_res.is_none() { return; } - let m = m_res.unwrap(); - let v_res = ModVersion::get_one(&json.id, &json.version, true, false, &mut pool).await; - if v_res.is_err() { + let version_res = ModVersion::get_one(&json.id, &json.version, true, false, &mut pool).await; + if version_res.is_err() { return; } - let v = v_res.unwrap(); create_or_update_thread( None, data.guild_id, data.channel_id, - data.bot_token.clone(), - m, - v, - None, - data.app_url.clone(), + &data.bot_token, + &mod_res.unwrap(), + &version_res.unwrap(), + "", + &data.app_url, ).await; }); diff --git a/src/forum.rs b/src/forum/discord.rs similarity index 77% rename from src/forum.rs rename to src/forum/discord.rs index a5d7fc6..5905215 100644 --- a/src/forum.rs +++ b/src/forum/discord.rs @@ -1,39 +1,33 @@ use serde_json::{json, to_string, Value}; -use crate::types::models::dependency::DependencyImportance; -use crate::types::models::developer::FetchedDeveloper; -use crate::types::models::incompatibility::IncompatibilityImportance; use crate::types::models::mod_entity::Mod; use crate::types::models::mod_gd_version::GDVersionEnum; use crate::types::models::mod_version::ModVersion; use crate::types::models::mod_version_status::ModVersionStatusEnum; fn gd_to_string(gd: Option) -> String { - gd.map(|x| to_string(&x).ok()).flatten().unwrap_or_else(|| "N/A".to_string()).replace("\"", "") + gd.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace("\"", "") } -fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { - let deps = v.dependencies.unwrap_or_default().into_iter().map(|d| { - format!("`{} {} ({})`", d.mod_id, d.version, match d.importance { - DependencyImportance::Required => "required", - DependencyImportance::Recommended => "recommended", - DependencyImportance::Suggested => "suggested", - }) - }).collect::>().join("\n"); - let incompats = v.incompatibilities.unwrap_or_default().into_iter().map(|i| { - format!("`{} {} ({})`", i.mod_id, i.version, match i.importance { - IncompatibilityImportance::Breaking => "breaking", - IncompatibilityImportance::Conflicting => "conflicting", - IncompatibilityImportance::Superseded => "superseded", - }) - }).collect::>().join("\n"); - let tags = m.tags.into_iter().map(|t| format!("`{}`", t)).collect::>().join(", "); +fn map_or_none(x: Option>, f: F, sep: &str) -> String + where + F: Fn(T) -> String +{ + let ret = x.map(|x| x.into_iter().map(f).collect::>().join(sep)).unwrap_or("None".to_string()); + if ret.is_empty() { + "None".to_string() + } else { + ret + } +} + +fn mod_embed(m: &Mod, v: &ModVersion, base_url: &str) -> Value { json!({ - "title": format!("{}{}", if m.featured { - "⭐️ " + "title": if m.featured { + format!("⭐️ {}", v.name) } else { - "" - }, v.name), + v.name.clone() + }, "description": v.description, "url": format!("https://geode-sdk.org/mods/{}?version={}", m.id, v.version), "thumbnail": { @@ -67,7 +61,7 @@ fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { }, { "name": "Developers", - "value": m.developers.into_iter().map(|d| { + "value": m.developers.clone().into_iter().map(|d| { if d.is_owner { format!("**[{}](https://geode-sdk.org/mods?developer={})**", d.display_name, d.username) } else { @@ -85,47 +79,29 @@ fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { }, { "name": "Dependencies", - "value": if !deps.is_empty() { - deps - } else { - "None".to_string() - }, + "value": map_or_none(v.dependencies.clone(), |d| format!("`{} {} ({})`", d.mod_id, d.version, + to_string(&d.importance).unwrap_or("unknown".to_string()).replace("\"", "")), "\n"), "inline": false }, { "name": "Incompatibilities", - "value": if !incompats.is_empty() { - incompats - } else { - "None".to_string() - }, + "value": map_or_none(v.incompatibilities.clone(), |i| format!("`{} {} ({})`", i.mod_id, i.version, + to_string(&i.importance).unwrap_or("unknown".to_string()).replace("\"", "")), "\n"), "inline": false }, { "name": "Source", - "value": if m.links.is_some() { - m.links.clone().unwrap().source.unwrap_or("N/A".to_string()) - } else { - m.repository.unwrap_or("N/A".to_string()) - }, + "value": m.links.clone().map(|l| l.source).flatten().unwrap_or(m.repository.clone().unwrap_or("N/A".to_string())), "inline": true }, { "name": "Community", - "value": if m.links.is_some() { - m.links.clone().unwrap().community.unwrap_or("N/A".to_string()) - } else { - "N/A".to_string() - }, + "value": m.links.clone().map(|l| l.community).flatten().unwrap_or("N/A".to_string()), "inline": true }, { "name": "Homepage", - "value": if m.links.is_some() { - m.links.clone().unwrap().homepage.unwrap_or("N/A".to_string()) - } else { - "N/A".to_string() - }, + "value": m.links.clone().map(|l| l.homepage).flatten().unwrap_or("N/A".to_string()), "inline": true }, { @@ -140,11 +116,7 @@ fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { }, { "name": "Tags", - "value": if !tags.is_empty() { - tags - } else { - "None".to_string() - }, + "value": map_or_none(v.tags.clone(), |t| format!("`{}`", t), ", "), "inline": true } ] @@ -153,7 +125,7 @@ fn mod_embed(m: Mod, v: ModVersion, base_url: String) -> Value { pub async fn get_threads( guild_id: u64, channel_id: u64, - token: String + token: &str ) -> Vec { let client = reqwest::Client::new(); let res = client @@ -212,11 +184,11 @@ pub async fn get_threads( pub async fn create_or_update_thread( threads: Option>, guild_id: u64, channel_id: u64, - token: String, - m: Mod, - v: ModVersion, - admin: Option, - base_url: String + token: &str, + m: &Mod, + v: &ModVersion, + admin: &str, + base_url: &str ) { if guild_id == 0 || channel_id == 0 || token.is_empty() { log::error!("Discord configuration is not set up. Not creating forum threads."); @@ -226,7 +198,7 @@ pub async fn create_or_update_thread( let thread_vec = if threads.is_some() { threads.unwrap() } else { - get_threads(guild_id, channel_id, token.clone()).await + get_threads(guild_id, channel_id, token).await }; let thread = thread_vec.iter().find(|t| { @@ -298,8 +270,8 @@ pub async fn create_or_update_thread( ModVersionStatusEnum::Accepted => "Accepted", ModVersionStatusEnum::Rejected => "Rejected", _ => "", - }, if admin.is_some() { - format!(" by {}", admin.unwrap().display_name) + }, if !admin.is_empty() { + format!(" by {}", admin) } else { "".to_string() }, if v.info.is_some() && !v.info.clone().unwrap().is_empty() { diff --git a/src/forum/mod.rs b/src/forum/mod.rs new file mode 100644 index 0000000..3228a62 --- /dev/null +++ b/src/forum/mod.rs @@ -0,0 +1 @@ +pub mod discord; diff --git a/src/main.rs b/src/main.rs index f413673..2eeccd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,8 @@ use actix_web::{ web::{self, QueryConfig}, App, HttpServer }; -use clap::Parser; use endpoints::mods::{IndexQueryParams, IndexSortType}; -use forum::{create_or_update_thread, get_threads}; +use forum::discord::{create_or_update_thread, get_threads}; use types::models::{mod_entity::Mod, mod_version::ModVersion, mod_version_status::ModVersionStatusEnum}; use crate::types::api; @@ -163,18 +162,16 @@ async fn main() -> anyhow::Result<()> { return; } - let threads = get_threads(guild_id, channel_id, bot_token.clone()).await; + let threads = get_threads(guild_id, channel_id, &bot_token).await; let threads_res = Some(threads); - let mut i = 0; - for m in results.unwrap().data { - let v_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await; - if v_res.is_err() { - i += 1; + let mods = results.unwrap(); + for i in 0..mods.count as usize { + let m = &mods.data[i]; + let version_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await; + if version_res.is_err() { continue; } - let v = v_res.unwrap(); - if i != 0 && i % 10 == 0 { tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; } @@ -185,14 +182,12 @@ async fn main() -> anyhow::Result<()> { threads_res.clone(), guild_id, channel_id, - bot_token.clone(), + &bot_token, m, - v, - None, - app_url.clone() + &version_res.unwrap(), + "", + &app_url ).await; - - i += 1; } }); From 4b31786e9e4f616ea5b6ddc8ac55fcec02409d9d Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:51:00 -0500 Subject: [PATCH 09/21] I think this should be it --- src/config.rs | 44 +++++++++++++++++++++++++++++++++++ src/endpoints/mod_versions.rs | 12 +++++----- src/endpoints/mods.rs | 10 ++++---- src/main.rs | 21 ++++++++++------- 4 files changed, 68 insertions(+), 19 deletions(-) diff --git a/src/config.rs b/src/config.rs index fff550f..7a7d0b3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ pub struct AppData { app_url: String, github: GitHubClientData, webhook_url: String, + discord: DiscordForumData, disable_downloads: bool, max_download_mb: u32, port: u16, @@ -16,6 +17,13 @@ pub struct GitHubClientData { client_secret: String, } +#[derive(Clone)] +pub struct DiscordForumData { + guild_id: u64, + channel_id: u64, + bot_token: String, +} + pub async fn build_config() -> anyhow::Result { let env_url = dotenvy::var("DATABASE_URL")?; @@ -29,6 +37,15 @@ pub async fn build_config() -> anyhow::Result { let github_client = dotenvy::var("GITHUB_CLIENT_ID").unwrap_or("".to_string()); let github_secret = dotenvy::var("GITHUB_CLIENT_SECRET").unwrap_or("".to_string()); let webhook_url = dotenvy::var("DISCORD_WEBHOOK_URL").unwrap_or("".to_string()); + let guild_id = dotenvy::var("DISCORD_GUILD_ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0); + let channel_id = dotenvy::var("DISCORD_CHANNEL_ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0); + let bot_token = dotenvy::var("DISCORD_BOT_TOKEN").unwrap_or("".to_string()); let disable_downloads = dotenvy::var("DISABLE_DOWNLOAD_COUNTS").unwrap_or("0".to_string()) == "1"; let max_download_mb = dotenvy::var("MAX_MOD_FILESIZE_MB") @@ -44,6 +61,11 @@ pub async fn build_config() -> anyhow::Result { client_secret: github_secret, }, webhook_url, + discord: DiscordForumData { + guild_id, + channel_id, + bot_token, + }, disable_downloads, max_download_mb, port, @@ -61,6 +83,24 @@ impl GitHubClientData { } } +impl DiscordForumData { + pub fn is_valid(&self) -> bool { + self.guild_id != 0 && self.channel_id != 0 && !self.bot_token.is_empty() + } + + pub fn guild_id(&self) -> u64 { + self.guild_id + } + + pub fn channel_id(&self) -> u64 { + self.channel_id + } + + pub fn bot_token(&self) -> &str { + &self.bot_token + } +} + impl AppData { pub fn db(&self) -> &sqlx::postgres::PgPool { &self.db @@ -78,6 +118,10 @@ impl AppData { &self.webhook_url } + pub fn discord(&self) -> &DiscordForumData { + &self.discord + } + pub fn disable_downloads(&self) -> bool { self.disable_downloads } diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index ca3bbd8..4ed3f1d 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -334,7 +334,7 @@ pub async fn create_version( &fetched_mod.unwrap(), &version_res.unwrap(), "", - &data.app_url(), + &data.app_url() ).await; }); } @@ -438,7 +438,7 @@ pub async fn update_version( if payload.status == ModVersionStatusEnum::Accepted || payload.status == ModVersionStatusEnum::Rejected { tokio::spawn(async move { - if data.guild_id == 0 || data.channel_id == 0 || data.bot_token.is_empty() { + if !data.discord().is_valid() { log::error!("Discord configuration is not set up. Not creating forum threads."); return; } @@ -453,13 +453,13 @@ pub async fn update_version( } create_or_update_thread( None, - data.guild_id, - data.channel_id, - &data.bot_token, + data.discord().guild_id(), + data.discord().channel_id(), + &data.discord().bot_token(), &mod_res.unwrap(), &version_res.unwrap(), &dev.display_name, - &data.app_url, + &data.app_url() ).await; }); } diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index c143a2c..7c7a411 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -150,7 +150,7 @@ pub async fn create( .or(Err(ApiError::TransactionError))?; tokio::spawn(async move { - if data.guild_id == 0 || data.channel_id == 0 || data.bot_token.is_empty() { + if !data.discord().is_valid() { log::error!("Discord configuration is not set up. Not creating forum threads."); return; } @@ -165,13 +165,13 @@ pub async fn create( } create_or_update_thread( None, - data.guild_id, - data.channel_id, - &data.bot_token, + data.discord().guild_id(), + data.discord().channel_id(), + &data.discord().bot_token(), &mod_res.unwrap(), &version_res.unwrap(), "", - &data.app_url, + &data.app_url() ).await; }); diff --git a/src/main.rs b/src/main.rs index 6496a98..ea77f23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,11 +38,12 @@ async fn main() -> anyhow::Result<()> { let port = app_data.port(); let debug = app_data.debug(); + let data = app_data.clone(); log::info!("Starting server on 0.0.0.0:{}", port); let server = HttpServer::new(move || { App::new() - .app_data(web::Data::new(app_data.clone())) + .app_data(web::Data::new(data.clone())) .app_data(QueryConfig::default().error_handler(api::query_error_handler)) .wrap( Cors::default() @@ -85,13 +86,13 @@ async fn main() -> anyhow::Result<()> { .bind(("0.0.0.0", port))?; tokio::spawn(async move { - if guild_id == 0 || channel_id == 0 || bot_token.is_empty() { + if !app_data.discord().is_valid() { log::error!("Discord configuration is not set up. Not creating forum threads."); return; } log::info!("Starting forum thread creation job"); - let pool_res = pool.clone().acquire().await; + let pool_res = app_data.db().acquire().await; if pool_res.is_err() { return; } @@ -114,7 +115,11 @@ async fn main() -> anyhow::Result<()> { return; } - let threads = get_threads(guild_id, channel_id, &bot_token).await; + let threads = get_threads( + app_data.discord().guild_id(), + app_data.discord().channel_id(), + &app_data.discord().bot_token() + ).await; let threads_res = Some(threads); let mods = results.unwrap(); for i in 0..mods.count as usize { @@ -132,13 +137,13 @@ async fn main() -> anyhow::Result<()> { create_or_update_thread( threads_res.clone(), - guild_id, - channel_id, - &bot_token, + app_data.discord().guild_id(), + app_data.discord().channel_id(), + &app_data.discord().bot_token(), m, &version_res.unwrap(), "", - &app_url + &app_data.app_url() ).await; } }); From 9fe2160b4324eaf0b18b0099b887f9318bfed9d1 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Mon, 20 Jan 2025 14:52:10 -0500 Subject: [PATCH 10/21] remove refs --- src/endpoints/mod_versions.rs | 4 ++-- src/endpoints/mods.rs | 2 +- src/main.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 4ed3f1d..ac2c47e 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -330,7 +330,7 @@ pub async fn create_version( None, data.discord().guild_id(), data.discord().channel_id(), - &data.discord().bot_token(), + data.discord().bot_token(), &fetched_mod.unwrap(), &version_res.unwrap(), "", @@ -455,7 +455,7 @@ pub async fn update_version( None, data.discord().guild_id(), data.discord().channel_id(), - &data.discord().bot_token(), + data.discord().bot_token(), &mod_res.unwrap(), &version_res.unwrap(), &dev.display_name, diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 7c7a411..f76f022 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -167,7 +167,7 @@ pub async fn create( None, data.discord().guild_id(), data.discord().channel_id(), - &data.discord().bot_token(), + data.discord().bot_token(), &mod_res.unwrap(), &version_res.unwrap(), "", diff --git a/src/main.rs b/src/main.rs index ea77f23..70cc1f6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,7 +118,7 @@ async fn main() -> anyhow::Result<()> { let threads = get_threads( app_data.discord().guild_id(), app_data.discord().channel_id(), - &app_data.discord().bot_token() + app_data.discord().bot_token() ).await; let threads_res = Some(threads); let mods = results.unwrap(); @@ -139,7 +139,7 @@ async fn main() -> anyhow::Result<()> { threads_res.clone(), app_data.discord().guild_id(), app_data.discord().channel_id(), - &app_data.discord().bot_token(), + app_data.discord().bot_token(), m, &version_res.unwrap(), "", From 419e69afda0c06554d328e5958047f5c1d1485a2 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Mon, 20 Jan 2025 18:30:18 -0500 Subject: [PATCH 11/21] Simplify parameters --- src/config.rs | 4 ++-- src/endpoints/mod_versions.rs | 8 ++------ src/endpoints/mods.rs | 4 +--- src/forum/discord.rs | 32 +++++++++++++++----------------- src/main.rs | 10 ++-------- 5 files changed, 22 insertions(+), 36 deletions(-) diff --git a/src/config.rs b/src/config.rs index 7a7d0b3..1057ae3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -96,8 +96,8 @@ impl DiscordForumData { self.channel_id } - pub fn bot_token(&self) -> &str { - &self.bot_token + pub fn bot_auth(&self) -> String { + format!("Bot {}", self.bot_token) } } diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index ac2c47e..0ccde94 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -328,9 +328,7 @@ pub async fn create_version( } create_or_update_thread( None, - data.discord().guild_id(), - data.discord().channel_id(), - data.discord().bot_token(), + &data.discord(), &fetched_mod.unwrap(), &version_res.unwrap(), "", @@ -453,9 +451,7 @@ pub async fn update_version( } create_or_update_thread( None, - data.discord().guild_id(), - data.discord().channel_id(), - data.discord().bot_token(), + &data.discord(), &mod_res.unwrap(), &version_res.unwrap(), &dev.display_name, diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index f76f022..e20c792 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -165,9 +165,7 @@ pub async fn create( } create_or_update_thread( None, - data.discord().guild_id(), - data.discord().channel_id(), - data.discord().bot_token(), + &data.discord(), &mod_res.unwrap(), &version_res.unwrap(), "", diff --git a/src/forum/discord.rs b/src/forum/discord.rs index 5905215..9ed87c1 100644 --- a/src/forum/discord.rs +++ b/src/forum/discord.rs @@ -1,5 +1,6 @@ use serde_json::{json, to_string, Value}; +use crate::config::DiscordForumData; use crate::types::models::mod_entity::Mod; use crate::types::models::mod_gd_version::GDVersionEnum; use crate::types::models::mod_version::ModVersion; @@ -123,14 +124,11 @@ fn mod_embed(m: &Mod, v: &ModVersion, base_url: &str) -> Value { }) } -pub async fn get_threads( - guild_id: u64, channel_id: u64, - token: &str -) -> Vec { +pub async fn get_threads(data: &DiscordForumData) -> Vec { let client = reqwest::Client::new(); let res = client - .get(format!("https://discord.com/api/v10/guilds/{}/threads/active", guild_id)) - .header("Authorization", format!("Bot {}", token)) + .get(format!("https://discord.com/api/v10/guilds/{}/threads/active", data.guild_id())) + .header("Authorization", data.bot_auth()) .send() .await; if res.is_err() { @@ -149,13 +147,14 @@ pub async fn get_threads( return vec![]; } + let channel_id = data.channel_id(); let vec1 = res.as_array().unwrap().clone().into_iter() .filter(|t| t["parent_id"].as_str().unwrap_or("").to_string().parse::().unwrap_or(0) == channel_id) .collect::>(); let res2 = client .get(format!("https://discord.com/api/v10/channels/{}/threads/archived/public", channel_id)) - .header("Authorization", format!("Bot {}", token)) + .header("Authorization", data.bot_auth()) .send() .await; if res2.is_err() { @@ -183,14 +182,13 @@ pub async fn get_threads( pub async fn create_or_update_thread( threads: Option>, - guild_id: u64, channel_id: u64, - token: &str, + data: &DiscordForumData, m: &Mod, v: &ModVersion, admin: &str, base_url: &str ) { - if guild_id == 0 || channel_id == 0 || token.is_empty() { + if !data.is_valid() { log::error!("Discord configuration is not set up. Not creating forum threads."); return; } @@ -198,7 +196,7 @@ pub async fn create_or_update_thread( let thread_vec = if threads.is_some() { threads.unwrap() } else { - get_threads(guild_id, channel_id, token).await + get_threads(data).await }; let thread = thread_vec.iter().find(|t| { @@ -212,8 +210,8 @@ pub async fn create_or_update_thread( } let _ = client - .post(format!("https://discord.com/api/v10/channels/{}/threads", channel_id)) - .header("Authorization", format!("Bot {}", token)) + .post(format!("https://discord.com/api/v10/channels/{}/threads", data.channel_id())) + .header("Authorization", data.bot_auth()) .json(&json!({ "name": format!("🕓 {} ({}) {}", v.name, m.id, v.version), "message": { @@ -264,7 +262,7 @@ pub async fn create_or_update_thread( let _ = client .post(format!("https://discord.com/api/v10/channels/{}/messages", thread_id)) - .header("Authorization", format!("Bot {}", token)) + .header("Authorization", data.bot_auth()) .json(&json!({ "content": format!("{}{}{}", match v.status { ModVersionStatusEnum::Accepted => "Accepted", @@ -289,7 +287,7 @@ pub async fn create_or_update_thread( let _ = client .patch(format!("https://discord.com/api/v10/channels/{}", thread_id)) - .header("Authorization", format!("Bot {}", token)) + .header("Authorization", data.bot_auth()) .json(&json!({ "name": match v.status { ModVersionStatusEnum::Accepted => format!("✅ {} ({}) {}", v.name, m.id, v.version), @@ -307,7 +305,7 @@ pub async fn create_or_update_thread( let _ = client .patch(format!("https://discord.com/api/v10/channels/{}", thread_id)) - .header("Authorization", format!("Bot {}", token)) + .header("Authorization", data.bot_auth()) .json(&json!({ "name": format!("🕓 {} ({}) {}", v.name, m.id, v.version) })) @@ -316,7 +314,7 @@ pub async fn create_or_update_thread( let _ = client .patch(format!("https://discord.com/api/v10/channels/{}/messages/{}", thread_id, thread_id)) - .header("Authorization", format!("Bot {}", token)) + .header("Authorization", data.bot_auth()) .json(&json!({ "embeds": [mod_embed(m, v, base_url)] })) diff --git a/src/main.rs b/src/main.rs index 70cc1f6..29658f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,11 +115,7 @@ async fn main() -> anyhow::Result<()> { return; } - let threads = get_threads( - app_data.discord().guild_id(), - app_data.discord().channel_id(), - app_data.discord().bot_token() - ).await; + let threads = get_threads(&app_data.discord()).await; let threads_res = Some(threads); let mods = results.unwrap(); for i in 0..mods.count as usize { @@ -137,9 +133,7 @@ async fn main() -> anyhow::Result<()> { create_or_update_thread( threads_res.clone(), - app_data.discord().guild_id(), - app_data.discord().channel_id(), - app_data.discord().bot_token(), + &app_data.discord(), m, &version_res.unwrap(), "", From 8e955e03a534fb59a8d466076a4aaf43ec639006 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Mon, 20 Jan 2025 20:48:29 -0500 Subject: [PATCH 12/21] clean up code --- src/forum/discord.rs | 64 ++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/src/forum/discord.rs b/src/forum/discord.rs index 9ed87c1..f66d0f5 100644 --- a/src/forum/discord.rs +++ b/src/forum/discord.rs @@ -2,26 +2,9 @@ use serde_json::{json, to_string, Value}; use crate::config::DiscordForumData; use crate::types::models::mod_entity::Mod; -use crate::types::models::mod_gd_version::GDVersionEnum; use crate::types::models::mod_version::ModVersion; use crate::types::models::mod_version_status::ModVersionStatusEnum; -fn gd_to_string(gd: Option) -> String { - gd.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace("\"", "") -} - -fn map_or_none(x: Option>, f: F, sep: &str) -> String - where - F: Fn(T) -> String -{ - let ret = x.map(|x| x.into_iter().map(f).collect::>().join(sep)).unwrap_or("None".to_string()); - if ret.is_empty() { - "None".to_string() - } else { - ret - } -} - fn mod_embed(m: &Mod, v: &ModVersion, base_url: &str) -> Value { json!({ "title": if m.featured { @@ -73,26 +56,49 @@ fn mod_embed(m: &Mod, v: &ModVersion, base_url: &str) -> Value { }, { "name": "Geometry Dash", - "value": format!("Windows: {}\nAndroid (32-bit): {}\nAndroid (64-bit): {}\nmacOS (Intel): {}\nmacOS (ARM): {}", - gd_to_string(v.gd.win), gd_to_string(v.gd.android32), gd_to_string(v.gd.android64), - gd_to_string(v.gd.mac_intel), gd_to_string(v.gd.mac_arm)), + "value": format!( + "Windows: {}\nAndroid (64-bit): {}\nAndroid (32-bit): {}\nmacOS (ARM): {}\nmacOS (Intel): {}\niOS: {}", + v.gd.win.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace('"', ""), + v.gd.android64.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace('"', ""), + v.gd.android32.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace('"', ""), + v.gd.mac_arm.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace('"', ""), + v.gd.mac_intel.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace('"', ""), + v.gd.ios.map(|x| to_string(&x).ok()).flatten().unwrap_or("N/A".to_string()).replace('"', "") + ), "inline": false }, { "name": "Dependencies", - "value": map_or_none(v.dependencies.clone(), |d| format!("`{} {} ({})`", d.mod_id, d.version, - to_string(&d.importance).unwrap_or("unknown".to_string()).replace("\"", "")), "\n"), + "value": v.dependencies.clone().map(|x| { + if !x.is_empty() { + x.into_iter().map(|d| { + format!("`{} {} ({})`", d.mod_id, d.version, to_string(&d.importance) + .unwrap_or("unknown".to_string()).replace('"', "")) + }).collect::>().join("\n") + } else { + "None".to_string() + } + }).unwrap_or("None".to_string()), "inline": false }, { "name": "Incompatibilities", - "value": map_or_none(v.incompatibilities.clone(), |i| format!("`{} {} ({})`", i.mod_id, i.version, - to_string(&i.importance).unwrap_or("unknown".to_string()).replace("\"", "")), "\n"), + "value": v.incompatibilities.clone().map(|x| { + if !x.is_empty() { + x.into_iter().map(|i| { + format!("`{} {} ({})`", i.mod_id, i.version, to_string(&i.importance) + .unwrap_or("unknown".to_string()).replace('"', "")) + }).collect::>().join("\n") + } else { + "None".to_string() + } + }).unwrap_or("None".to_string()), "inline": false }, { "name": "Source", - "value": m.links.clone().map(|l| l.source).flatten().unwrap_or(m.repository.clone().unwrap_or("N/A".to_string())), + "value": m.links.clone().map(|l| l.source).flatten() + .unwrap_or(m.repository.clone().unwrap_or("N/A".to_string())), "inline": true }, { @@ -117,7 +123,13 @@ fn mod_embed(m: &Mod, v: &ModVersion, base_url: &str) -> Value { }, { "name": "Tags", - "value": map_or_none(v.tags.clone(), |t| format!("`{}`", t), ", "), + "value": v.tags.clone().map(|x| { + if !x.is_empty() { + x.into_iter().map(|t| format!("`{}`", t)).collect::>().join(", ") + } else { + "None".to_string() + } + }).unwrap_or("None".to_string()), "inline": true } ] From ee4e75e8013e36668d21e979a65b5b65615b3e2b Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Mon, 27 Jan 2025 11:02:15 -0500 Subject: [PATCH 13/21] create pending threads from oldest to newest --- src/main.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 637d455..637d67f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,20 +120,24 @@ async fn main() -> anyhow::Result<()> { let threads = get_threads(&app_data.discord()).await; let threads_res = Some(threads); - let mods = results.unwrap(); - for i in 0..mods.count as usize { - let m = &mods.data[i]; + let mut mods = results.unwrap().data; + mods.sort_by(|a, b| { + let a = &a.versions[0]; + let b = &b.versions[0]; + a.created_at.cmp(&b.created_at) + }); + for i in 0..mods.len() { + let m = &mods[i]; let version_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await; if version_res.is_err() { continue; } if i != 0 && i % 10 == 0 { + log::info!("Created {i} threads, sleeping for 10 seconds"); tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; } - log::info!("Creating thread for mod {}", m.id); - create_or_update_thread( threads_res.clone(), &app_data.discord(), @@ -143,6 +147,8 @@ async fn main() -> anyhow::Result<()> { &app_data.app_url() ).await; } + + log::info!("Finished creating forum threads"); }); if debug { From f0d934b4ee90357ac99ae6703b5a8e912867020f Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjustin000@users.noreply.github.com> Date: Mon, 27 Jan 2025 12:23:53 -0500 Subject: [PATCH 14/21] change button ids --- src/forum/discord.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/forum/discord.rs b/src/forum/discord.rs index f66d0f5..35f297b 100644 --- a/src/forum/discord.rs +++ b/src/forum/discord.rs @@ -240,7 +240,7 @@ pub async fn create_or_update_thread( "id": Value::Null, "name": "✅" }, - "custom_id": "index/admin/accept:forum" + "custom_id": "index/accept" }, { "type": 2, @@ -250,7 +250,7 @@ pub async fn create_or_update_thread( "id": Value::Null, "name": "❌" }, - "custom_id": "index-admin/reject:forum" + "custom_id": "index/reject" } ] } From 8d2f024119d10b587830b7631c730c779238cbf0 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 10:56:20 -0500 Subject: [PATCH 15/21] Get back up to speed --- src/endpoints/mod_versions.rs | 33 ++++++++++++++++++++++----------- src/endpoints/mods.rs | 4 ++-- src/main.rs | 4 ++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index b38450b..41fd6af 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -21,6 +21,7 @@ use crate::{ api::ApiResponse, mod_json::{split_version_and_compare, ModJson}, models::{ + mod_entity::Mod, mod_gd_version::{GDVersionEnum, VerPlatform}, mod_version::{self, ModVersion}, mod_version_status::ModVersionStatusEnum, @@ -354,6 +355,8 @@ pub async fn create_version( .collect(), ); + let json_version = json.version.clone(); + if make_accepted { if let Some(links) = json.links.clone() { mod_links::upsert( @@ -392,21 +395,29 @@ pub async fn create_version( } .to_discord_webhook() .send(&data.webhook_url()); - } else { + } + + version.modify_metadata(data.app_url(), false); + + if !make_accepted { tokio::spawn(async move { if !data.discord().is_valid() { log::error!("Discord configuration is not set up. Not creating forum threads."); return; } - let version_res = ModVersion::get_one(&path.id, &json.version, true, false, &mut pool).await; - if version_res.is_err() { + let mod_res = Mod::get_one(&id, false, &mut pool).await.ok().flatten(); + if mod_res.is_none() { + return; + } + let version_res = ModVersion::get_one(&id, &json_version, true, false, &mut pool).await.ok().flatten(); + if version_res.is_none() { return; } create_or_update_thread( None, &data.discord(), - &fetched_mod.unwrap(), + &mod_res.unwrap(), &version_res.unwrap(), "", &data.app_url() @@ -414,8 +425,6 @@ pub async fn create_version( }); } - version.modify_metadata(data.app_url(), false); - Ok(HttpResponse::Created().json(ApiResponse { error: "".into(), payload: version, @@ -516,6 +525,8 @@ pub async fn update_version( tx.commit().await?; + let display_name = dev.display_name.clone(); + if payload.status == ModVersionStatusEnum::Accepted { let is_update = approved_count > 0; @@ -531,7 +542,7 @@ pub async fn update_version( name: version.name.clone(), version: version.version.clone(), owner, - verified_by: dev.clone(), + verified_by: dev, base_url: data.app_url().to_string(), } .to_discord_webhook() @@ -542,7 +553,7 @@ pub async fn update_version( name: version.name.clone(), version: version.version.clone(), owner, - verified: NewModVersionVerification::Admin(dev.clone()), + verified: NewModVersionVerification::Admin(dev), base_url: data.app_url().to_string(), } .to_discord_webhook() @@ -561,8 +572,8 @@ pub async fn update_version( if mod_res.is_none() { return; } - let version_res = ModVersion::get_one(&path.id, &path.version, true, false, &mut pool).await; - if version_res.is_err() { + let version_res = ModVersion::get_one(&path.id, &path.version, true, false, &mut pool).await.ok().flatten(); + if version_res.is_none() { return; } create_or_update_thread( @@ -570,7 +581,7 @@ pub async fn update_version( &data.discord(), &mod_res.unwrap(), &version_res.unwrap(), - &dev.display_name, + &display_name, &data.app_url() ).await; }); diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 7aed45f..0df2913 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -232,8 +232,8 @@ pub async fn create( if mod_res.is_none() { return; } - let version_res = ModVersion::get_one(&json.id, &json.version, true, false, &mut pool).await; - if version_res.is_err() { + let version_res = ModVersion::get_one(&json.id, &json.version, true, false, &mut pool).await.ok().flatten(); + if version_res.is_none() { return; } create_or_update_thread( diff --git a/src/main.rs b/src/main.rs index 5c1e748..fcd9a1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -131,8 +131,8 @@ async fn main() -> anyhow::Result<()> { }); for i in 0..mods.len() { let m = &mods[i]; - let version_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await; - if version_res.is_err() { + let version_res = ModVersion::get_one(&m.id, &m.versions[0].version, true, false, &mut pool).await.ok().flatten(); + if version_res.is_none() { continue; } From 3d27a4cdbeea0253a2733a036878afc58fa508e5 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:03:18 -0500 Subject: [PATCH 16/21] Clean up a little --- src/endpoints/mod_versions.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 41fd6af..ea4e1e6 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -17,7 +17,8 @@ use crate::mod_zip::{self, download_mod}; use crate::types::models; use crate::webhook::discord::DiscordWebhook; use crate::{ - extractors::auth::Auth, forum::discord::create_or_update_thread, types::{ + extractors::auth::Auth, forum::discord::create_or_update_thread, + types::{ api::ApiResponse, mod_json::{split_version_and_compare, ModJson}, models::{ @@ -394,7 +395,7 @@ pub async fn create_version( base_url: data.app_url().to_string(), } .to_discord_webhook() - .send(&data.webhook_url()); + .send(data.webhook_url()); } version.modify_metadata(data.app_url(), false); From 5aa5a520be2e771461a2e69dbd1b1cd3425e99b6 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:39:14 -0500 Subject: [PATCH 17/21] Clean up messages --- src/forum/discord.rs | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/src/forum/discord.rs b/src/forum/discord.rs index 35f297b..930cf8e 100644 --- a/src/forum/discord.rs +++ b/src/forum/discord.rs @@ -47,9 +47,9 @@ fn mod_embed(m: &Mod, v: &ModVersion, base_url: &str) -> Value { "name": "Developers", "value": m.developers.clone().into_iter().map(|d| { if d.is_owner { - format!("**[{}](https://geode-sdk.org/mods?developer={})**", d.display_name, d.username) + format!("**[{}](https://geode-sdk.org/developers/{})**", d.display_name, d.id) } else { - format!("[{}](https://geode-sdk.org/mods?developer={})", d.display_name, d.username) + format!("[{}](https://geode-sdk.org/developers/{})", d.display_name, d.id) } }).collect::>().join(", "), "inline": true @@ -227,34 +227,7 @@ pub async fn create_or_update_thread( .json(&json!({ "name": format!("🕓 {} ({}) {}", v.name, m.id, v.version), "message": { - "embeds": [mod_embed(m, v, base_url)], - "components": [ - { - "type": 1, - "components": [ - { - "type": 2, - "style": 3, - "label": "Accept", - "emoji": { - "id": Value::Null, - "name": "✅" - }, - "custom_id": "index/accept" - }, - { - "type": 2, - "style": 4, - "label": "Reject", - "emoji": { - "id": Value::Null, - "name": "❌" - }, - "custom_id": "index/reject" - } - ] - } - ] + "embeds": [mod_embed(m, v, base_url)] } })) .send() @@ -285,7 +258,7 @@ pub async fn create_or_update_thread( } else { "".to_string() }, if v.info.is_some() && !v.info.clone().unwrap().is_empty() { - format!(": `{}`", v.info.clone().unwrap()) + format!("\n```\n{}\n```", v.info.clone().unwrap()) } else { "".to_string() }), From b3e4e3842e6c38980e1159db6b47833f792e7949 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:29:27 -0500 Subject: [PATCH 18/21] Replace with helper --- src/endpoints/mod_versions.rs | 64 ++++++++++------------------------- src/endpoints/mods.rs | 33 +++++------------- src/forum/discord.rs | 35 ++++++++++++++++++- src/main.rs | 5 ++- 4 files changed, 62 insertions(+), 75 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index ea4e1e6..d50367a 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -17,7 +17,7 @@ use crate::mod_zip::{self, download_mod}; use crate::types::models; use crate::webhook::discord::DiscordWebhook; use crate::{ - extractors::auth::Auth, forum::discord::create_or_update_thread, + extractors::auth::Auth, forum, types::{ api::ApiResponse, mod_json::{split_version_and_compare, ModJson}, @@ -401,29 +401,14 @@ pub async fn create_version( version.modify_metadata(data.app_url(), false); if !make_accepted { - tokio::spawn(async move { - if !data.discord().is_valid() { - log::error!("Discord configuration is not set up. Not creating forum threads."); - return; - } - - let mod_res = Mod::get_one(&id, false, &mut pool).await.ok().flatten(); - if mod_res.is_none() { - return; - } - let version_res = ModVersion::get_one(&id, &json_version, true, false, &mut pool).await.ok().flatten(); - if version_res.is_none() { - return; - } - create_or_update_thread( - None, - &data.discord(), - &mod_res.unwrap(), - &version_res.unwrap(), - "", - &data.app_url() - ).await; - }); + forum::discord::create_or_update_thread( + data.discord().clone(), + id, + json_version, + "".to_string(), + data.app_url().to_string(), + pool, + ); } Ok(HttpResponse::Created().json(ApiResponse { @@ -563,29 +548,14 @@ pub async fn update_version( } if payload.status == ModVersionStatusEnum::Accepted || payload.status == ModVersionStatusEnum::Rejected { - tokio::spawn(async move { - if !data.discord().is_valid() { - log::error!("Discord configuration is not set up. Not creating forum threads."); - return; - } - - let mod_res = Mod::get_one(&path.id, false, &mut pool).await.ok().flatten(); - if mod_res.is_none() { - return; - } - let version_res = ModVersion::get_one(&path.id, &path.version, true, false, &mut pool).await.ok().flatten(); - if version_res.is_none() { - return; - } - create_or_update_thread( - None, - &data.discord(), - &mod_res.unwrap(), - &version_res.unwrap(), - &display_name, - &data.app_url() - ).await; - }); + forum::discord::create_or_update_thread( + data.discord().clone(), + path.id.clone(), + path.version.clone(), + display_name, + data.app_url().to_string(), + pool + ); } Ok(HttpResponse::NoContent()) diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 0df2913..95ecfb4 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -10,7 +10,7 @@ use crate::database::repository::mods; use crate::endpoints::ApiError; use crate::events::mod_feature::ModFeaturedEvent; use crate::extractors::auth::Auth; -use crate::forum::discord::create_or_update_thread; +use crate::forum; use crate::mod_zip; use crate::types::api::{create_download_link, ApiResponse}; use crate::types::mod_json::ModJson; @@ -222,29 +222,14 @@ pub async fn create( i.modify_metadata(data.app_url(), false); } - tokio::spawn(async move { - if !data.discord().is_valid() { - log::error!("Discord configuration is not set up. Not creating forum threads."); - return; - } - - let mod_res = Mod::get_one(&json.id, false, &mut pool).await.ok().flatten(); - if mod_res.is_none() { - return; - } - let version_res = ModVersion::get_one(&json.id, &json.version, true, false, &mut pool).await.ok().flatten(); - if version_res.is_none() { - return; - } - create_or_update_thread( - None, - &data.discord(), - &mod_res.unwrap(), - &version_res.unwrap(), - "", - &data.app_url() - ).await; - }); + forum::discord::create_or_update_thread( + data.discord().clone(), + json.id, + json.version, + "".to_string(), + data.app_url().to_string(), + pool + ); Ok(HttpResponse::Created().json(ApiResponse { error: "".into(), diff --git a/src/forum/discord.rs b/src/forum/discord.rs index 930cf8e..fed6eb1 100644 --- a/src/forum/discord.rs +++ b/src/forum/discord.rs @@ -192,7 +192,40 @@ pub async fn get_threads(data: &DiscordForumData) -> Vec { .collect::>() } -pub async fn create_or_update_thread( +pub fn create_or_update_thread( + data: DiscordForumData, + id: String, + ver: String, + admin: String, + base_url: String, + mut pool: sqlx::pool::PoolConnection, +) { + tokio::spawn(async move { + if !data.is_valid() { + log::error!("Discord configuration is not set up. Not creating forum threads."); + return; + } + + let mod_res = Mod::get_one(&id, false, &mut pool).await.ok().flatten(); + if mod_res.is_none() { + return; + } + let version_res = ModVersion::get_one(&id, &ver, true, false, &mut pool).await.ok().flatten(); + if version_res.is_none() { + return; + } + create_or_update_thread_internal( + None, + &data, + &mod_res.unwrap(), + &version_res.unwrap(), + &admin, + &base_url + ).await; + }); +} + +pub async fn create_or_update_thread_internal( threads: Option>, data: &DiscordForumData, m: &Mod, diff --git a/src/main.rs b/src/main.rs index fcd9a1c..2ec7834 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ use actix_web::{ App, HttpServer, }; use endpoints::mods::{IndexQueryParams, IndexSortType}; -use forum::discord::{create_or_update_thread, get_threads}; use types::models::{mod_entity::Mod, mod_version::ModVersion, mod_version_status::ModVersionStatusEnum}; use crate::types::api; @@ -121,7 +120,7 @@ async fn main() -> anyhow::Result<()> { return; } - let threads = get_threads(&app_data.discord()).await; + let threads = forum::discord::get_threads(&app_data.discord()).await; let threads_res = Some(threads); let mut mods = results.unwrap().data; mods.sort_by(|a, b| { @@ -141,7 +140,7 @@ async fn main() -> anyhow::Result<()> { tokio::time::sleep(tokio::time::Duration::from_secs(10)).await; } - create_or_update_thread( + forum::discord::create_or_update_thread_internal( threads_res.clone(), &app_data.discord(), m, From 8ad8dada32350c97e5b1cb8dea373cfdad3e2096 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 12:30:29 -0500 Subject: [PATCH 19/21] Forgot these --- src/endpoints/mod_versions.rs | 1 - src/endpoints/mods.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index d50367a..221f2c4 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -22,7 +22,6 @@ use crate::{ api::ApiResponse, mod_json::{split_version_and_compare, ModJson}, models::{ - mod_entity::Mod, mod_gd_version::{GDVersionEnum, VerPlatform}, mod_version::{self, ModVersion}, mod_version_status::ModVersionStatusEnum, diff --git a/src/endpoints/mods.rs b/src/endpoints/mods.rs index 95ecfb4..f58b0b3 100644 --- a/src/endpoints/mods.rs +++ b/src/endpoints/mods.rs @@ -19,7 +19,6 @@ use crate::types::models::incompatibility::Incompatibility; use crate::types::models::mod_entity::{Mod, ModUpdate}; use crate::types::models::mod_gd_version::{GDVersionEnum, VerPlatform}; use crate::types::models::mod_link::ModLinks; -use crate::types::models::mod_version::ModVersion; use crate::types::models::mod_version_status::ModVersionStatusEnum; use crate::webhook::discord::DiscordWebhook; use actix_web::{get, post, put, web, HttpResponse, Responder}; From ba3db91570d7647eb923b0c7b44de79e04b6db17 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:12:53 -0500 Subject: [PATCH 20/21] This is probably how it should go --- src/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2ec7834..bee1147 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,7 +42,7 @@ async fn main() -> anyhow::Result<()> { log::info!("Starting server on 0.0.0.0:{}", port); let server = HttpServer::new(move || { App::new() - .app_data(web::Data::new(data.clone())) + .app_data(web::Data::new(app_data.clone())) .app_data(QueryConfig::default().error_handler(api::query_error_handler)) .wrap( Cors::default() @@ -91,13 +91,13 @@ async fn main() -> anyhow::Result<()> { .bind(("0.0.0.0", port))?; tokio::spawn(async move { - if !app_data.discord().is_valid() { + if !data.discord().is_valid() { log::error!("Discord configuration is not set up. Not creating forum threads."); return; } log::info!("Starting forum thread creation job"); - let pool_res = app_data.db().acquire().await; + let pool_res = data.db().acquire().await; if pool_res.is_err() { return; } @@ -120,7 +120,7 @@ async fn main() -> anyhow::Result<()> { return; } - let threads = forum::discord::get_threads(&app_data.discord()).await; + let threads = forum::discord::get_threads(&data.discord()).await; let threads_res = Some(threads); let mut mods = results.unwrap().data; mods.sort_by(|a, b| { @@ -142,11 +142,11 @@ async fn main() -> anyhow::Result<()> { forum::discord::create_or_update_thread_internal( threads_res.clone(), - &app_data.discord(), + &data.discord(), m, &version_res.unwrap(), "", - &app_data.app_url() + &data.app_url() ).await; } From 81e0d704281d96db80fbe316cb67ff6c20c25340 Mon Sep 17 00:00:00 2001 From: Jasmine <52604018+hiimjasmine00@users.noreply.github.com> Date: Thu, 13 Nov 2025 13:17:57 -0500 Subject: [PATCH 21/21] Zero deletions! --- src/endpoints/mod_versions.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/endpoints/mod_versions.rs b/src/endpoints/mod_versions.rs index 221f2c4..5006c49 100644 --- a/src/endpoints/mod_versions.rs +++ b/src/endpoints/mod_versions.rs @@ -17,7 +17,8 @@ use crate::mod_zip::{self, download_mod}; use crate::types::models; use crate::webhook::discord::DiscordWebhook; use crate::{ - extractors::auth::Auth, forum, + extractors::auth::Auth, + forum, types::{ api::ApiResponse, mod_json::{split_version_and_compare, ModJson},