A tool for parsing traffic on the jetstream and applying a moderation workstream based on regexp based rules

Refactor: Move LINK_SHORTENER to constants file

Move the `LINK_SHORTENER` constant from `rules/posts.ts` to
`rules/constants.ts` and remove the now duplicate declaration.

Skywatch 71e5b0c4 f82874d3

+1 -1
eslint.config.mjs
··· 1 1 import eslint from "@eslint/js"; 2 2 import stylistic from "@stylistic/eslint-plugin"; 3 - import { defineConfig } from "eslint/config"; 4 3 import prettier from "eslint-config-prettier"; 5 4 import importPlugin from "eslint-plugin-import"; 5 + import { defineConfig } from "eslint/config"; 6 6 import tseslint from "typescript-eslint"; 7 7 8 8 export default defineConfig(
+2
rules/constants.ts
··· 6 6 export const GLOBAL_ALLOW: string[] = [ 7 7 // Example: "did:plc:example123", 8 8 ]; 9 + 10 + export const LINK_SHORTENER = new RegExp("", "i");
-5
rules/posts.ts
··· 16 16 // check: new RegExp("example-pattern", "i"), 17 17 // }, 18 18 ]; 19 - 20 - /** 21 - * Link shortener detection pattern 22 - */ 23 - export const LINK_SHORTENER = new RegExp("", "i");
+2 -2
src/config.ts
··· 20 20 export const CURSOR_UPDATE_INTERVAL = process.env.CURSOR_UPDATE_INTERVAL 21 21 ? Number(process.env.CURSOR_UPDATE_INTERVAL) 22 22 : 60000; 23 - export const {LABEL_LIMIT} = process.env; 24 - export const {LABEL_LIMIT_WAIT} = process.env; 23 + export const { LABEL_LIMIT } = process.env; 24 + export const { LABEL_LIMIT_WAIT } = process.env; 25 25 export const REDIS_URL = process.env.REDIS_URL ?? "redis://redis:6379";
+21 -18
src/main.ts
··· 2 2 import type { 3 3 CommitCreateEvent, 4 4 CommitUpdateEvent, 5 - IdentityEvent} from "@skyware/jetstream"; 6 - import { 7 - Jetstream, 5 + IdentityEvent, 8 6 } from "@skyware/jetstream"; 7 + import { Jetstream } from "@skyware/jetstream"; 9 8 import { 10 9 CURSOR_UPDATE_INTERVAL, 11 10 FIREHOSE_URL, ··· 112 111 "app.bsky.feed.post", 113 112 (event: CommitCreateEvent<"app.bsky.feed.post">) => { 114 113 const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`; 115 - const hasEmbed = Object.prototype.hasOwnProperty.call(event.commit.record, "embed"); 116 - const hasFacets = Object.prototype.hasOwnProperty.call(event.commit.record, "facets"); 117 - const hasText = Object.prototype.hasOwnProperty.call(event.commit.record, "text"); 114 + const hasEmbed = Object.prototype.hasOwnProperty.call( 115 + event.commit.record, 116 + "embed", 117 + ); 118 + const hasFacets = Object.prototype.hasOwnProperty.call( 119 + event.commit.record, 120 + "facets", 121 + ); 122 + const hasText = Object.prototype.hasOwnProperty.call( 123 + event.commit.record, 124 + "text", 125 + ); 118 126 119 127 const tasks: Promise<void>[] = []; 120 128 ··· 136 144 137 145 // Check account age for quote posts 138 146 if (hasEmbed) { 139 - const {embed} = event.commit.record; 147 + const { embed } = event.commit.record; 140 148 if ( 141 149 embed && 142 150 typeof embed === "object" && ··· 170 178 if (hasFacets) { 171 179 // Check for facet spam (hidden mentions with duplicate byte positions) 172 180 const facets = event.commit.record.facets ?? null; 173 - tasks.push( 174 - checkFacetSpam( 175 - event.did, 176 - event.time_us, 177 - atURI, 178 - facets, 179 - ), 180 - ); 181 + tasks.push(checkFacetSpam(event.did, event.time_us, atURI, facets)); 181 182 182 183 const hasLinkType = facets?.some((facet) => 183 184 facet.features.some( ··· 225 226 } 226 227 227 228 if (hasEmbed) { 228 - const {embed} = event.commit.record; 229 + const { embed } = event.commit.record; 229 230 if ( 230 231 embed && 231 232 typeof embed === "object" && 232 233 "$type" in embed && 233 234 embed.$type === "app.bsky.embed.external" 234 235 ) { 235 - const {external} = embed as { external: { uri: string } }; 236 + const { external } = embed as { external: { uri: string } }; 236 237 const posts: Post[] = [ 237 238 { 238 239 did: event.did, ··· 252 253 "$type" in embed && 253 254 embed.$type === "app.bsky.embed.recordWithMedia" 254 255 ) { 255 - const {media} = embed as { media: { $type: string; external?: { uri: string } } }; 256 + const { media } = embed as { 257 + media: { $type: string; external?: { uri: string } }; 258 + }; 256 259 if (media.$type === "app.bsky.embed.external" && media.external) { 257 260 const posts: Post[] = [ 258 261 {
-3
src/rules/account/tests/countStarterPacks.test.ts
··· 1 - 2 - 3 - 4 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 5 2 import { createAccountLabel } from "../../../accountModeration.js"; 6 3 import { agent } from "../../../agent.js";
+5 -1
src/rules/facets/facets.ts
··· 52 52 positionMap.set(key, new Set()); 53 53 } 54 54 const dids = positionMap.get(key); 55 - if (dids && "did" in mentionFeature && typeof mentionFeature.did === "string") { 55 + if ( 56 + dids && 57 + "did" in mentionFeature && 58 + typeof mentionFeature.did === "string" 59 + ) { 56 60 dids.add(mentionFeature.did); 57 61 } 58 62 }
-1
src/rules/facets/tests/facets.test.ts
··· 1 - 2 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 3 2 import { createAccountLabel } from "../../../accountModeration.js"; 4 3 import { logger } from "../../../logger.js";
+1 -8
src/rules/handles/checkHandles.test.ts
··· 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 9 2 import { 10 3 createAccountComment, ··· 277 270 }); 278 271 279 272 it("should handle very long handles", async () => { 280 - const longHandle = `spam-${ "a".repeat(1000)}`; 273 + const longHandle = `spam-${"a".repeat(1000)}`; 281 274 const time = Date.now(); 282 275 await checkHandle("did:plc:user1", longHandle, time); 283 276
+4 -1
src/rules/handles/checkHandles.ts
··· 58 58 { process: "CHECKHANDLE", did, handle, time, label: checkList.label }, 59 59 "Reporting account", 60 60 ); 61 - void createAccountReport(did, `${time.toString()}: ${checkList.comment} - ${handle}`); 61 + void createAccountReport( 62 + did, 63 + `${time.toString()}: ${checkList.comment} - ${handle}`, 64 + ); 62 65 } 63 66 64 67 if (checkList.commentAcct) {
-6
src/rules/posts/tests/checkPosts.test.ts
··· 1 - 2 - 3 - 4 - 5 - 6 - 7 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 8 2 import { 9 3 createAccountComment,
-6
src/rules/profiles/tests/checkProfiles.test.ts
··· 1 - 2 - 3 - 4 - 5 - 6 - 7 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 8 2 import { 9 3 createAccountComment,
-1
src/tests/accountThreshold.test.ts
··· 1 - 2 1 import { afterEach, describe, expect, it, vi } from "vitest"; 3 2 import { 4 3 createAccountComment,
-1
src/tests/agent.test.ts
··· 1 - 2 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 3 2 4 3 describe("Agent", () => {
-5
src/tests/moderation.test.ts
··· 1 - 2 - 3 - 4 - 5 - 6 1 import { beforeEach, describe, expect, it, vi } from "vitest"; 7 2 // --- Imports Second --- 8 3 import { checkAccountLabels } from "../accountModeration.js";
-1
src/tests/redis.test.ts
··· 1 - 2 1 // Import the mocked redis first to get a reference to the mock client 3 2 import { createClient } from "redis"; 4 3 import { afterEach, describe, expect, it, vi } from "vitest";
+6 -6
src/tests/session.test.ts
··· 1 - import { describe, it, expect, beforeEach, afterEach } from "vitest"; 2 1 import { 2 + chmodSync, 3 3 existsSync, 4 4 mkdirSync, 5 - rmSync, 6 - writeFileSync, 7 5 readFileSync, 6 + rmSync, 8 7 unlinkSync, 9 - chmodSync, 8 + writeFileSync, 10 9 } from "node:fs"; 11 10 import { join } from "node:path"; 11 + import { afterEach, beforeEach, describe, expect, it } from "vitest"; 12 12 import type { SessionData } from "../session.js"; 13 13 14 14 const TEST_DIR = join(process.cwd(), ".test-session"); ··· 136 136 writeFileSync( 137 137 TEST_SESSION_PATH, 138 138 JSON.stringify({ accessJwt: "token" }), 139 - "utf-8" 139 + "utf-8", 140 140 ); 141 141 142 142 const session = testLoadSession(); ··· 151 151 refreshJwt: "refresh", 152 152 handle: "test.bsky.social", 153 153 }), 154 - "utf-8" 154 + "utf-8", 155 155 ); 156 156 157 157 const session = testLoadSession();