Discord: customizable announcements and live embeds (status, guilds, leaderboard, baltop) backend

This commit is contained in:
Claude committed 2026-10-01 01:43:00 +00:00
1 parent 6fd78f32ea
commit f9b0dcedff
7 files changed
+924 -15

No files matched your search

+28 -15
View File
@@ -11,6 +11,7 @@ use crate::routes::connections::ConnectionsSettings;
use crate::state::AppState;
use crate::store;
use axum::extract::{Path, State};
use crate::embeds;
use axum::Json;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
@@ -336,36 +337,46 @@ pub async fn announce(state: &AppState) -> AppResult<u32> {
}
let mut sent = 0;
if s.notify_achievements {
let rows: Vec<(String, String, String)> = sqlx::query_as(
"SELECT COALESCE(u.username, ua.user_uuid), a.title, ua.unlocked_at FROM user_achievements ua
let rows: Vec<(String, String, String, String, i64, String, String)> = sqlx::query_as(
"SELECT ua.user_uuid, COALESCE(u.username, ua.user_uuid), a.title, a.description, a.xp_reward, a.category, ua.unlocked_at FROM user_achievements ua
JOIN achievements a ON a.id = ua.achievement_id LEFT JOIN users u ON u.uuid = ua.user_uuid
WHERE ua.unlocked_at > ? ORDER BY ua.unlocked_at LIMIT 10",
)
.bind(&cursor.achievements)
.fetch_all(&state.db)
.await?;
for (who, title, at) in rows {
post_webhook(state, &s.webhook_url, "Achievement unlocked", &format!("**{}** earned **{}**", clip(&who), clip(&title)), 0xfcd34d).await;
for (uuid, who, title, desc, xp, category, at) in rows {
let mut vars = embeds::player_vars(state, &uuid, &who).await;
for (k, v) in [("achievement", embeds::plain(&title)), ("achievement_description", embeds::plain(&desc)), ("xp", xp.to_string()), ("category", embeds::plain(&category))] {
vars.insert(k.into(), v);
}
if embeds::announce_event(state, "achievement", &vars).await.unwrap_or(false) {
sent += 1;
}
cursor.achievements = at;
sent += 1;
}
}
if s.notify_guilds {
let rows: Vec<(String, String, String)> =
sqlx::query_as("SELECT name, tag, created_at FROM guilds WHERE created_at > ? ORDER BY created_at LIMIT 10").bind(&cursor.guilds).fetch_all(&state.db).await?;
for (name, tag, at) in rows {
post_webhook(state, &s.webhook_url, "New guild", &format!("**{}** [{}] was founded", clip(&name), clip(&tag)), 0x22d3ee).await;
let rows: Vec<(String, String)> = sqlx::query_as("SELECT id, created_at FROM guilds WHERE created_at > ? ORDER BY created_at LIMIT 10").bind(&cursor.guilds).fetch_all(&state.db).await?;
for (id, at) in rows {
let vars = embeds::guild_vars(state, &id).await;
if embeds::announce_event(state, "guild", &vars).await.unwrap_or(false) {
sent += 1;
}
cursor.guilds = at;
sent += 1;
}
}
if s.notify_members {
let rows: Vec<(String, String)> =
sqlx::query_as("SELECT username, created_at FROM users WHERE status = 'active' AND created_at > ? ORDER BY created_at LIMIT 10").bind(&cursor.members).fetch_all(&state.db).await?;
for (name, at) in rows {
post_webhook(state, &s.webhook_url, "New player", &format!("Welcome **{}**!", clip(&name)), 0x6ee7b7).await;
let rows: Vec<(String, String, String)> =
sqlx::query_as("SELECT uuid, username, created_at FROM users WHERE status = 'active' AND created_at > ? ORDER BY created_at LIMIT 10").bind(&cursor.members).fetch_all(&state.db).await?;
for (uuid, name, at) in rows {
let mut vars = embeds::player_vars(state, &uuid, &name).await;
let total: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users WHERE status = 'active'").fetch_one(&state.db).await.unwrap_or(0);
vars.insert("members_total".into(), total.to_string());
if embeds::announce_event(state, "member", &vars).await.unwrap_or(false) {
sent += 1;
}
cursor.members = at;
sent += 1;
}
}
store::kv_set(state, "discord_cursor", &cursor).await?;
@@ -385,11 +396,13 @@ pub async fn test_webhook(_: AdminUser, State(state): State<AppState>) -> AppRes
pub fn spawn_worker(state: AppState) {
tokio::spawn(async move {
let mut tick = 0u64;
let mut live_last = std::collections::BTreeMap::new();
loop {
tokio::time::sleep(Duration::from_secs(30)).await;
if let Err(e) = announce(&state).await {
tracing::debug!("discord announce: {e:?}");
}
embeds::tick_live(&state, &mut live_last).await;
tick += 1;
if tick % 30 == 0 {
if let Err(e) = sync_all(&state).await {