From 455a3ba78d8ec922a3e237255812141107ff4c07 Mon Sep 17 00:00:00 2001 From: storcale Date: Sun, 4 Oct 2026 10:58:17 +0200 Subject: [PATCH 1/2] Add CL BLs check and fixes --- db/db.js | 2 +- tests/test.js | 16 ++++++++++++++++ utils/clanLabs.js | 29 +++++++++++++++++++++++++++++ utils/group.js | 42 +++++++++++++++++++++++++++--------------- utils/registry.js | 14 ++++++++++++-- 5 files changed, 85 insertions(+), 18 deletions(-) create mode 100644 utils/clanLabs.js diff --git a/db/db.js b/db/db.js index 1d7c5d0..49e9808 100644 --- a/db/db.js +++ b/db/db.js @@ -32,7 +32,7 @@ async function connectDB() { isConnected = false; console.warn('MongoDB disconnected.'); }); - if (process.env.NODE_ENV === "development" || process.env.NODE_EJV === "test") { + if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") { mongoose.set("debug", true); } return mongoose.connection; diff --git a/tests/test.js b/tests/test.js index 2cba63c..d714798 100644 --- a/tests/test.js +++ b/tests/test.js @@ -239,3 +239,19 @@ describe("External Case Registries", () => { }); }); }); +describe("ClanLabs", () => { + test("Get blacklist for user", async () => { + await runTest("Get blacklist for user", async () => { + const getBlacklist = require(path.join(global.__basedir, 'utils/clanLabs.js')); + const result = await getBlacklist("User", "1478099365"); // goggybo + expect(result.name).toBe("goggybo"); + }); + }); + test("Get blacklist for group", async () => { + await runTest("Get blacklist for group", async () => { + const getBlacklist = require(path.join(global.__basedir, 'utils/clanLabs.js')); + const result = await getBlacklist("Group", "35736046"); // Valax + expect(result.name).toBe("Valax ’"); + }); + }); +}); \ No newline at end of file diff --git a/utils/clanLabs.js b/utils/clanLabs.js new file mode 100644 index 0000000..d45478a --- /dev/null +++ b/utils/clanLabs.js @@ -0,0 +1,29 @@ + +const axios = require('axios'); + +/** + * @param {"User"|"Group"} type + * @param {string} id + * @return {Promise} blacklist object, or false if not blacklisted + * @throws if the request fails for any reason other than "not found" + */ +async function getBlacklist(type, id) { + const response = await axios.get( + `https://api.clanlabs.co/v2/blacklists/${type}/${id}`, + { + headers: { + Authorization: "Bearer " + process.env.CL_API_KEY, + "X-Clan-Id": process.env.CL_CLAN_ID, + }, + // don't throw on 404/204 + validateStatus: (status) => (status >= 200 && status < 300) || status === 404, + } + ); + + if (response.status === 404 || response.status === 204) return false; + + return response.data?.data ?? false; +} + +module.exports = getBlacklist; + diff --git a/utils/group.js b/utils/group.js index 6c1ecbf..b18b699 100644 --- a/utils/group.js +++ b/utils/group.js @@ -1,7 +1,8 @@ const axios = require("axios"); const path = require("path"); const Case = require(path.join(global.__basedir, 'db/schemas/eic/case.js')); - const RegistryCase = require(path.join(global.__basedir, 'utils/registry.js')); +const RegistryCase = require(path.join(global.__basedir, 'utils/registry.js')); +const getBlacklist = require(path.join(global.__basedir, 'utils/clanLabs.js')); // send webhook via api async function sendAcceptedWebhook(userId, groupId, username) { let target = process.env.pendingWebhookCode; @@ -243,7 +244,7 @@ class Game extends Group { // try { const response = await axios.get( - `https://apis.roblox.com/universes/v1/places/${placeid}/universe `, + `https://apis.roblox.com/universes/v1/places/${placeid}/universe`, { headers: { "x-api-key": process.env.TREASURY_API_KEY } } ); return response.data; @@ -276,7 +277,12 @@ class User { } class JoinRequest extends Group { - userId = ''; + /** + * Creates an instance of JoinRequest. + * @param {string} [userId=''] + * @param {integer} [groupId=process.env.TNIV_GROUP_ID] + * @memberof JoinRequest + */ constructor(userId = '', groupId = process.env.TNIV_GROUP_ID) { super(groupId); this.userId = userId; @@ -319,7 +325,7 @@ class JoinRequest extends Group { } } - async decline(reasonText,username) { + async decline(reasonText, username) { try { const response = await axios.post( `https://apis.roblox.com/cloud/v2/groups/${this.groupId}/join-requests/${this.userId}:decline`, @@ -339,28 +345,29 @@ class JoinRequest extends Group { } async processPending() { const pending = await this.getJoinRequests(); - const text = "" + let text = ""; + if (!pending || pending.length === 0) { + return "No pending join requests found."; + } for (const request of pending) { - const userId = request.user.split("/").slice(1).join("/");; + const userId = request.user.split("/").slice(1).join("/"); this.userId = userId; try { const result = await processUser(userId); - console.log(userId + result) if (result.result === true) { await this.accept(result.username); - text += result.username + ": Accepted\n" + text += result.username + ": Accepted\n"; } else { - await this.decline(result.reasonText,result.username); - text += result.username + ": Declined" + "\n" + await this.decline(result.reason, result.username); + text += result.username + ": Declined\n"; } - return text } catch (err) { console.error(`[processPending] Error processing user ${userId}:`, err?.response?.data || err.message); - sendErrorWebhook(err?.response?.data || err?.message, "Process Pending", "Pending Join Requests") - throw err - return "An error occurred." + sendErrorWebhook(err, "Process Pending", "Pending Join Requests"); + text += `${userId}: Error (${err.message})\n`; } } + return text; } } @@ -391,7 +398,12 @@ async function processUser(id) { return { result: false, reason: `User ${cases[0].usernames} has active cases in registries: ${cases.map(c => c.source).join(", ")}. \n Category: ${cases[0].category} | Type: ${cases[0].type} | Strike: ${cases[0].strike} \n Notes: ${cases[0].notes} EndDate: ${cases[0].end_date}`, username }; } - // TODO CL BLs + // CL BLs + + const blacklist = await getBlacklist("User", id); + if (blacklist) { + return { result: false, reason: `User is blacklisted in ClanLabs: ${blacklist.name} (${blacklist.reason})`, username }; + } // TODO rotector etc diff --git a/utils/registry.js b/utils/registry.js index cc0b61c..cd523d5 100644 --- a/utils/registry.js +++ b/utils/registry.js @@ -6,10 +6,20 @@ let url = "https://api.keystone-swords.com/api/cases" // TODO cache - +/** + * + * + * @class RegistryCase + */ class RegistryCase { active = true - registry = false + /** + * Creates an instance of RegistryCase. + * @param {string} registry + * @param {string} userId + * @param {boolean} active + * @memberof RegistryCase + */ constructor(registry, userId, active) { this.registry = registry; this.userId = userId; From b62e8a4a5f0c38b42f7b70a48bddd752e37f29ae Mon Sep 17 00:00:00 2001 From: storcale Date: Sun, 4 Oct 2026 11:01:34 +0200 Subject: [PATCH 2/2] Fix test workflow secrets --- .github/workflows/Tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/Tests.yml b/.github/workflows/Tests.yml index c124a45..edd1a89 100644 --- a/.github/workflows/Tests.yml +++ b/.github/workflows/Tests.yml @@ -70,6 +70,8 @@ jobs: TREASURY_ID: ${{ vars.TREASURY_ID }} TNIV_GROUP_ID: ${{ vars.TNIV_GROUP_ID }} OFFICE_CODE: ${{ secrets.OFFICE_CODE }} + CL_API_KEY: ${{ secrets.CL_API_KEY }} + CL_CLAN_ID: ${{ secrets.CL_CLAN_ID }} NODE_OPTIONS: --experimental-vm-modules PUBLIC_DIRS: "admin-ui/login,login,api-docs,health" run: |