From d1d4510ce830a2890f90be791d1f300bebc8efd6 Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Thu, 4 Jun 2026 18:52:56 +0700 Subject: [PATCH] chore(format): auto-format files with biome --- services/discord-gateway/drizzle.config.ts | 4 +- services/discord-gateway/src/app/shutdown.ts | 2 +- services/discord-gateway/src/index.ts | 2 +- .../modules/ai-moderation/aiAnalysisWorker.ts | 93 +++++++--- .../ai-moderation/autoDeleteManager.ts | 43 +++-- .../ai-moderation/conversationContext.ts | 12 +- .../src/modules/ai-moderation/index.ts | 4 +- .../ai-moderation/indonesianTextNormalizer.ts | 161 +++++++++++++++--- .../src/modules/ai-moderation/llmClient.ts | 4 +- .../ai-moderation/llmModerationClient.ts | 44 ++--- .../modules/ai-moderation/responseLogger.ts | 6 +- .../modules/ai-moderation/textCacheStore.ts | 32 ++-- .../attachment-upload/attachmentUploader.ts | 4 +- .../modules/attachment-upload/imageResizer.ts | 2 +- .../modules/attachment-upload/teleUpload.ts | 10 +- .../modules/command-handler/commandHandler.ts | 2 +- .../event-broadcaster/eventBroadcaster.ts | 2 +- .../modules/message-capture/analyticsStore.ts | 2 +- .../modules/message-capture/broadcaster.ts | 4 +- .../src/modules/message-capture/index.ts | 2 +- .../modules/message-capture/messageCapture.ts | 7 +- .../modules/message-capture/messageStore.ts | 2 +- .../src/modules/voice-recording/player.ts | 5 +- .../src/modules/voice-recording/recorder.ts | 6 +- .../voice-recording/recorder/metadata.ts | 6 +- .../recorder/sessionRecording.ts | 2 +- .../voice-recording/recorder/uploader.ts | 2 +- .../src/modules/voice-recording/teleUpload.ts | 10 +- .../voice-recording/voiceController.ts | 4 +- .../src/shared/database/drizzle.ts | 2 +- .../src/shared/database/migrate.ts | 13 +- .../src/shared/database/schema.ts | 3 +- .../src/shared/database/voiceRecordingRepo.ts | 2 +- 33 files changed, 342 insertions(+), 157 deletions(-) diff --git a/services/discord-gateway/drizzle.config.ts b/services/discord-gateway/drizzle.config.ts index 1585f0d..bf64a2c 100644 --- a/services/discord-gateway/drizzle.config.ts +++ b/services/discord-gateway/drizzle.config.ts @@ -5,6 +5,8 @@ export default defineConfig({ out: "./drizzle/migrations", dialect: "postgresql", dbCredentials: { - url: process.env.DATABASE_URL || "postgresql://postgres:postgres@localhost:5432/bete", + url: + process.env.DATABASE_URL || + "postgresql://postgres:postgres@localhost:5432/bete", }, }); diff --git a/services/discord-gateway/src/app/shutdown.ts b/services/discord-gateway/src/app/shutdown.ts index 2c611cb..1d94e1f 100644 --- a/services/discord-gateway/src/app/shutdown.ts +++ b/services/discord-gateway/src/app/shutdown.ts @@ -1,9 +1,9 @@ +import type { createChildLogger } from "@bete/shared/logger"; import type { Client } from "discord.js-selfbot-v13"; import type { CommandHandler } from "../modules/command-handler/commandHandler.js"; import type { EventBroadcaster } from "../modules/event-broadcaster/index.js"; import type { VoiceController } from "../modules/voice-recording/voiceController.js"; import type { closeDatabase } from "../shared/database/drizzle.js"; -import type { createChildLogger } from "@bete/shared/logger"; type Logger = ReturnType; type CloseDatabase = typeof closeDatabase; diff --git a/services/discord-gateway/src/index.ts b/services/discord-gateway/src/index.ts index a0bb29c..a4ece8b 100644 --- a/services/discord-gateway/src/index.ts +++ b/services/discord-gateway/src/index.ts @@ -2,8 +2,8 @@ import "./mock-crc.js"; import "libsodium-wrappers"; import "@snazzah/davey"; import "dotenv/config"; -import { initializeDiscordGateway } from "./app/bootstrap.js"; import { createChildLogger } from "@bete/shared/logger"; +import { initializeDiscordGateway } from "./app/bootstrap.js"; const logger = createChildLogger("discord-gateway"); diff --git a/services/discord-gateway/src/modules/ai-moderation/aiAnalysisWorker.ts b/services/discord-gateway/src/modules/ai-moderation/aiAnalysisWorker.ts index a784327..cc939b6 100644 --- a/services/discord-gateway/src/modules/ai-moderation/aiAnalysisWorker.ts +++ b/services/discord-gateway/src/modules/ai-moderation/aiAnalysisWorker.ts @@ -1,13 +1,19 @@ import { config } from "../../shared/config/config.js"; import { initializeDatabase } from "../../shared/database/drizzle.js"; -import { buildConversationContext } from "./conversationContext.js"; -import { runModerationAnalysis, runSimpleTextFallback } from "./llmModerationClient.js"; import { getAttachmentsForMessages, getConversationContextBefore, updateMessagesAIAnalysisBulk, } from "../message-capture/messageStore.js"; -import type { MessageRecord, AnalysisResult } from "../message-capture/types.js"; +import type { + AnalysisResult, + MessageRecord, +} from "../message-capture/types.js"; +import { buildConversationContext } from "./conversationContext.js"; +import { + runModerationAnalysis, + runSimpleTextFallback, +} from "./llmModerationClient.js"; let dbInitialized = false; let dbInitPromise: Promise | null = null; @@ -30,24 +36,44 @@ type WorkerJob = | { type: "batch"; conversationKey: string; messages: MessageRecord[] } | { type: "individual"; message: MessageRecord; skipNormalAnalysis: boolean }; -type BatchOkResponse = { ok: true; conversationKey: string; rows: MessageRecord[] }; -type BatchErrorResponse = { ok: false; conversationKey: string; rows: MessageRecord[]; error: string }; +type BatchOkResponse = { + ok: true; + conversationKey: string; + rows: MessageRecord[]; +}; +type BatchErrorResponse = { + ok: false; + conversationKey: string; + rows: MessageRecord[]; + error: string; +}; type IndividualOkResponse = { ok: true; results: AnalysisResult[] }; -type IndividualErrorResponse = { ok: false; results: AnalysisResult[]; error: string }; +type IndividualErrorResponse = { + ok: false; + results: AnalysisResult[]; + error: string; +}; -type WorkerResponse = BatchOkResponse | BatchErrorResponse | IndividualOkResponse | IndividualErrorResponse; +type WorkerResponse = + | BatchOkResponse + | BatchErrorResponse + | IndividualOkResponse + | IndividualErrorResponse; /** * Default export — Piscina worker entry point. * Routes to the correct handler based on `type` field. */ -export default async function workerRouter(job: WorkerJob): Promise { +export default async function workerRouter( + job: WorkerJob, +): Promise { if (!config.AI_LLM_API_KEY) { console.error( JSON.stringify({ level: "FATAL", context: "aiAnalysisWorker", - error: "AI_LLM_API_KEY is missing from environment. Force closing worker operation.", + error: + "AI_LLM_API_KEY is missing from environment. Force closing worker operation.", timestamp: new Date().toISOString(), }), ); @@ -59,7 +85,12 @@ export default async function workerRouter(job: WorkerJob): Promise { +async function processBatch(job: { + type: "batch"; + conversationKey: string; + messages: MessageRecord[]; +}): Promise { const { conversationKey, messages } = job; const firstMessage = messages[0]; if (!firstMessage) return { ok: true, conversationKey, rows: [] }; @@ -150,7 +192,11 @@ async function processBatch(job: { type: "batch"; conversationKey: string; messa // Individual fallback handler (offloaded from main thread) // --------------------------------------------------------------------------- -async function processIndividual(job: { type: "individual"; message: MessageRecord; skipNormalAnalysis: boolean }): Promise { +async function processIndividual(job: { + type: "individual"; + message: MessageRecord; + skipNormalAnalysis: boolean; +}): Promise { const { message, skipNormalAnalysis } = job; const contextBefore = await getConversationContextBefore({ @@ -167,7 +213,10 @@ async function processIndividual(job: { type: "individual"; message: MessageReco }); const contextIds = contextBefore.map((m) => m.id); - const attachments = await getAttachmentsForMessages([message.id, ...contextIds]); + const attachments = await getAttachmentsForMessages([ + message.id, + ...contextIds, + ]); let results: AnalysisResult[]; diff --git a/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts b/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts index f193f2e..7da1e0c 100644 --- a/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts +++ b/services/discord-gateway/src/modules/ai-moderation/autoDeleteManager.ts @@ -1,6 +1,6 @@ +import { createChildLogger } from "@bete/shared/logger"; import type { Client, PermissionString } from "discord.js-selfbot-v13"; import { config } from "../../shared/config/config.js"; -import { createChildLogger } from "@bete/shared/logger"; import { createModerationAction } from "../message-capture/messageStore.js"; import type { MessageRecord } from "../message-capture/types.js"; @@ -323,17 +323,22 @@ export async function attemptAutoDeleteFlaggedMessage( try { const targetUser = await client.users.fetch(message.user_id); if (targetUser) { - const reason = message.ai_categories ?? message.ai_moderation_flags ?? "(unknown)"; + const reason = + message.ai_categories ?? message.ai_moderation_flags ?? "(unknown)"; await targetUser.send( `Pesan Anda di **${guild.name}** telah dihapus oleh sistem moderasi otomatis.\n` + - `Alasan: ${reason}\n` + - `Jika Anda merasa ini adalah kesalahan, silakan hubungi admin server.`, + `Alasan: ${reason}\n` + + `Jika Anda merasa ini adalah kesalahan, silakan hubungi admin server.`, ); } } catch (dmErr) { // DM might fail if user has DMs disabled — not critical logger.debug( - { messageId: message.id, userId: message.user_id, error: String(dmErr) }, + { + messageId: message.id, + userId: message.user_id, + error: String(dmErr), + }, "Failed to send DM notification for auto-deleted message", ); } @@ -342,18 +347,28 @@ export async function attemptAutoDeleteFlaggedMessage( // ── Log to moderation channel ── if (config.AUTO_DELETE_LOG_CHANNEL_ID) { try { - const logChannel = guild.channels.cache.get(config.AUTO_DELETE_LOG_CHANNEL_ID); - if (logChannel && "send" in logChannel && typeof (logChannel as any).send === "function") { + const logChannel = guild.channels.cache.get( + config.AUTO_DELETE_LOG_CHANNEL_ID, + ); + if ( + logChannel && + "send" in logChannel && + typeof (logChannel as any).send === "function" + ) { const severity = message.ai_severity ?? "none"; - const categories = message.ai_categories ?? message.ai_moderation_flags ?? "—"; - const snippet = (message.edited_content ?? message.content).substring(0, 200); + const categories = + message.ai_categories ?? message.ai_moderation_flags ?? "—"; + const snippet = (message.edited_content ?? message.content).substring( + 0, + 200, + ); await (logChannel as any).send( `**🧹 Auto-Delete** — Pesan dari <@${message.user_id}> di <#${channelId}>\n` + - `**Status:** ${message.ai_status}\n` + - `**Severitas:** ${severity}\n` + - `**Kategori:** ${categories}\n` + - `**Isi:** ${snippet}\n` + - `**Waktu:** `, + `**Status:** ${message.ai_status}\n` + + `**Severitas:** ${severity}\n` + + `**Kategori:** ${categories}\n` + + `**Isi:** ${snippet}\n` + + `**Waktu:** `, ); } } catch (logErr) { diff --git a/services/discord-gateway/src/modules/ai-moderation/conversationContext.ts b/services/discord-gateway/src/modules/ai-moderation/conversationContext.ts index 0052217..4574b79 100644 --- a/services/discord-gateway/src/modules/ai-moderation/conversationContext.ts +++ b/services/discord-gateway/src/modules/ai-moderation/conversationContext.ts @@ -1,7 +1,7 @@ -import { formatModerationTextEvidenceForPrompt } from "./indonesianTextNormalizer.js"; +import { encoding_for_model as encodingForModel } from "tiktoken"; import { formatMediaEvidenceForPrompt } from "../message-capture/messageMetadata.js"; import type { MessageRecord } from "../message-capture/types.js"; -import { encoding_for_model as encodingForModel } from "tiktoken"; +import { formatModerationTextEvidenceForPrompt } from "./indonesianTextNormalizer.js"; export interface ConversationContextInput { contextBefore: MessageRecord[]; @@ -59,13 +59,17 @@ export function buildConversationContext( const { contextBefore, targets, maxTokens } = input; // Calculate tokens used by targets (parallel) - const targetLines = targets.map((msg) => formatMessageForPrompt(msg, "target")); + const targetLines = targets.map((msg) => + formatMessageForPrompt(msg, "target"), + ); let usedTokens = targetLines.reduce( (sum, line) => sum + estimateTokens(line), 0, ); - const contextLines = contextBefore.map((msg) => formatMessageForPrompt(msg, "context")); + const contextLines = contextBefore.map((msg) => + formatMessageForPrompt(msg, "context"), + ); const selectedContextLines: string[] = []; // Go backwards through context, taking most recent first diff --git a/services/discord-gateway/src/modules/ai-moderation/index.ts b/services/discord-gateway/src/modules/ai-moderation/index.ts index 48f9e70..584ad99 100644 --- a/services/discord-gateway/src/modules/ai-moderation/index.ts +++ b/services/discord-gateway/src/modules/ai-moderation/index.ts @@ -1,8 +1,8 @@ export { startPendingAIAnalysisWorker } from "./aiAnalyzer.js"; export { - normalizeDiscordCustomEmoji, - detectIndonesianBadwords, buildModerationTextEvidence, + detectIndonesianBadwords, + normalizeDiscordCustomEmoji, } from "./indonesianTextNormalizer.js"; export { runModerationAnalysis } from "./llmModerationClient.js"; export { buildSystemPrompt } from "./moderationPrompt.js"; diff --git a/services/discord-gateway/src/modules/ai-moderation/indonesianTextNormalizer.ts b/services/discord-gateway/src/modules/ai-moderation/indonesianTextNormalizer.ts index 9c6b223..9b10017 100644 --- a/services/discord-gateway/src/modules/ai-moderation/indonesianTextNormalizer.ts +++ b/services/discord-gateway/src/modules/ai-moderation/indonesianTextNormalizer.ts @@ -19,17 +19,29 @@ const badwordCache = new Map(); // Conservative: only returns true for patterns that CANNOT be violations. // --------------------------------------------------------------------------- -const SAFE_PATTERNS: Array<{ test: (text: string) => boolean; reason: string }> = [ +const SAFE_PATTERNS: Array<{ + test: (text: string) => boolean; + reason: string; +}> = [ { - test: (t) => /^(wkwk+|w+kw+k+|wkwkw+|haha+|hehe+|hihi+|huhu+|xixi+|wakak+|awkwa+)$/i.test(t), + test: (t) => + /^(wkwk+|w+kw+k+|wkwkw+|haha+|hehe+|hihi+|huhu+|xixi+|wakak+|awkwa+)$/i.test( + t, + ), reason: "laughter pattern", }, { - test: (t) => /^(ok|oke|okay|sip|siap|aman|mantap|gas|gass|gaskeun|santuy|gaskan|lah|wih|wah|eh|nah|loh|hmm|hm|heh)$/i.test(t), + test: (t) => + /^(ok|oke|okay|sip|siap|aman|mantap|gas|gass|gaskeun|santuy|gaskan|lah|wih|wah|eh|nah|loh|hmm|hm|heh)$/i.test( + t, + ), reason: "single-word affirmative", }, { - test: (t) => /^(hai|halo|hello|hi|oi|woy|woi|pagi|siang|sore|malam|mlm|p|w|L|F|gws|thx|thks|makasih|ty|thanks|yw|sama-sama|ok sip|ok bang|siap bang)$/i.test(t), + test: (t) => + /^(hai|halo|hello|hi|oi|woy|woi|pagi|siang|sore|malam|mlm|p|w|L|F|gws|thx|thks|makasih|ty|thanks|yw|sama-sama|ok sip|ok bang|siap bang)$/i.test( + t, + ), reason: "greeting/common expression", }, { @@ -63,49 +75,148 @@ const BADWORD_CATEGORIES: BadwordEntry[] = [ description: "vulgar genitalia / sexual terms", flag: "vulgar_language", words: [ - "kontol", "memek", "pepek", "tempik", "peler", "pelir", "pukimak", "pukima", - "jancok", "jancuk", "cok", "cuk", "pantek", "palek", "ngentot", "ngewe", - "entot", "ewe", "coli", "sange", "sangean", "ngocok", "bangkot", - "nenen", "tete", "tetek", "dodot", "kentu", "perek", "bispak", "bangsat", - "babi", "asu", "anjing", "anjir", "anjirt", "njing", "njir", "anjay", - "kampret", "kampang", "brengsek", "brengus", "bejad", "bajingan", - "goblok", "tolol", "bego", "dungu", "idiot", "beban", "keparat", - "setan", "iblis", "sialan", "sial", "kacang", "edan", "gila", + "kontol", + "memek", + "pepek", + "tempik", + "peler", + "pelir", + "pukimak", + "pukima", + "jancok", + "jancuk", + "cok", + "cuk", + "pantek", + "palek", + "ngentot", + "ngewe", + "entot", + "ewe", + "coli", + "sange", + "sangean", + "ngocok", + "bangkot", + "nenen", + "tete", + "tetek", + "dodot", + "kentu", + "perek", + "bispak", + "bangsat", + "babi", + "asu", + "anjing", + "anjir", + "anjirt", + "njing", + "njir", + "anjay", + "kampret", + "kampang", + "brengsek", + "brengus", + "bejad", + "bajingan", + "goblok", + "tolol", + "bego", + "dungu", + "idiot", + "beban", + "keparat", + "setan", + "iblis", + "sialan", + "sial", + "kacang", + "edan", + "gila", ], }, { description: "harassment / targeted insults", flag: "harassment", words: [ - "mampus", "mati", "bunuh", "bacot", "cupu", "geblek", "kere", - "ngawur", "sembarangan", "nyampah", "nyampah", "sarap", - "ke laut aja", "gila lu", "sinting", "editan", "mending mati", - "monyet", "kuda", "unta", "bangke", "bangsat", + "mampus", + "mati", + "bunuh", + "bacot", + "cupu", + "geblek", + "kere", + "ngawur", + "sembarangan", + "nyampah", + "nyampah", + "sarap", + "ke laut aja", + "gila lu", + "sinting", + "editan", + "mending mati", + "monyet", + "kuda", + "unta", + "bangke", + "bangsat", ], }, { description: "SARA / racial slurs (non-exhaustive)", flag: "sara", words: [ - "cina", "tionghoa", "pribumi", "non-pribumi", "kaffir", "kafir", - "murtad", "sesat", "liberal", "komunis", "komunisme", "pki", + "cina", + "tionghoa", + "pribumi", + "non-pribumi", + "kaffir", + "kafir", + "murtad", + "sesat", + "liberal", + "komunis", + "komunisme", + "pki", ], }, { description: "gambling / judi", flag: "gambling", words: [ - "judi", "slot", "togel", "toto gelap", "casino", "roulette", - "poker", "domino", "gaple", "sabung ayam", "bola jalan", - "maxwin", "gacor", "scatter", "bonanza", "olympus", + "judi", + "slot", + "togel", + "toto gelap", + "casino", + "roulette", + "poker", + "domino", + "gaple", + "sabung ayam", + "bola jalan", + "maxwin", + "gacor", + "scatter", + "bonanza", + "olympus", ], }, { description: "hate speech / extreme discrimination", flag: "hate_speech", words: [ - "bencina", "bencin", "bangsat", "dajjal", "laknat", "keparat", - "dasar cina", "dasar tionghoa", "dasar pribumi", + "bencina", + "bencin", + "bangsat", + "dajjal", + "laknat", + "keparat", + "dasar cina", + "dasar tionghoa", + "dasar pribumi", ], }, ]; @@ -294,9 +405,7 @@ export function buildModerationTextEvidence( }; } -export function formatModerationTextEvidenceForPrompt( - text: string, -): string { +export function formatModerationTextEvidenceForPrompt(text: string): string { const evidence = buildModerationTextEvidence(text); if (evidence.normalized === evidence.raw && evidence.notes.length === 0) { return ""; diff --git a/services/discord-gateway/src/modules/ai-moderation/llmClient.ts b/services/discord-gateway/src/modules/ai-moderation/llmClient.ts index 073669c..1d0f67a 100644 --- a/services/discord-gateway/src/modules/ai-moderation/llmClient.ts +++ b/services/discord-gateway/src/modules/ai-moderation/llmClient.ts @@ -6,11 +6,11 @@ * defaults are maintained in one place. */ +import { createChildLogger } from "@bete/shared/logger"; +import { retryWithBackoff } from "@bete/shared/utils"; import OpenAI from "openai"; import { config } from "../../shared/config/config.js"; -import { retryWithBackoff } from "@bete/shared/utils"; import { withLlmConcurrency } from "./concurrencyLimiter.js"; -import { createChildLogger } from "@bete/shared/logger"; const log = createChildLogger("llm-client"); diff --git a/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts b/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts index d8cc8aa..0b23bf6 100644 --- a/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts +++ b/services/discord-gateway/src/modules/ai-moderation/llmModerationClient.ts @@ -32,19 +32,17 @@ import { computeImagePhash, getCachedMediaAnalysis, getCachedMediaByPhash, + getCachedUserModeration, getRecentCorrectedModerations, makeCustomEmojiCacheKey, makeImageCacheKey, makeStickerCacheKey, + makeUserModerationCacheKey, + setCachedUserModeration, upsertCachedMediaAnalysis, upsertCachedMediaByPhash, } from "./textCacheStore.js"; import { extractUrlsFromText, fetchUrlSafely } from "./urlFetcher.js"; -import { - getCachedUserModeration, - makeUserModerationCacheKey, - setCachedUserModeration, -} from "./textCacheStore.js"; const SeveritySchema = z.enum(["none", "low", "medium", "high", "critical"]); const RecommendedActionSchema = z.enum([ @@ -1690,7 +1688,11 @@ export async function runModerationAnalysis( // This guards against both legacy corrupt entries and any future write-path bugs. if ( cached.flags.some((f) => - ["analysis_api_failed", "analysis_parse_failed", "analysis_incomplete"].includes(f), + [ + "analysis_api_failed", + "analysis_parse_failed", + "analysis_incomplete", + ].includes(f), ) ) { log.warn( @@ -1707,7 +1709,8 @@ export async function runModerationAnalysis( categories: cached.categories, severity: cached.severity as AnalysisResult["severity"], confidence: cached.confidence, - recommendedAction: cached.recommendedAction as AnalysisResult["recommendedAction"], + recommendedAction: + cached.recommendedAction as AnalysisResult["recommendedAction"], policyVersion: "cached-user-moderation-2026-06", evidence: [], }); @@ -1727,7 +1730,11 @@ export async function runModerationAnalysis( if (cacheHits.length > 0) { log.info( - { cacheHits: cacheHits.length, uncached: uncachedTargets.length, total: targets.length }, + { + cacheHits: cacheHits.length, + uncached: uncachedTargets.length, + total: targets.length, + }, "User moderation cache applied — skipping LLM call for cached targets", ); } @@ -1953,15 +1960,11 @@ Kategori: spam`; } // ── Parse category from "Kategori: xxx" line ── - const categoryMatch = analysis.match( - /[Kk]ategori:\s*(\w+)/i, - ); + const categoryMatch = analysis.match(/[Kk]ategori:\s*(\w+)/i); if (categoryMatch) { const parsedCat = categoryMatch[1].toLowerCase(); // Only accept known categories - if ( - ["harassment", "spam", "gambling", "sara"].includes(parsedCat) - ) { + if (["harassment", "spam", "gambling", "sara"].includes(parsedCat)) { category = parsedCat; } // Strip the "Kategori:" line from the analysis text so it's cleaner @@ -1969,7 +1972,12 @@ Kategori: spam`; } log.info( - { messageId: message.id, status, category, analysis: analysis.slice(0, 100) }, + { + messageId: message.id, + status, + category, + analysis: analysis.slice(0, 100), + }, "Simple fallback step 2 — reason + category", ); } catch (error) { @@ -1985,10 +1993,8 @@ Kategori: spam`; } // Build the result fields using parsed category - const flags: string[] = - status === "clean" ? [] : [category]; - const categories: string[] = - status === "clean" ? [] : [category]; + const flags: string[] = status === "clean" ? [] : [category]; + const categories: string[] = status === "clean" ? [] : [category]; const score = status === "flagged" ? 0.7 : status === "warn" ? 0.4 : 0; const severity: "none" | "low" | "medium" | "high" | "critical" = status === "flagged" ? "medium" : status === "warn" ? "low" : "none"; diff --git a/services/discord-gateway/src/modules/ai-moderation/responseLogger.ts b/services/discord-gateway/src/modules/ai-moderation/responseLogger.ts index d88f2c5..7871dba 100644 --- a/services/discord-gateway/src/modules/ai-moderation/responseLogger.ts +++ b/services/discord-gateway/src/modules/ai-moderation/responseLogger.ts @@ -83,7 +83,11 @@ export function logModerationAnalysis( model: string, results: AnalysisResult[], duration_ms: number, - tokenUsage?: { prompt_tokens: number; completion_tokens: number; total_tokens: number }, + tokenUsage?: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }, parseErrors: string[] = [], ): void { const response: ModerationAnalysisResponse = { diff --git a/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts b/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts index c6dc30c..9807967 100644 --- a/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts +++ b/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; -import { executeAll, executeGet } from "../../shared/database/drizzle.js"; import { createChildLogger } from "@bete/shared/logger"; +import { executeAll, executeGet } from "../../shared/database/drizzle.js"; const logger = createChildLogger("text-cache-store"); @@ -255,10 +255,7 @@ export function makeUserModerationCacheKey( userId: string, content: string, ): string { - const hash = createHash("sha256") - .update(content) - .digest("hex") - .slice(0, 16); + const hash = createHash("sha256").update(content).digest("hex").slice(0, 16); return `user_mod:${userId}:${hash}`; } @@ -266,9 +263,7 @@ export function makeUserModerationCacheKey( * Lookup a cached moderation result for a (user, content) pair. * Returns the stored result fields or null. */ -export async function getCachedUserModeration( - cacheKey: string, -): Promise<{ +export async function getCachedUserModeration(cacheKey: string): Promise<{ status: "clean" | "flagged"; flags: string[]; score: number; @@ -409,8 +404,9 @@ export async function computeImagePhash( ): Promise { try { // Dynamic import — imghash is ESM with a default export containing { hash, hashRaw, ... } - const imghashModule: { default?: { hash?: (buf: Buffer) => Promise } } = - await import("imghash"); + const imghashModule: { + default?: { hash?: (buf: Buffer) => Promise }; + } = await import("imghash"); const hashFn = imghashModule.default?.hash; if (typeof hashFn !== "function") return null; const hash = await hashFn(buffer); @@ -451,13 +447,15 @@ export async function getRecentCorrectedModerations( if (!rows || rows.length === 0) return []; - return (rows as Array<{ - id: string; - original_flags: string; - corrected_flags: string; - correction_notes: string | null; - content_snippet: string; - }>).map((row) => ({ + return ( + rows as Array<{ + id: string; + original_flags: string; + corrected_flags: string; + correction_notes: string | null; + content_snippet: string; + }> + ).map((row) => ({ id: row.id, originalFlags: JSON.parse(row.original_flags) as string[], correctedFlags: JSON.parse(row.corrected_flags) as string[], diff --git a/services/discord-gateway/src/modules/attachment-upload/attachmentUploader.ts b/services/discord-gateway/src/modules/attachment-upload/attachmentUploader.ts index 6cb36fa..4e36c8a 100644 --- a/services/discord-gateway/src/modules/attachment-upload/attachmentUploader.ts +++ b/services/discord-gateway/src/modules/attachment-upload/attachmentUploader.ts @@ -1,11 +1,11 @@ -import { config } from "../../shared/config/config.js"; import { createChildLogger } from "@bete/shared/logger"; -import { uploadToTele } from "./teleUpload.js"; +import { config } from "../../shared/config/config.js"; import { updateAttachmentAsFailedUpload, updateAttachmentAsUploaded, updateAttachmentDiscordUrl, } from "../message-capture/messageStore.js"; +import { uploadToTele } from "./teleUpload.js"; const logger = createChildLogger("attachment-uploader"); diff --git a/services/discord-gateway/src/modules/attachment-upload/imageResizer.ts b/services/discord-gateway/src/modules/attachment-upload/imageResizer.ts index b173a30..b6ca203 100644 --- a/services/discord-gateway/src/modules/attachment-upload/imageResizer.ts +++ b/services/discord-gateway/src/modules/attachment-upload/imageResizer.ts @@ -1,5 +1,5 @@ -import sharp from "sharp"; import { createChildLogger } from "@bete/shared/logger"; +import sharp from "sharp"; const log = createChildLogger("imageResizer"); diff --git a/services/discord-gateway/src/modules/attachment-upload/teleUpload.ts b/services/discord-gateway/src/modules/attachment-upload/teleUpload.ts index 91716d8..633ad50 100644 --- a/services/discord-gateway/src/modules/attachment-upload/teleUpload.ts +++ b/services/discord-gateway/src/modules/attachment-upload/teleUpload.ts @@ -37,14 +37,8 @@ export async function uploadToTele(input: { timeoutMs?: number; retries: number; }): Promise { - const { - buffer, - filename, - contentType, - uploadUrl, - timeoutMs, - retries, - } = input; + const { buffer, filename, contentType, uploadUrl, timeoutMs, retries } = + input; const response = await retryWithBackoff( async () => { diff --git a/services/discord-gateway/src/modules/command-handler/commandHandler.ts b/services/discord-gateway/src/modules/command-handler/commandHandler.ts index 8d69411..a1af7b0 100644 --- a/services/discord-gateway/src/modules/command-handler/commandHandler.ts +++ b/services/discord-gateway/src/modules/command-handler/commandHandler.ts @@ -1,7 +1,7 @@ +import { createChildLogger } from "@bete/shared/logger"; import type { Client } from "discord.js-selfbot-v13"; import Redis from "ioredis"; import { config } from "../../shared/config/config.js"; -import { createChildLogger } from "@bete/shared/logger"; import { discordPlayer } from "../voice-recording/player.js"; import type { VoiceController } from "../voice-recording/voiceController.js"; diff --git a/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts b/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts index 20a92b6..558c1c3 100644 --- a/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts +++ b/services/discord-gateway/src/modules/event-broadcaster/eventBroadcaster.ts @@ -1,5 +1,5 @@ -import Redis from "ioredis"; import type { CustomLogger } from "@bete/shared/logger"; +import Redis from "ioredis"; export interface DiscordGatewayEvent { type: string; diff --git a/services/discord-gateway/src/modules/message-capture/analyticsStore.ts b/services/discord-gateway/src/modules/message-capture/analyticsStore.ts index bdbed84..8cd1f83 100644 --- a/services/discord-gateway/src/modules/message-capture/analyticsStore.ts +++ b/services/discord-gateway/src/modules/message-capture/analyticsStore.ts @@ -1,5 +1,5 @@ -import { executeAll, executeGet } from "../../shared/database/drizzle.js"; import { createChildLogger } from "@bete/shared/logger"; +import { executeAll, executeGet } from "../../shared/database/drizzle.js"; import type { MessageRecord } from "./types.js"; const logger = createChildLogger("analytics-store"); diff --git a/services/discord-gateway/src/modules/message-capture/broadcaster.ts b/services/discord-gateway/src/modules/message-capture/broadcaster.ts index ff01918..fab261e 100644 --- a/services/discord-gateway/src/modules/message-capture/broadcaster.ts +++ b/services/discord-gateway/src/modules/message-capture/broadcaster.ts @@ -1,12 +1,12 @@ -import type { WebSocket } from "ws"; import { createChildLogger } from "@bete/shared/logger"; -import type { MediaState } from "../voice-recording/mediaTypes.js"; +import type { WebSocket } from "ws"; import type { AnalysisQueueStatus, AttachmentRecord, MessageRecord, ModerationWsEvent, } from "../message-capture/types.js"; +import type { MediaState } from "../voice-recording/mediaTypes.js"; export type BroadcasterClient = Pick; diff --git a/services/discord-gateway/src/modules/message-capture/index.ts b/services/discord-gateway/src/modules/message-capture/index.ts index c2bbe33..8daa385 100644 --- a/services/discord-gateway/src/modules/message-capture/index.ts +++ b/services/discord-gateway/src/modules/message-capture/index.ts @@ -1,4 +1,3 @@ -export { registerMessageCapture } from "./messageCapture.js"; export { getDisplayContent, getMessageLocation, @@ -19,3 +18,4 @@ export type { MessageRecord, VoiceSegmentRecord, } from "../message-capture/types.js"; +export { registerMessageCapture } from "./messageCapture.js"; diff --git a/services/discord-gateway/src/modules/message-capture/messageCapture.ts b/services/discord-gateway/src/modules/message-capture/messageCapture.ts index 1eba86b..2b861ef 100644 --- a/services/discord-gateway/src/modules/message-capture/messageCapture.ts +++ b/services/discord-gateway/src/modules/message-capture/messageCapture.ts @@ -1,6 +1,6 @@ +import { createChildLogger } from "@bete/shared/logger"; import type { Client, Message } from "discord.js-selfbot-v13"; import { config } from "../../shared/config/config.js"; -import { createChildLogger } from "@bete/shared/logger"; import { queueMessageAnalysis } from "../ai-moderation/aiAnalyzer.js"; import { processAttachmentUpload } from "../attachment-upload/attachmentUploader.js"; import type { EventBroadcaster } from "../event-broadcaster/eventBroadcaster.js"; @@ -16,7 +16,10 @@ import { updateMessageAsEdited, upsertMessageForCapture, } from "../message-capture/messageStore.js"; -import type { AttachmentRecord, MessageRecord } from "../message-capture/types.js"; +import type { + AttachmentRecord, + MessageRecord, +} from "../message-capture/types.js"; const logger = createChildLogger("message-capture"); diff --git a/services/discord-gateway/src/modules/message-capture/messageStore.ts b/services/discord-gateway/src/modules/message-capture/messageStore.ts index e534d73..fb488de 100644 --- a/services/discord-gateway/src/modules/message-capture/messageStore.ts +++ b/services/discord-gateway/src/modules/message-capture/messageStore.ts @@ -1,3 +1,4 @@ +import { createChildLogger } from "@bete/shared/logger"; import { and, asc, @@ -17,7 +18,6 @@ import { moderationActionsTable, retentionPoliciesTable, } from "../../shared/database/schema.js"; -import { createChildLogger } from "@bete/shared/logger"; import { decodeCursor, encodeCursor } from "../message-capture/pagination.js"; import type { AttachmentRecord, diff --git a/services/discord-gateway/src/modules/voice-recording/player.ts b/services/discord-gateway/src/modules/voice-recording/player.ts index 86e8880..8783860 100644 --- a/services/discord-gateway/src/modules/voice-recording/player.ts +++ b/services/discord-gateway/src/modules/voice-recording/player.ts @@ -8,10 +8,7 @@ import { StreamType, VoiceConnection, } from "@discordjs/voice"; -import type { - DiscordPlayerOwner, - DiscordPlayOptions, -} from "./mediaTypes.js"; +import type { DiscordPlayerOwner, DiscordPlayOptions } from "./mediaTypes.js"; export class DiscordPlayer { private player: AudioPlayer; diff --git a/services/discord-gateway/src/modules/voice-recording/recorder.ts b/services/discord-gateway/src/modules/voice-recording/recorder.ts index f48bb7c..687edf8 100644 --- a/services/discord-gateway/src/modules/voice-recording/recorder.ts +++ b/services/discord-gateway/src/modules/voice-recording/recorder.ts @@ -1,5 +1,7 @@ import fs from "node:fs"; import path from "node:path"; +import { createChildLogger } from "@bete/shared/logger"; +import { retryWithBackoff } from "@bete/shared/utils"; import { type DiscordGatewayAdapterCreator, EndBehaviorType, @@ -11,7 +13,7 @@ import { } from "@discordjs/voice"; import type { Client, VoiceChannel } from "discord.js-selfbot-v13"; import { config } from "../../shared/config/config.js"; -import { createChildLogger } from "@bete/shared/logger"; +import type { PcmBroadcaster } from "../message-capture/types.js"; import { PacketFilter } from "./packetFilter.js"; import { subscribeToAudioStream } from "./recorder/audioStream.js"; import { OpusDecoder } from "./recorder/decoder.js"; @@ -26,8 +28,6 @@ import { type RecordingSession, } from "./recorder/sessionRecording.js"; import { uploadRecordingSegment } from "./recorder/uploader.js"; -import { retryWithBackoff } from "@bete/shared/utils"; -import type { PcmBroadcaster } from "../message-capture/types.js"; const logger = createChildLogger("recorder"); diff --git a/services/discord-gateway/src/modules/voice-recording/recorder/metadata.ts b/services/discord-gateway/src/modules/voice-recording/recorder/metadata.ts index 399fe0c..fefa261 100644 --- a/services/discord-gateway/src/modules/voice-recording/recorder/metadata.ts +++ b/services/discord-gateway/src/modules/voice-recording/recorder/metadata.ts @@ -1,7 +1,11 @@ import path from "node:path"; import type { Client, VoiceChannel } from "discord.js-selfbot-v13"; import { config } from "../../../shared/config/config.js"; -import type { SegmentMetadata, SegmentState, UserMetadata } from "../../message-capture/types.js"; +import type { + SegmentMetadata, + SegmentState, + UserMetadata, +} from "../../message-capture/types.js"; export async function collectUserMetadata( client: Client, diff --git a/services/discord-gateway/src/modules/voice-recording/recorder/sessionRecording.ts b/services/discord-gateway/src/modules/voice-recording/recorder/sessionRecording.ts index 8b9132a..739ea6e 100644 --- a/services/discord-gateway/src/modules/voice-recording/recorder/sessionRecording.ts +++ b/services/discord-gateway/src/modules/voice-recording/recorder/sessionRecording.ts @@ -1,10 +1,10 @@ import fs from "node:fs"; import path from "node:path"; +import type { UserMetadata } from "../../message-capture/types.js"; import { buildMuxFfmpegArgs, runFfmpeg as defaultRunFfmpeg, } from "../ffmpegProcess.js"; -import type { UserMetadata } from "../../message-capture/types.js"; export type SessionRecordingStatus = | "pending" diff --git a/services/discord-gateway/src/modules/voice-recording/recorder/uploader.ts b/services/discord-gateway/src/modules/voice-recording/recorder/uploader.ts index 9eab419..50b5b53 100644 --- a/services/discord-gateway/src/modules/voice-recording/recorder/uploader.ts +++ b/services/discord-gateway/src/modules/voice-recording/recorder/uploader.ts @@ -1,12 +1,12 @@ import fs from "node:fs"; import path from "node:path"; +import { createChildLogger } from "@bete/shared/logger"; import { config } from "../../../shared/config/config.js"; import { insertVoiceRecording, updateVoiceRecordingAsFailed, updateVoiceRecordingAsUploaded, } from "../../../shared/database/voiceRecordingRepo.js"; -import { createChildLogger } from "@bete/shared/logger"; import { uploadToTele } from "../teleUpload.js"; const logger = createChildLogger("recording-uploader"); diff --git a/services/discord-gateway/src/modules/voice-recording/teleUpload.ts b/services/discord-gateway/src/modules/voice-recording/teleUpload.ts index 91716d8..633ad50 100644 --- a/services/discord-gateway/src/modules/voice-recording/teleUpload.ts +++ b/services/discord-gateway/src/modules/voice-recording/teleUpload.ts @@ -37,14 +37,8 @@ export async function uploadToTele(input: { timeoutMs?: number; retries: number; }): Promise { - const { - buffer, - filename, - contentType, - uploadUrl, - timeoutMs, - retries, - } = input; + const { buffer, filename, contentType, uploadUrl, timeoutMs, retries } = + input; const response = await retryWithBackoff( async () => { diff --git a/services/discord-gateway/src/modules/voice-recording/voiceController.ts b/services/discord-gateway/src/modules/voice-recording/voiceController.ts index 25f8761..7bafe8a 100644 --- a/services/discord-gateway/src/modules/voice-recording/voiceController.ts +++ b/services/discord-gateway/src/modules/voice-recording/voiceController.ts @@ -1,7 +1,7 @@ -import { getVoiceConnection, type VoiceConnection } from "@discordjs/voice"; -import type { Client, Guild, VoiceChannel } from "discord.js-selfbot-v13"; import { AppError } from "@bete/shared/errors"; import { createChildLogger } from "@bete/shared/logger"; +import { getVoiceConnection, type VoiceConnection } from "@discordjs/voice"; +import type { Client, Guild, VoiceChannel } from "discord.js-selfbot-v13"; import { discordPlayer } from "./player.js"; import { startRecording, stopRecording } from "./recorder.js"; diff --git a/services/discord-gateway/src/shared/database/drizzle.ts b/services/discord-gateway/src/shared/database/drizzle.ts index 3843779..e1ee1dd 100644 --- a/services/discord-gateway/src/shared/database/drizzle.ts +++ b/services/discord-gateway/src/shared/database/drizzle.ts @@ -1,8 +1,8 @@ +import { createChildLogger } from "@bete/shared/logger"; import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; import type { PoolClient } from "pg"; import { Pool } from "pg"; import { config } from "../../shared/config/config.js"; -import { createChildLogger } from "@bete/shared/logger"; import * as schema from "./schema.js"; const logger = createChildLogger("drizzle"); diff --git a/services/discord-gateway/src/shared/database/migrate.ts b/services/discord-gateway/src/shared/database/migrate.ts index d4d2830..1f0950e 100644 --- a/services/discord-gateway/src/shared/database/migrate.ts +++ b/services/discord-gateway/src/shared/database/migrate.ts @@ -1,10 +1,10 @@ import "dotenv/config"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; -import type { PoolClient } from "pg"; +import { createChildLogger } from "@bete/shared/logger"; import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator"; -import { createChildLogger } from "@bete/shared/logger"; +import type { PoolClient } from "pg"; import { closeDatabase, initializeDatabase, @@ -44,7 +44,9 @@ async function getFirstMigrationTag(): Promise { const journal: MigrationJournal = JSON.parse(raw); if (!journal.entries || journal.entries.length === 0) { - throw new Error("Migration journal is empty — cannot determine first migration tag"); + throw new Error( + "Migration journal is empty — cannot determine first migration tag", + ); } // Entries are ordered by idx — the first entry is the initial migration. @@ -110,7 +112,10 @@ async function seedDrizzleHistory(client: PoolClient): Promise { [firstMigrationTag, Date.now()], ); } - logger.info({ firstMigrationTag }, "Drizzle history seeded — first migration marked applied"); + logger.info( + { firstMigrationTag }, + "Drizzle history seeded — first migration marked applied", + ); } export async function runMigrations(): Promise { diff --git a/services/discord-gateway/src/shared/database/schema.ts b/services/discord-gateway/src/shared/database/schema.ts index 3c6f835..cd5ffbf 100644 --- a/services/discord-gateway/src/shared/database/schema.ts +++ b/services/discord-gateway/src/shared/database/schema.ts @@ -497,4 +497,5 @@ export type StickerCacheRecord = typeof stickerCacheTable.$inferSelect; export type StickerCacheInsert = typeof stickerCacheTable.$inferInsert; export type CorrectedModeration = typeof correctedModerationsTable.$inferSelect; -export type CorrectedModerationInsert = typeof correctedModerationsTable.$inferInsert; +export type CorrectedModerationInsert = + typeof correctedModerationsTable.$inferInsert; diff --git a/services/discord-gateway/src/shared/database/voiceRecordingRepo.ts b/services/discord-gateway/src/shared/database/voiceRecordingRepo.ts index 8030a17..14df6d6 100644 --- a/services/discord-gateway/src/shared/database/voiceRecordingRepo.ts +++ b/services/discord-gateway/src/shared/database/voiceRecordingRepo.ts @@ -1,5 +1,5 @@ -import { desc, eq } from "drizzle-orm"; import { createChildLogger } from "@bete/shared/logger"; +import { desc, eq } from "drizzle-orm"; import { getDatabase } from "./drizzle.js"; import { type VoiceRecording,