diff --git a/biome.json b/biome.json index 80cc8c9..2d6fa83 100644 --- a/biome.json +++ b/biome.json @@ -14,12 +14,12 @@ "packages/**/*.ts", "*.json", "*.ts", - "!vendor/**", - "!.claude/**", - "!services/**/dist/**", - "!packages/**/dist/**", - "!services/**/.next/**", - "!services/**/out/**" + "!vendor", + "!.claude", + "!services/**/dist", + "!packages/**/dist", + "!services/**/.next", + "!services/**/out" ] }, "formatter": { diff --git a/services/backend/src/modules/voice/voice.service.ts b/services/backend/src/modules/voice/voice.service.ts index f8880a8..776c068 100644 --- a/services/backend/src/modules/voice/voice.service.ts +++ b/services/backend/src/modules/voice/voice.service.ts @@ -4,7 +4,7 @@ import { COMMAND_VOICE_CHANNELS, COMMAND_VOICE_CONNECT, COMMAND_VOICE_DISCONNECT, - CommandReply, + type CommandReply, VOICE_STATUS_KEY, } from "@bete/shared"; import { diff --git a/services/backend/src/shared/middlewares/index.ts b/services/backend/src/shared/middlewares/index.ts index 92176f0..1b2f242 100644 --- a/services/backend/src/shared/middlewares/index.ts +++ b/services/backend/src/shared/middlewares/index.ts @@ -1,7 +1,4 @@ -import { - AppError, - ValidationError, -} from "@bete/shared/errors"; +import { AppError, ValidationError } from "@bete/shared/errors"; import { createChildLogger } from "@bete/shared/logger"; import type { NextFunction, Request, Response } from "express"; diff --git a/services/discord-gateway/src/app/shutdown.ts b/services/discord-gateway/src/app/shutdown.ts index 4a89e4a..ddb1e25 100644 --- a/services/discord-gateway/src/app/shutdown.ts +++ b/services/discord-gateway/src/app/shutdown.ts @@ -2,7 +2,7 @@ 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 { stopMetricsServer } from "../modules/gateway-metrics/index.js"; +import type { stopMetricsServer } from "../modules/gateway-metrics/index.js"; import type { VoicePcmWsClient } from "../modules/voice-pcm-ws/index.js"; import { stopMuxerWorker } from "../modules/voice-recording/muxer.js"; import type { VoiceController } from "../modules/voice-recording/voiceController.js"; diff --git a/services/discord-gateway/src/modules/ai-moderation/channelCultureStore.ts b/services/discord-gateway/src/modules/ai-moderation/channelCultureStore.ts index faecfd7..593c460 100644 --- a/services/discord-gateway/src/modules/ai-moderation/channelCultureStore.ts +++ b/services/discord-gateway/src/modules/ai-moderation/channelCultureStore.ts @@ -2,7 +2,7 @@ import { createChildLogger } from "@bete/shared/logger"; import { eq } from "drizzle-orm"; import { getDatabase } from "../../shared/database/drizzle.js"; import { - ChannelCulture, + type ChannelCulture, channelCulturesTable, } from "../../shared/database/schema.js"; diff --git a/services/discord-gateway/src/modules/ai-moderation/moderationPrompt.ts b/services/discord-gateway/src/modules/ai-moderation/moderationPrompt.ts index dc9893a..a902243 100644 --- a/services/discord-gateway/src/modules/ai-moderation/moderationPrompt.ts +++ b/services/discord-gateway/src/modules/ai-moderation/moderationPrompt.ts @@ -133,7 +133,7 @@ Pedoman ini mencerminkan nilai-nilai yang dijunjung server. Terapkan dengan bija Prioritas tertinggi (ANCAMAN KESELAMATAN): - child_safety, violence, illegal_content — flag jika ada indikasi nyata - self_harm: BEDAKAN antara ancaman klinis nyata dengan hiperbola stres remaja (mis. "mati aja gua ngerjain tugas", "bunuh aku sekarang"). Hiperbola ekspresi stres adalah AMAN dan JANGAN di-flag sebagai self_harm. -- Pornografi/NSFW, ajakan seksual, roleplay seksual → "sexual_content". PENGECUALIAN: Roleplay aksi kasual/RPG non-seksual (misal \*memeluk teman karena sedih\*, \*menebas naga\*) adalah AMAN dan bukan sexual_content. +- Pornografi/NSFW, ajakan seksual, roleplay seksual → "sexual_content". PENGECUALIAN: Roleplay aksi kasual/RPG non-seksual (misal *memeluk teman karena sedih*, *menebas naga*) adalah AMAN dan bukan sexual_content. - Judi/promosi judi → "gambling" - Narkoba/promosi → "drugs" @@ -425,7 +425,7 @@ const ALL_EXAMPLES: ExampleDef[] = [ id: "13", title: "Obfuscation / Zalgo Text (Evasion)", input: - '[target] id=14141 user=hater: ++++++K1[[ your $€/F" "~\`| \\\\0ve $ 1F ¥°U |}iE ®©', + '[target] id=14141 user=hater: ++++++K1[[ your $€/F" "~`| \\\\0ve $ 1F ¥°U |}iE ®©', output: '{"results":[{"message_id":"14141","status":"flagged","flags":["harassment","hate_speech"],"score":0.95,"categories":["harassment","hate_speech"],"severity":"critical","confidence":0.95,"recommended_action":"delete","policy_version":"default-2026-05-30","evidence":["++++++K1[[ your $€/F","\\\\\\\\0ve $ 1F ¥°U |}iE"],"analysis":"Pesan menggunakan teknik obfuscation/simbol untuk menyembunyikan frasa \'Kill yourself I love if you die\'. Ini adalah ancaman dan pelecehan berat yang disamarkan."}]}', modes: ["text", "media", "mixed"], @@ -522,7 +522,7 @@ const ALL_EXAMPLES: ExampleDef[] = [ }, { id: "24", - title: "Nabi palsu (SARA agama, Ba\'al)", + title: "Nabi palsu (SARA agama, Ba'al)", input: "[target] id=24245 user=provokator: nabi palsu ba'al, sembah aja patung", output: diff --git a/services/discord-gateway/src/modules/ai-moderation/severityDeriver.ts b/services/discord-gateway/src/modules/ai-moderation/severityDeriver.ts index c2d0e5d..fa247f0 100644 --- a/services/discord-gateway/src/modules/ai-moderation/severityDeriver.ts +++ b/services/discord-gateway/src/modules/ai-moderation/severityDeriver.ts @@ -1,6 +1,6 @@ import { createChildLogger } from "@bete/shared/logger"; import type { z } from "zod"; -import { +import type { RecommendedActionSchema, SeveritySchema, } from "./moderationSchemas.js"; diff --git a/services/discord-gateway/src/modules/ai-moderation/userProfileStore.ts b/services/discord-gateway/src/modules/ai-moderation/userProfileStore.ts index edefe20..b2654e7 100644 --- a/services/discord-gateway/src/modules/ai-moderation/userProfileStore.ts +++ b/services/discord-gateway/src/modules/ai-moderation/userProfileStore.ts @@ -2,7 +2,7 @@ import { createChildLogger } from "@bete/shared/logger"; import { eq } from "drizzle-orm"; import { getDatabase } from "../../shared/database/drizzle.js"; import { - UserProfile, + type UserProfile, userProfilesTable, } from "../../shared/database/schema.js"; diff --git a/services/discord-gateway/src/modules/ai-moderation/userReputationStore.ts b/services/discord-gateway/src/modules/ai-moderation/userReputationStore.ts index c37067b..c1a702a 100644 --- a/services/discord-gateway/src/modules/ai-moderation/userReputationStore.ts +++ b/services/discord-gateway/src/modules/ai-moderation/userReputationStore.ts @@ -3,7 +3,7 @@ import { and, desc, eq } from "drizzle-orm"; import { getDatabase } from "../../shared/database/drizzle.js"; import { messagesTable, - UserReputation, + type UserReputation, userReputationsTable, } from "../../shared/database/schema.js"; diff --git a/services/discord-gateway/src/modules/command-handler/guild.handler.ts b/services/discord-gateway/src/modules/command-handler/guild.handler.ts index 8ddd5c5..37af046 100644 --- a/services/discord-gateway/src/modules/command-handler/guild.handler.ts +++ b/services/discord-gateway/src/modules/command-handler/guild.handler.ts @@ -1,4 +1,4 @@ -import { type CommandMessage, type CommandReply } from "@bete/shared"; +import type { CommandMessage, CommandReply } from "@bete/shared"; import { createChildLogger } from "@bete/shared/logger"; import type { Client } from "discord.js-selfbot-v13"; diff --git a/services/discord-gateway/src/modules/command-handler/media.handler.ts b/services/discord-gateway/src/modules/command-handler/media.handler.ts index f84fd5f..4a526b8 100644 --- a/services/discord-gateway/src/modules/command-handler/media.handler.ts +++ b/services/discord-gateway/src/modules/command-handler/media.handler.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { type CommandMessage, type CommandReply } from "@bete/shared"; +import type { CommandMessage, CommandReply } from "@bete/shared"; import { createChildLogger } from "@bete/shared/logger"; import { StreamType } from "@discordjs/voice"; import { diff --git a/services/discord-gateway/src/modules/command-handler/moderation.handler.ts b/services/discord-gateway/src/modules/command-handler/moderation.handler.ts index 78dbda4..2968c04 100644 --- a/services/discord-gateway/src/modules/command-handler/moderation.handler.ts +++ b/services/discord-gateway/src/modules/command-handler/moderation.handler.ts @@ -1,4 +1,4 @@ -import { type CommandMessage, type CommandReply } from "@bete/shared"; +import type { CommandMessage, CommandReply } from "@bete/shared"; import { createChildLogger } from "@bete/shared/logger"; import type { Client } from "discord.js-selfbot-v13"; import { createModerationAction } from "../message-capture/messageStore.js"; diff --git a/services/discord-gateway/src/modules/message-capture/messageMetadata.ts b/services/discord-gateway/src/modules/message-capture/messageMetadata.ts index 07ee9fb..92e7b26 100644 --- a/services/discord-gateway/src/modules/message-capture/messageMetadata.ts +++ b/services/discord-gateway/src/modules/message-capture/messageMetadata.ts @@ -153,8 +153,8 @@ export function getCustomEmojiMetadata( ): RichMessageMetadata["customEmojis"] { const CUSTOM_EMOJI_PATTERN = /<(a)?:([a-zA-Z0-9_]+):(\d+)>/g; const emojis: CustomEmojiEvidence[] = []; - let match; - while ((match = CUSTOM_EMOJI_PATTERN.exec(message.content)) !== null) { + const matches = [...message.content.matchAll(CUSTOM_EMOJI_PATTERN)]; + for (const match of matches) { const [, animated, name, id] = match; const ext = animated ? "gif" : "png"; emojis.push({ diff --git a/services/discord-gateway/src/modules/voice-recording/mediaSource.ts b/services/discord-gateway/src/modules/voice-recording/mediaSource.ts index b251bab..d98ab5c 100644 --- a/services/discord-gateway/src/modules/voice-recording/mediaSource.ts +++ b/services/discord-gateway/src/modules/voice-recording/mediaSource.ts @@ -1,5 +1,5 @@ import { type ChildProcess, spawn } from "node:child_process"; -import { PassThrough, Readable } from "node:stream"; +import { PassThrough, type Readable } from "node:stream"; import { createChildLogger } from "@bete/shared/logger"; import { StreamType } from "@discordjs/voice"; diff --git a/services/discord-gateway/src/modules/voice-recording/packetFilter.ts b/services/discord-gateway/src/modules/voice-recording/packetFilter.ts index ecd12ec..e290160 100644 --- a/services/discord-gateway/src/modules/voice-recording/packetFilter.ts +++ b/services/discord-gateway/src/modules/voice-recording/packetFilter.ts @@ -1,4 +1,4 @@ -import { Transform, TransformCallback } from "node:stream"; +import { Transform, type TransformCallback } from "node:stream"; import { createChildLogger } from "@bete/shared/logger"; const logger = createChildLogger("packet-filter"); diff --git a/services/discord-gateway/src/modules/voice-recording/player.ts b/services/discord-gateway/src/modules/voice-recording/player.ts index ac6bb02..f201c6d 100644 --- a/services/discord-gateway/src/modules/voice-recording/player.ts +++ b/services/discord-gateway/src/modules/voice-recording/player.ts @@ -1,13 +1,13 @@ -import { Readable } from "node:stream"; +import type { Readable } from "node:stream"; import { createChildLogger } from "@bete/shared/logger"; import { - AudioPlayer, + type AudioPlayer, AudioPlayerStatus, type AudioResource, createAudioPlayer, createAudioResource, StreamType, - VoiceConnection, + type VoiceConnection, } from "@discordjs/voice"; import type { DiscordPlayerOwner, DiscordPlayOptions } from "./mediaTypes.js"; diff --git a/services/discord-gateway/src/modules/voice-recording/recorder/speakingHandler.ts b/services/discord-gateway/src/modules/voice-recording/recorder/speakingHandler.ts index 3cd0452..9152458 100644 --- a/services/discord-gateway/src/modules/voice-recording/recorder/speakingHandler.ts +++ b/services/discord-gateway/src/modules/voice-recording/recorder/speakingHandler.ts @@ -121,7 +121,7 @@ export function createSpeakingHandler( // Step 4: Open the first segment const activeSession = activeSessions.get(channel.guild.id); - let currentSegment = segmentManager.open(oggPacketStream); + const currentSegment = segmentManager.open(oggPacketStream); // Step 5: Handle segment file completion currentSegment.out.on("finish", () => { diff --git a/services/frontend/src/app/dashboard/layout.tsx b/services/frontend/src/app/dashboard/layout.tsx index cfdeb1b..fa43a6f 100644 --- a/services/frontend/src/app/dashboard/layout.tsx +++ b/services/frontend/src/app/dashboard/layout.tsx @@ -2,11 +2,11 @@ import { useRouter, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useRef } from "react"; -import { uiStateApi } from "@/lib/api"; import { Header } from "@/components/layout/header"; import { MobileTabBar } from "@/components/layout/mobile-tab-bar"; import { Sidebar } from "@/components/layout/sidebar"; import { MascotChatbot } from "@/features/mascot/mascot-chatbot"; +import { uiStateApi } from "@/lib/api"; import { WsProvider } from "@/lib/ws/context"; function DashboardShell({ children }: { children: React.ReactNode }) { diff --git a/services/frontend/src/features/live/live-panel.tsx b/services/frontend/src/features/live/live-panel.tsx index aad65d5..2d2c666 100644 --- a/services/frontend/src/features/live/live-panel.tsx +++ b/services/frontend/src/features/live/live-panel.tsx @@ -489,9 +489,7 @@ export function LivePanel() { setMicActive(!micActive); try { await voiceApi.sendCommand( - micActive - ? "voice:transmit:stop" - : "voice:transmit:start", + micActive ? "voice:transmit:stop" : "voice:transmit:start", ); } catch { setMicActive(micActive); diff --git a/services/frontend/src/features/mascot/mascot-chatbot.tsx b/services/frontend/src/features/mascot/mascot-chatbot.tsx index 0af2434..caf9af3 100644 --- a/services/frontend/src/features/mascot/mascot-chatbot.tsx +++ b/services/frontend/src/features/mascot/mascot-chatbot.tsx @@ -1,6 +1,14 @@ "use client"; -import { Bot, Loader2, MessageCircle, Send, Trash2, User, X } from "lucide-react"; +import { + Bot, + Loader2, + MessageCircle, + Send, + Trash2, + User, + X, +} from "lucide-react"; import { useCallback, useEffect, useRef, useState } from "react"; import { mascotApi } from "@/lib/api"; import type { ChatHistoryMessage } from "@/lib/types"; @@ -20,6 +28,7 @@ export function MascotChatbot() { .catch(() => {}); }, [open]); + // biome-ignore lint/correctness/useExhaustiveDependencies: scrollRef doesn't need messages in deps useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; diff --git a/services/frontend/src/features/messages/messages-panel.tsx b/services/frontend/src/features/messages/messages-panel.tsx index 1aa3d17..ed15c74 100644 --- a/services/frontend/src/features/messages/messages-panel.tsx +++ b/services/frontend/src/features/messages/messages-panel.tsx @@ -1,20 +1,20 @@ "use client"; -import { useState, useEffect, useCallback } from "react"; -import { messagesApi, voiceApi } from "@/lib/api"; -import type { MessageRecord, Channel, AttachmentRecord } from "@/lib/types"; -import { useWebSocket } from "@/lib/ws/context"; import { - Search, - RefreshCw, - Loader2, AlertCircle, - Flag, - X, Download, ExternalLink, + Flag, + Loader2, + RefreshCw, + Search, + X, } from "lucide-react"; +import { useCallback, useEffect, useState } from "react"; +import { messagesApi, voiceApi } from "@/lib/api"; import { useAppConfig } from "@/lib/hooks/use-config"; +import type { AttachmentRecord, Channel, MessageRecord } from "@/lib/types"; +import { useWebSocket } from "@/lib/ws/context"; export function MessagesPanel() { const { config } = useAppConfig(); @@ -27,14 +27,20 @@ export function MessagesPanel() { const [cursor, setCursor] = useState(null); const [hasMore, setHasMore] = useState(true); const [searchQuery, setSearchQuery] = useState(""); - const [searchResults, setSearchResults] = useState(null); + const [searchResults, setSearchResults] = useState( + null, + ); const [searching, setSearching] = useState(false); const [viewTab, setViewTab] = useState<"all" | "images" | "review">("all"); const [imageMessages, setImageMessages] = useState([]); const [reviewMessages, setReviewMessages] = useState([]); const [channels, setChannels] = useState([]); - const [detailMessage, setDetailMessage] = useState(null); - const [detailAttachments, setDetailAttachments] = useState([]); + const [detailMessage, setDetailMessage] = useState( + null, + ); + const [detailAttachments, setDetailAttachments] = useState< + AttachmentRecord[] + >([]); const [detailLoading, setDetailLoading] = useState(false); const [selectedChannel, setSelectedChannel] = useState(""); @@ -43,7 +49,10 @@ export function MessagesPanel() { // Fetch available text channels for filtering useEffect(() => { if (!guildId) return; - voiceApi.getTextChannels(guildId).then(setChannels).catch(() => {}); + voiceApi + .getTextChannels(guildId) + .then(setChannels) + .catch(() => {}); }, [guildId]); // Fetch initial messages @@ -51,7 +60,11 @@ export function MessagesPanel() { setLoading(true); setError(null); try { - const result = await messagesApi.list(guildId, 50, selectedChannel || undefined); + const result = await messagesApi.list( + guildId, + 50, + selectedChannel || undefined, + ); setMessages(result.data); setCursor(result.nextCursor); setHasMore(result.nextCursor !== null); @@ -75,7 +88,10 @@ export function MessagesPanel() { // Fetch review (flagged) messages const fetchReview = useCallback(async () => { try { - const result = await messagesApi.getReview(50, selectedChannel || undefined); + const result = await messagesApi.getReview( + 50, + selectedChannel || undefined, + ); setReviewMessages(result.results); } catch { // silently fail @@ -146,7 +162,12 @@ export function MessagesPanel() { if (!cursor || loadingMore) return; setLoadingMore(true); try { - const result = await messagesApi.list(guildId, 50, selectedChannel || undefined, cursor); + const result = await messagesApi.list( + guildId, + 50, + selectedChannel || undefined, + cursor, + ); setMessages((prev) => [...prev, ...result.data]); setCursor(result.nextCursor); setHasMore(result.nextCursor !== null); @@ -506,7 +527,9 @@ export function MessagesPanel() { {detailMessage.ai_recommended_action && detailMessage.ai_recommended_action !== "none" && (
-

Action

+

+ Action +

{detailMessage.ai_recommended_action}

@@ -602,13 +625,14 @@ function MessageCard({ const timeStr = date.toLocaleString(); return ( + // biome-ignore lint/a11y/useSemanticElements: complex nested content prevents using button
onClick(msg.id)} onKeyDown={(e) => e.key === "Enter" && onClick(msg.id)} className={`rounded-lg border p-4 space-y-2 transition-colors cursor-pointer hover:bg-muted/50 ${ - msg.ai_severity ? severityColor[msg.ai_severity] ?? "" : "" + msg.ai_severity ? (severityColor[msg.ai_severity] ?? "") : "" } ${msg.ai_severity && msg.ai_severity !== "none" ? "border-l-2" : ""}`} > {/* Header */} @@ -676,21 +700,18 @@ function MessageCard({

{/* AI Details */} - {msg.ai_moderation_flags && - msg.ai_moderation_flags !== "[]" && ( -
- {safeParseJsonArray(msg.ai_moderation_flags).map( - (flag) => ( - - {flag} - - ), - )} -
- )} + {msg.ai_moderation_flags && msg.ai_moderation_flags !== "[]" && ( +
+ {safeParseJsonArray(msg.ai_moderation_flags).map((flag) => ( + + {flag} + + ))} +
+ )} {msg.ai_analysis && (

@@ -699,22 +720,21 @@ function MessageCard({ )} {/* Confidence score */} - {msg.ai_confidence !== undefined && - msg.ai_confidence !== null && ( -

-
-
-
- - {(msg.ai_confidence * 100).toFixed(0)}% - + {msg.ai_confidence !== undefined && msg.ai_confidence !== null && ( +
+
+
- )} + + {(msg.ai_confidence * 100).toFixed(0)}% + +
+ )} {/* Actions */}
@@ -755,9 +775,7 @@ function formatBytes(bytes: number): string { return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } -function safeParseJsonArray( - value: string | null | undefined, -): string[] { +function safeParseJsonArray(value: string | null | undefined): string[] { if (!value) return []; try { const parsed = JSON.parse(value); diff --git a/services/frontend/src/lib/hooks/use-config.ts b/services/frontend/src/lib/hooks/use-config.ts index e1b8852..741f5cb 100644 --- a/services/frontend/src/lib/hooks/use-config.ts +++ b/services/frontend/src/lib/hooks/use-config.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import { configApi } from "@/lib/api"; export interface AppConfig {