From 5588ece6c7dac454ff9f235e495f0b540689d9ef Mon Sep 17 00:00:00 2001 From: MythEclipse Date: Thu, 21 May 2026 03:52:51 +0700 Subject: [PATCH] feat(moderation): normalize JSON response handling and enhance test coverage for moderation analysis --- pnpm-lock.yaml | 34 ++++++++++++++++-- src/moderation/llmModerationClient.ts | 11 +++++- tests/moderation/llmModerationClient.test.ts | 37 +++++++++++++++++++- 3 files changed, 78 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ebdadd..fa5a0b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -179,8 +179,8 @@ importers: specifier: ^25.0.1 version: 25.8.0 discord.js-selfbot-v13: - specifier: workspace:* - version: link:../discord.js-selfbot-v13 + specifier: ^3.7.1 + version: 3.7.1 pkg-pr-new: specifier: ^0.0.62 version: 0.0.62 @@ -2370,6 +2370,11 @@ packages: discord-api-types@0.38.47: resolution: {integrity: sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA==} + discord.js-selfbot-v13@3.7.1: + resolution: {integrity: sha512-cq5AW/CVvNIUVTSBdZmhsob7v+wjxnkFjuNULcxBXvxutVBnSZqZupsT/9CDtdnT71iKUn9N8GGL6GPg9aZlGA==} + engines: {node: '>=20.18'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + dmd@4.0.6: resolution: {integrity: sha512-7ZYAnFQ6jGm4SICArwqNPylJ83PaOdPTAkds3Z/s1ueFqSc5ilJ2F0b7uP+35W1PUbemH++gn5/VlC3KwEgiHQ==} engines: {node: '>=8'} @@ -6376,6 +6381,31 @@ snapshots: discord-api-types@0.38.47: {} + discord.js-selfbot-v13@3.7.1: + dependencies: + '@discordjs/builders': 1.14.1 + '@discordjs/collection': 2.1.1 + '@sapphire/async-queue': 1.5.5 + '@sapphire/shapeshift': 4.0.0 + discord-api-types: 0.38.47 + fetch-cookie: 3.2.0 + find-process: 2.1.1 + otplib: 12.0.1 + prism-media: 1.3.5 + qrcode: 1.5.4 + tough-cookie: 5.1.2 + tree-kill: 1.2.2 + undici: 7.25.0 + werift-rtp: 0.8.8 + ws: 8.20.1 + transitivePeerDependencies: + - '@discordjs/opus' + - bufferutil + - ffmpeg-static + - node-opus + - opusscript + - utf-8-validate + dmd@4.0.6: dependencies: array-back: 4.0.2 diff --git a/src/moderation/llmModerationClient.ts b/src/moderation/llmModerationClient.ts index f61aae6..63234eb 100644 --- a/src/moderation/llmModerationClient.ts +++ b/src/moderation/llmModerationClient.ts @@ -19,7 +19,16 @@ const openai = new OpenAI({ ? await response.text() : JSON.stringify(await response.json()); - return new Response(body, { + let normalizedBody = body; + if (response.ok !== false) { + try { + JSON.parse(body); + } catch { + normalizedBody = JSON.stringify(extractJson(body)); + } + } + + return new Response(normalizedBody, { status: response.status ?? 200, headers: { "Content-Type": "application/json" }, }); diff --git a/tests/moderation/llmModerationClient.test.ts b/tests/moderation/llmModerationClient.test.ts index 9e18906..6f0fb0a 100644 --- a/tests/moderation/llmModerationClient.test.ts +++ b/tests/moderation/llmModerationClient.test.ts @@ -481,7 +481,7 @@ describe("runModerationAnalysis", () => { ).rejects.toThrow(/500/); }); - it("parses first JSON object when provider appends extra JSON", async () => { + it("parses first JSON object when provider appends extra text to message content", async () => { const moderationJson = JSON.stringify({ results: [ { @@ -517,6 +517,41 @@ describe("runModerationAnalysis", () => { expect(result.results[0].messageId).toBe("m1"); }); + it("normalizes first JSON object when provider appends extra text to HTTP body", async () => { + const mockResponse = { + choices: [ + { + message: { + content: JSON.stringify({ + results: [ + { + message_id: "m1", + status: "clean", + flags: [], + score: 0.1, + analysis: "OK", + }, + ], + }), + }, + }, + ], + }; + + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + text: async () => `${JSON.stringify(mockResponse)}\nextra`, + }); + + const result = await runModerationAnalysis({ + targets: [createMessageRecord()], + contextText: "test context", + }); + + expect(result.results).toHaveLength(1); + expect(result.results[0].messageId).toBe("m1"); + }); + it("throws on missing choices in response", async () => { global.fetch = vi.fn().mockResolvedValue({ ok: true,