chore(format): auto-format files with biome

This commit is contained in:
MythEclipse
2026-06-04 18:52:56 +07:00
parent 367d8696c8
commit d1d4510ce8
33 changed files with 342 additions and 157 deletions
+3 -1
View File
@@ -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",
},
});
+1 -1
View File
@@ -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<typeof createChildLogger>;
type CloseDatabase = typeof closeDatabase;
+1 -1
View File
@@ -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");
@@ -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<any> | 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<WorkerResponse> {
export default async function workerRouter(
job: WorkerJob,
): Promise<WorkerResponse> {
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<WorkerRespon
} catch (dbError) {
const msg = dbError instanceof Error ? dbError.message : String(dbError);
if (job.type === "batch") {
return { ok: false, conversationKey: job.conversationKey, rows: [], error: `Database init failed: ${msg}` };
return {
ok: false,
conversationKey: job.conversationKey,
rows: [],
error: `Database init failed: ${msg}`,
};
}
return { ok: false, results: [], error: `Database init failed: ${msg}` };
}
@@ -72,16 +103,23 @@ export default async function workerRouter(job: WorkerJob): Promise<WorkerRespon
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const errorStack = error instanceof Error ? error.stack : undefined;
console.error(JSON.stringify({
level: "ERROR",
context: "aiAnalysisWorker",
type: job.type,
error: errorMessage,
stack: errorStack,
timestamp: new Date().toISOString(),
}));
console.error(
JSON.stringify({
level: "ERROR",
context: "aiAnalysisWorker",
type: job.type,
error: errorMessage,
stack: errorStack,
timestamp: new Date().toISOString(),
}),
);
if (job.type === "batch") {
return { ok: false, conversationKey: job.conversationKey, rows: [], error: errorMessage };
return {
ok: false,
conversationKey: job.conversationKey,
rows: [],
error: errorMessage,
};
}
return { ok: false, results: [], error: errorMessage };
}
@@ -91,7 +129,11 @@ export default async function workerRouter(job: WorkerJob): Promise<WorkerRespon
// Batch handler
// ---------------------------------------------------------------------------
async function processBatch(job: { type: "batch"; conversationKey: string; messages: MessageRecord[] }): Promise<BatchOkResponse | BatchErrorResponse> {
async function processBatch(job: {
type: "batch";
conversationKey: string;
messages: MessageRecord[];
}): Promise<BatchOkResponse | BatchErrorResponse> {
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<IndividualOkResponse | IndividualErrorResponse> {
async function processIndividual(job: {
type: "individual";
message: MessageRecord;
skipNormalAnalysis: boolean;
}): Promise<IndividualOkResponse | IndividualErrorResponse> {
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[];
@@ -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:** <t:${Math.floor(Date.now() / 1000)}:R>`,
`**Status:** ${message.ai_status}\n` +
`**Severitas:** ${severity}\n` +
`**Kategori:** ${categories}\n` +
`**Isi:** ${snippet}\n` +
`**Waktu:** <t:${Math.floor(Date.now() / 1000)}:R>`,
);
}
} catch (logErr) {
@@ -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
@@ -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";
@@ -19,17 +19,29 @@ const badwordCache = new Map<string, BadwordCacheEntry>();
// 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 "";
@@ -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");
@@ -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";
@@ -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 = {
@@ -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<string | null> {
try {
// Dynamic import — imghash is ESM with a default export containing { hash, hashRaw, ... }
const imghashModule: { default?: { hash?: (buf: Buffer) => Promise<string> } } =
await import("imghash");
const imghashModule: {
default?: { hash?: (buf: Buffer) => Promise<string> };
} = 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[],
@@ -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");
@@ -1,5 +1,5 @@
import sharp from "sharp";
import { createChildLogger } from "@bete/shared/logger";
import sharp from "sharp";
const log = createChildLogger("imageResizer");
@@ -37,14 +37,8 @@ export async function uploadToTele(input: {
timeoutMs?: number;
retries: number;
}): Promise<TeleUploadResult> {
const {
buffer,
filename,
contentType,
uploadUrl,
timeoutMs,
retries,
} = input;
const { buffer, filename, contentType, uploadUrl, timeoutMs, retries } =
input;
const response = await retryWithBackoff(
async () => {
@@ -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";
@@ -1,5 +1,5 @@
import Redis from "ioredis";
import type { CustomLogger } from "@bete/shared/logger";
import Redis from "ioredis";
export interface DiscordGatewayEvent {
type: string;
@@ -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");
@@ -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<WebSocket, "readyState" | "send">;
@@ -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";
@@ -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");
@@ -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,
@@ -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;
@@ -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");
@@ -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,
@@ -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"
@@ -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");
@@ -37,14 +37,8 @@ export async function uploadToTele(input: {
timeoutMs?: number;
retries: number;
}): Promise<TeleUploadResult> {
const {
buffer,
filename,
contentType,
uploadUrl,
timeoutMs,
retries,
} = input;
const { buffer, filename, contentType, uploadUrl, timeoutMs, retries } =
input;
const response = await retryWithBackoff(
async () => {
@@ -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";
@@ -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");
@@ -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<string> {
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<void> {
[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<void> {
@@ -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;
@@ -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,