style: format code for improved readability and consistency across multiple files

This commit is contained in:
MythEclipse
2026-05-31 16:54:15 +07:00
parent f224be2a66
commit 30ce607d88
14 changed files with 131 additions and 57 deletions
+9 -3
View File
@@ -107,7 +107,9 @@ async function skipAgeRestrictedMessages(
getModerationBroadcaster()?.messageAnalyzed(row); getModerationBroadcaster()?.messageAnalyzed(row);
} }
const skippedIds = new Set(ageRestrictedMessages.map((message) => message.id)); const skippedIds = new Set(
ageRestrictedMessages.map((message) => message.id),
);
return messages.filter((message) => !skippedIds.has(message.id)); return messages.filter((message) => !skippedIds.has(message.id));
} }
@@ -798,7 +800,10 @@ export async function queueMessageAnalysis(messageId: string): Promise<void> {
if (updated) { if (updated) {
getModerationBroadcaster()?.messageAnalyzed(updated); getModerationBroadcaster()?.messageAnalyzed(updated);
} }
logger.info({ messageId }, "Skipped AI analysis for age-restricted message"); logger.info(
{ messageId },
"Skipped AI analysis for age-restricted message",
);
return; return;
} }
@@ -902,7 +907,8 @@ export function startPendingAIAnalysisWorker(client?: Client): void {
config.AI_ANALYSIS_INDIVIDUAL_MAX_CONCURRENT, config.AI_ANALYSIS_INDIVIDUAL_MAX_CONCURRENT,
) )
.then(async (msgs) => { .then(async (msgs) => {
const processableMessages = await skipAgeRestrictedMessages(msgs); const processableMessages =
await skipAgeRestrictedMessages(msgs);
return processableMessages; return processableMessages;
}) })
.then((msgs) => { .then((msgs) => {
+3 -7
View File
@@ -1,8 +1,8 @@
import type { Client, PermissionString } from "discord.js-selfbot-v13"; import type { Client, PermissionString } from "discord.js-selfbot-v13";
import { config } from "../config.js"; import { config } from "../config.js";
import { createChildLogger } from "../logger.js"; import { createChildLogger } from "../logger.js";
import type { MessageRecord } from "./types.js";
import { createModerationAction } from "./messageStore.js"; import { createModerationAction } from "./messageStore.js";
import type { MessageRecord } from "./types.js";
const logger = createChildLogger("auto-delete-manager"); const logger = createChildLogger("auto-delete-manager");
@@ -183,9 +183,7 @@ function isAlreadyDeletedError(error: unknown): boolean {
return code === 10008 || code === 404 || code === "10008" || code === "404"; return code === 10008 || code === 404 || code === "10008" || code === "404";
} }
function hasChannelMessagesApi( function hasChannelMessagesApi(channel: unknown): channel is {
channel: unknown,
): channel is {
messages: { messages: {
fetch: (id: string) => Promise<{ delete: () => Promise<unknown> }>; fetch: (id: string) => Promise<{ delete: () => Promise<unknown> }>;
}; };
@@ -200,9 +198,7 @@ function hasChannelMessagesApi(
); );
} }
function hasPermissionApi( function hasPermissionApi(channel: unknown): channel is {
channel: unknown,
): channel is {
permissionsFor: ( permissionsFor: (
member: unknown, member: unknown,
) => { has: (permission: string) => boolean } | null; ) => { has: (permission: string) => boolean } | null;
+14 -6
View File
@@ -1,9 +1,9 @@
import axios from "axios"; import axios from "axios";
import OpenAI from "openai"; import OpenAI from "openai";
import { config } from "../config.js"; import { config } from "../config.js";
import { INDONESIAN_SLANG_LEXICON } from "./resources/indonesianSlangLexicon.js";
import { createChildLogger } from "../logger.js"; import { createChildLogger } from "../logger.js";
import { retryWithBackoff } from "../retry.js"; import { retryWithBackoff } from "../retry.js";
import { INDONESIAN_SLANG_LEXICON } from "./resources/indonesianSlangLexicon.js";
const log = createChildLogger("indonesianTextNormalizer"); const log = createChildLogger("indonesianTextNormalizer");
@@ -259,7 +259,10 @@ function getPrimaryModerationClient(): OpenAI | null {
} }
function normalizePrimaryAiFlag(value: string): string | null { function normalizePrimaryAiFlag(value: string): string | null {
const lower = value.trim().toLowerCase().replace(/[\s-]+/g, "_"); const lower = value
.trim()
.toLowerCase()
.replace(/[\s-]+/g, "_");
if (!lower) return null; if (!lower) return null;
if (VALID_PRIMARY_AI_FLAGS.has(lower)) { if (VALID_PRIMARY_AI_FLAGS.has(lower)) {
@@ -337,7 +340,7 @@ async function callPrimaryAiModeration(text: string): Promise<string[]> {
role: "user", role: "user",
content: content:
"Deteksi kata kasar / pelanggaran ringan dari teks Indonesia berikut. " + "Deteksi kata kasar / pelanggaran ringan dari teks Indonesia berikut. " +
"Balas hanya JSON object dengan format {\"flags\":[...]} dan gunakan hanya flag valid ini: " + 'Balas hanya JSON object dengan format {"flags":[...]} dan gunakan hanya flag valid ini: ' +
Array.from(VALID_PRIMARY_AI_FLAGS).join(", ") + Array.from(VALID_PRIMARY_AI_FLAGS).join(", ") +
". Jika tidak ada pelanggaran, flags harus array kosong. Teks: " + ". Jika tidak ada pelanggaran, flags harus array kosong. Teks: " +
text, text,
@@ -478,9 +481,12 @@ export async function detectIndonesianBadwords(
hits.add(hit); hits.add(hit);
} }
} catch (error) { } catch (error) {
const status = axios.isAxiosError(error) ? error.response?.status : null; const status = axios.isAxiosError(error)
? error.response?.status
: null;
if (status === 429) { if (status === 429) {
nemotronUnavailableUntil = Date.now() + NEMOTRON_RATE_LIMIT_COOLDOWN_MS; nemotronUnavailableUntil =
Date.now() + NEMOTRON_RATE_LIMIT_COOLDOWN_MS;
} }
log.warn( log.warn(
{ error }, { error },
@@ -497,7 +503,9 @@ export async function detectIndonesianBadwords(
hits.add(hit); hits.add(hit);
} }
} catch (error) { } catch (error) {
const status = axios.isAxiosError(error) ? error.response?.status : null; const status = axios.isAxiosError(error)
? error.response?.status
: null;
if (status === 429) { if (status === 429) {
primaryAiUnavailableUntil = primaryAiUnavailableUntil =
Date.now() + PRIMARY_AI_RATE_LIMIT_COOLDOWN_MS; Date.now() + PRIMARY_AI_RATE_LIMIT_COOLDOWN_MS;
+11 -6
View File
@@ -6,16 +6,16 @@ import { createChildLogger } from "../logger.js";
import { retryWithBackoff } from "../retry.js"; import { retryWithBackoff } from "../retry.js";
import { formatModerationTextEvidenceForPrompt } from "./indonesianTextNormalizer.js"; import { formatModerationTextEvidenceForPrompt } from "./indonesianTextNormalizer.js";
import { extractMessageMediaEvidence } from "./messageMetadata.js"; import { extractMessageMediaEvidence } from "./messageMetadata.js";
import {
buildStickerTextOnlyWarning,
buildStickerVisionPrompt,
} from "./stickerPrompt.js";
import { import {
getStickerFromCache, getStickerFromCache,
initStickerCache, initStickerCache,
isStickerCacheReady, isStickerCacheReady,
setStickerInCache, setStickerInCache,
} from "./stickerCache.js"; } from "./stickerCache.js";
import {
buildStickerTextOnlyWarning,
buildStickerVisionPrompt,
} from "./stickerPrompt.js";
import type { import type {
AnalysisResult, AnalysisResult,
AttachmentRecord, AttachmentRecord,
@@ -244,7 +244,12 @@ export function parseModerationResponse(
return ( return (
Array.isArray(val) && Array.isArray(val) &&
val.length > 0 && val.length > 0 &&
val.every((item: unknown) => typeof item === "object" && item !== null && "message_id" in (item as any)) val.every(
(item: unknown) =>
typeof item === "object" &&
item !== null &&
"message_id" in (item as any),
)
); );
}); });
if (arrayKey) { if (arrayKey) {
@@ -311,7 +316,7 @@ export function parseModerationResponse(
flags: flags ?? [], flags: flags ?? [],
score: normalizedScore, score: normalizedScore,
analysis: coalescedAnalysis, analysis: coalescedAnalysis,
categories: categories ?? (flags ?? []), categories: categories ?? flags ?? [],
severity: normalizedSeverity, severity: normalizedSeverity,
confidence: normalizedConfidence, confidence: normalizedConfidence,
recommendedAction: recommendedAction:
+6 -2
View File
@@ -87,7 +87,10 @@ export function getMessageLocation(message: Message): MessageLocation {
threadId: null, threadId: null,
threadName: null, threadName: null,
channelName: "name" in channel ? channel.name : null, channelName: "name" in channel ? channel.name : null,
nsfw: typeof safetyChannel.nsfw === "boolean" ? safetyChannel.nsfw : undefined, nsfw:
typeof safetyChannel.nsfw === "boolean"
? safetyChannel.nsfw
: undefined,
nsfwLevel: nsfwLevel:
typeof safetyChannel.nsfwLevel === "string" typeof safetyChannel.nsfwLevel === "string"
? safetyChannel.nsfwLevel ? safetyChannel.nsfwLevel
@@ -104,7 +107,8 @@ export function getMessageLocation(message: Message): MessageLocation {
threadId: channel.id, threadId: channel.id,
threadName: channel.name, threadName: channel.name,
channelName: channel.parent?.name ?? null, channelName: channel.parent?.name ?? null,
nsfw: typeof safetyChannel.nsfw === "boolean" ? safetyChannel.nsfw : undefined, nsfw:
typeof safetyChannel.nsfw === "boolean" ? safetyChannel.nsfw : undefined,
nsfwLevel: nsfwLevel:
typeof safetyChannel.nsfwLevel === "string" typeof safetyChannel.nsfwLevel === "string"
? safetyChannel.nsfwLevel ? safetyChannel.nsfwLevel
+2 -2
View File
@@ -1,3 +1,4 @@
import { and, eq, isNull, lt } from "drizzle-orm";
import { getDatabase } from "../database/drizzle.js"; import { getDatabase } from "../database/drizzle.js";
import { import {
attachmentsTable, attachmentsTable,
@@ -6,9 +7,8 @@ import {
voiceRecordingsTable, voiceRecordingsTable,
} from "../database/schema.js"; } from "../database/schema.js";
import { createChildLogger } from "../logger.js"; import { createChildLogger } from "../logger.js";
import { getExpiredMessages, getRetentionPolicy } from "./messageStore.js"; import { getRetentionPolicy } from "./messageStore.js";
import type { RetentionPolicy } from "./types.js"; import type { RetentionPolicy } from "./types.js";
import { and, eq, isNull, lt, sql } from "drizzle-orm";
const logger = createChildLogger("retention-manager"); const logger = createChildLogger("retention-manager");
+1 -1
View File
@@ -1,4 +1,4 @@
import { mkdir, readFile, writeFile, unlink } from "node:fs/promises"; import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import { join } from "node:path"; import { join } from "node:path";
import { createChildLogger } from "../logger.js"; import { createChildLogger } from "../logger.js";
+4 -1
View File
@@ -5,7 +5,10 @@ import {
getAnalysisQueueStatus, getAnalysisQueueStatus,
queueMessageAnalysis, queueMessageAnalysis,
} from "../moderation/aiAnalyzer.js"; } from "../moderation/aiAnalyzer.js";
import { searchMessages, updateMessageAIAnalysis } from "../moderation/messageStore.js"; import {
searchMessages,
updateMessageAIAnalysis,
} from "../moderation/messageStore.js";
import type { MessageRecord } from "../moderation/types.js"; import type { MessageRecord } from "../moderation/types.js";
export function createAnalysisRoutes(): Router { export function createAnalysisRoutes(): Router {
+51 -7
View File
@@ -1,14 +1,15 @@
import process from "node:process"; import process from "node:process";
import { import { Pool } from "pg";
getDatabase, import { getDatabase, initializeDatabase } from "../../src/database/drizzle";
initializeDatabase,
} from "../../src/database/drizzle";
interface RunnableDatabase { interface RunnableDatabase {
run(sql: string): Promise<unknown>; run(sql: string): Promise<unknown>;
} }
const SAFE_TEST_DATABASE_NAME = /(^|[_-])(test|testing)([_-]|$)|gmw_test/i; const SAFE_TEST_DATABASE_NAME = /(^|[_-])(test|testing)([_-]|$)|gmw_test/i;
const DEFAULT_TEST_SCHEMA = "gmw_test";
const SAFE_TEST_SCHEMA_NAME =
/^[a-zA-Z_][a-zA-Z0-9_]*(test|testing)[a-zA-Z0-9_]*$/i;
function getDatabaseNameFromUrl(databaseUrl: string): string { function getDatabaseNameFromUrl(databaseUrl: string): string {
try { try {
@@ -26,6 +27,41 @@ function getConfiguredDatabaseName(): string {
return process.env.POSTGRES_DB ?? ""; return process.env.POSTGRES_DB ?? "";
} }
function getTestSchemaName(): string {
const schemaName = process.env.TEST_DATABASE_SCHEMA ?? DEFAULT_TEST_SCHEMA;
if (!SAFE_TEST_SCHEMA_NAME.test(schemaName)) {
throw new Error(
`Refusing to use unsafe test schema "${schemaName}". Schema name must contain "test" and use identifier-safe characters only.`,
);
}
return schemaName;
}
function quoteIdentifier(identifier: string): string {
return `"${identifier.replace(/"/g, '""')}"`;
}
async function ensureTestSchemaExists(): Promise<void> {
assertSafeTestDatabaseUrl();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const schemaName = getTestSchemaName();
try {
await pool.query(
`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(schemaName)}`,
);
} finally {
await pool.end();
}
}
async function configureTestSearchPath(): Promise<void> {
const db = getTestDatabase();
await db.run(
`SET search_path TO ${quoteIdentifier(getTestSchemaName())}, public`,
);
}
export function assertSafeTestDatabaseUrl(): void { export function assertSafeTestDatabaseUrl(): void {
if (process.env.NODE_ENV !== "test") { if (process.env.NODE_ENV !== "test") {
throw new Error( throw new Error(
@@ -38,16 +74,24 @@ export function assertSafeTestDatabaseUrl(): void {
} }
const databaseName = getConfiguredDatabaseName(); const databaseName = getConfiguredDatabaseName();
if (!SAFE_TEST_DATABASE_NAME.test(databaseName)) { const hasSafeSchema = Boolean(process.env.TEST_DATABASE_SCHEMA);
if (!SAFE_TEST_DATABASE_NAME.test(databaseName) && !hasSafeSchema) {
throw new Error( throw new Error(
`Refusing to run destructive database test against non-test database "${databaseName || "unknown"}". Set TEST_DATABASE_URL or DATABASE_URL to a database whose name contains "test" (for example hub_test).`, `Refusing to run destructive database test against non-test database "${databaseName || "unknown"}" without TEST_DATABASE_SCHEMA. Set TEST_DATABASE_SCHEMA to a safe test schema name or use a database whose name contains "test" (for example hub_test).`,
); );
} }
if (hasSafeSchema) {
getTestSchemaName();
}
} }
export async function initializeTestDatabase() { export async function initializeTestDatabase() {
assertSafeTestDatabaseUrl(); assertSafeTestDatabaseUrl();
return initializeDatabase(); await ensureTestSchemaExists();
const database = await initializeDatabase();
await configureTestSearchPath();
return database;
} }
export function getTestDatabase(): RunnableDatabase { export function getTestDatabase(): RunnableDatabase {
+10 -4
View File
@@ -1,17 +1,23 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("../../src/moderation/indonesianTextNormalizer.js", async (importOriginal) => { vi.mock(
const actual = await importOriginal<typeof import("../../src/moderation/indonesianTextNormalizer.js")>(); "../../src/moderation/indonesianTextNormalizer.js",
async (importOriginal) => {
const actual =
await importOriginal<
typeof import("../../src/moderation/indonesianTextNormalizer.js")
>();
return { return {
...actual, ...actual,
formatModerationTextEvidenceForPrompt: vi.fn(async (content: string) => { formatModerationTextEvidenceForPrompt: vi.fn(async (content: string) => {
// Deterministic mock evidence — length tuned for the "tight budget" test: // Deterministic mock evidence — length tuned for the "tight budget" test:
// maxTokens=300, target ~88, c3 ~108, c2 ~108, c1 ~108 // maxTokens=300, target ~88, c3 ~108, c2 ~108, c1 ~108
// Expectation: target+c3 fits (196), target+c3+c2 overflows (304) // Expectation: target+c3 fits (196), target+c3+c2 overflows (304)
return "[text_evidence] categories=[\"offensive\",\"profanity\",\"sexual_violence\"] severity=high confidence=0.92 language=id detected=badword normalized=false metadata_v2=true context=true"; return '[text_evidence] categories=["offensive","profanity","sexual_violence"] severity=high confidence=0.92 language=id detected=badword normalized=false metadata_v2=true context=true';
}), }),
}; };
}); },
);
import { import {
buildConversationContext, buildConversationContext,
@@ -1,4 +1,5 @@
import { afterAll, afterEach, describe, expect, it } from "vitest"; import { afterAll, afterEach, describe, expect, it } from "vitest";
import { config } from "../../src/config";
import { import {
buildModerationTextEvidence, buildModerationTextEvidence,
detectIndonesianBadwords, detectIndonesianBadwords,
@@ -6,7 +7,6 @@ import {
normalizeDiscordCustomEmoji, normalizeDiscordCustomEmoji,
normalizeIndonesianSlang, normalizeIndonesianSlang,
} from "../../src/moderation/indonesianTextNormalizer"; } from "../../src/moderation/indonesianTextNormalizer";
import { config } from "../../src/config";
const originalNemotronKey = config.NVIDIA_NEMOTRON_API_KEY; const originalNemotronKey = config.NVIDIA_NEMOTRON_API_KEY;
const originalPrimaryAiKey = config.AI_LLM_API_KEY; const originalPrimaryAiKey = config.AI_LLM_API_KEY;
@@ -105,7 +105,9 @@ describe("formatModerationTextEvidenceForPrompt", () => {
expect(formatted).toContain("[emoji:hadeh]"); expect(formatted).toContain("[emoji:hadeh]");
expect(formatted).toContain("[normalization_notes:"); expect(formatted).toContain("[normalization_notes:");
// The NVIDIA API may or may not detect badwords for this input // The NVIDIA API may or may not detect badwords for this input
expect(formatted).toMatch(/no Indonesian badword detected|Indonesian badword detected/); expect(formatted).toMatch(
/no Indonesian badword detected|Indonesian badword detected/,
);
}); });
it("includes normalized text even for clean input", async () => { it("includes normalized text even for clean input", async () => {
+2 -2
View File
@@ -8,13 +8,13 @@ import {
vi, vi,
} from "vitest"; } from "vitest";
import { closeDatabase } from "../../src/database/drizzle"; import { closeDatabase } from "../../src/database/drizzle";
import { captureMessage } from "../../src/moderation/messageCapture";
import type { ModerationBroadcaster } from "../../src/moderation/types";
import { import {
clearTestTables, clearTestTables,
getTestDatabase, getTestDatabase,
initializeTestDatabase, initializeTestDatabase,
} from "../helpers/testDatabase"; } from "../helpers/testDatabase";
import { captureMessage } from "../../src/moderation/messageCapture";
import type { ModerationBroadcaster } from "../../src/moderation/types";
const queueMessageAnalysis = vi.fn(); const queueMessageAnalysis = vi.fn();
+5 -5
View File
@@ -1,10 +1,5 @@
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { closeDatabase } from "../../src/database/drizzle"; import { closeDatabase } from "../../src/database/drizzle";
import {
clearTestTables,
getTestDatabase,
initializeTestDatabase,
} from "../helpers/testDatabase";
import { createChildLogger } from "../../src/logger"; import { createChildLogger } from "../../src/logger";
import { import {
decodeCursor, decodeCursor,
@@ -18,6 +13,11 @@ import {
updateMessageAsEdited, updateMessageAsEdited,
} from "../../src/moderation/messageStore"; } from "../../src/moderation/messageStore";
import type { MessageRecord } from "../../src/moderation/types"; import type { MessageRecord } from "../../src/moderation/types";
import {
clearTestTables,
getTestDatabase,
initializeTestDatabase,
} from "../helpers/testDatabase";
const logger = createChildLogger("messageStoreQueries.test"); const logger = createChildLogger("messageStoreQueries.test");