diff --git a/services/discord-gateway/src/modules/ai-moderation/individualFallbackProcessor.ts b/services/discord-gateway/src/modules/ai-moderation/individualFallbackProcessor.ts index abd81a8..064c54b 100644 --- a/services/discord-gateway/src/modules/ai-moderation/individualFallbackProcessor.ts +++ b/services/discord-gateway/src/modules/ai-moderation/individualFallbackProcessor.ts @@ -155,13 +155,16 @@ async function processIndividualFallback( } const resultSummary = analysisResult.results[0]; - logModerationError([messageId], config.AI_LLM_MODEL, new Error("Success"), { - phase: "individual_fallback", - status: resultSummary?.status, - flags: resultSummary?.flags, - severity: resultSummary?.severity, - confidence: resultSummary?.confidence, - }); + logger.info( + { + phase: "individual_fallback", + status: resultSummary?.status, + flags: resultSummary?.flags, + severity: resultSummary?.severity, + confidence: resultSummary?.confidence, + }, + `Individual fallback moderation complete for ${messageId} (${resultSummary?.status})`, + ); individualConsecutiveErrors = 0; diff --git a/services/discord-gateway/src/modules/ai-moderation/mediaDownloader.ts b/services/discord-gateway/src/modules/ai-moderation/mediaDownloader.ts index 049ef3d..91b93af 100644 --- a/services/discord-gateway/src/modules/ai-moderation/mediaDownloader.ts +++ b/services/discord-gateway/src/modules/ai-moderation/mediaDownloader.ts @@ -198,7 +198,7 @@ async function extractVideoFrames( try { await writeFile(inputPath, videoBytes); const { stdout: durationStr } = await execFileAsync( - "/usr/bin/ffprobe", + "ffprobe", [ "-v", "error", @@ -213,7 +213,7 @@ async function extractVideoFrames( const duration = parseFloat(durationStr.trim()) || 1; const fps = (3 / duration).toFixed(6); await execFileAsync( - "/usr/bin/ffmpeg", + "ffmpeg", [ "-i", inputPath, diff --git a/services/discord-gateway/src/modules/ai-moderation/moderationResponseParser.ts b/services/discord-gateway/src/modules/ai-moderation/moderationResponseParser.ts index 6504b8a..f71b041 100644 --- a/services/discord-gateway/src/modules/ai-moderation/moderationResponseParser.ts +++ b/services/discord-gateway/src/modules/ai-moderation/moderationResponseParser.ts @@ -157,6 +157,22 @@ export function sanitizeErrorMessage( return `Analisis gagal dan memerlukan pemeriksaan manual. Error code: MOD_${Date.now().toString(36).slice(0, 6)}`; } +/** + * Strip generic clean-verdict closing phrases the LLM sometimes appends + * ("Tidak ada indikasi pelanggaran.") even when it already described the + * message content specifically. Keeps the substance, drops the boilerplate. + * Only matches a TRAILING standalone phrase — never mid-analysis text. + */ +export const GENERIC_CLEAN_CLOSER = + /\s*(?:(?:tidak ada (?:indikasi|tanda|temuan)(?: adanya)? pelanggaran(?: kebijakan| aturan)?)|(?:tidak ada (?:indikasi|tanda|temuan)(?: konten| pesan)? (?:yang )?melanggar)|(?:pesan (?:dianggap|tergolong|dinilai) bersih)|(?:tidak ditemukan (?:adanya )?pelanggaran))\.?\s*$/i; + +export function sanitizeGenericCleanCloser(analysis: string): string { + if (!analysis) return analysis; + const cleaned = analysis.replace(GENERIC_CLEAN_CLOSER, "").trim(); + // Drop a dangling separator the removal may have left behind. + return cleaned.replace(/(\s[.\-–—]\s*)$/, "").trim(); +} + export function parseModerationResponse( content: string, targetIds: string[], @@ -250,7 +266,7 @@ export function parseModerationResponse( status: status as "clean" | "warn" | "flagged", flags: flags ?? [], score: normalizedScore, - analysis: coalescedAnalysis, + analysis: sanitizeGenericCleanCloser(coalescedAnalysis), categories: categories ?? flags ?? [], severity: normalizedSeverity, confidence: normalizedConfidence,