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

Fix: Use `plc.directory` and improve account age lookups

Use the correct PLC URL.

Improve account age lookup by fetching the audit log from the PLC and
using `createdAt` instead of `indexedAt` from the profile.

Skywatch 11ee91be 2f32063f

+14 -15
+1 -1
src/config.ts
··· 12 12 : 4101; // Left this intact from the code I adapted this from 13 13 export const FIREHOSE_URL = 14 14 process.env.FIREHOSE_URL ?? "wss://jetstream.atproto.tools/subscribe"; 15 - export const PLC_URL = process.env.PLC_URL ?? "plc.wtf"; 15 + export const PLC_URL = process.env.PLC_URL ?? "plc.directory"; 16 16 export const WANTED_COLLECTION = [ 17 17 "app.bsky.feed.post", 18 18 "app.bsky.actor.defs",
+10 -11
src/rules/account/age.ts
··· 25 25 // For plc DIDs, try to extract creation from the DID document 26 26 if (did.startsWith("did:plc:")) { 27 27 try { 28 - const response = await fetch(`https://${PLC_URL}/${did}`); 28 + const response = await fetch(`https://${PLC_URL}/${did}/log/audit`); 29 29 if (response.ok) { 30 30 const didDoc = await response.json(); 31 31 ··· 53 53 // Fallback: try getting profile for any DID type 54 54 try { 55 55 const profile = await agent.getProfile({ actor: did }); 56 - if (profile.data.indexedAt) { 57 - return new Date(profile.data.indexedAt); 56 + if (profile.data.createdAt) { 57 + return new Date(profile.data.createdAt); 58 58 } 59 59 } catch (profileError) { 60 - logger.debug( 61 - { process: "ACCOUNT_AGE", did }, 62 - "Failed to get profile", 63 - ); 60 + logger.debug({ process: "ACCOUNT_AGE", did }, "Failed to get profile"); 64 61 } 65 62 66 63 logger.warn( ··· 91 88 /** 92 89 * Checks if a reply meets age criteria and applies labels accordingly 93 90 */ 94 - export const checkAccountAge = async ( 95 - context: ReplyContext, 96 - ): Promise<void> => { 91 + export const checkAccountAge = async (context: ReplyContext): Promise<void> => { 97 92 // Skip if no checks configured 98 93 if (ACCOUNT_AGE_CHECKS.length === 0) { 99 94 return; ··· 102 97 // Skip if DID is globally allowlisted 103 98 if (GLOBAL_ALLOW.includes(context.replyingDid)) { 104 99 logger.debug( 105 - { process: "ACCOUNT_AGE", did: context.replyingDid, atURI: context.atURI }, 100 + { 101 + process: "ACCOUNT_AGE", 102 + did: context.replyingDid, 103 + atURI: context.atURI, 104 + }, 106 105 "Global allowlisted DID", 107 106 ); 108 107 return;
+3 -3
src/rules/account/tests/age.test.ts
··· 101 101 const result = await getAccountCreationDate("did:plc:test123"); 102 102 103 103 expect(global.fetch).toHaveBeenCalledWith( 104 - "https://plc.wtf/did:plc:test123", 104 + "https://plc.directory/did:plc:test123/log/audit", 105 105 ); 106 106 expect(result).toEqual(new Date("2025-01-10T12:00:00.000Z")); 107 107 }); 108 108 109 - it("should fall back to profile.indexedAt if plc lookup fails", async () => { 109 + it("should fall back to profile.createdAt if plc lookup fails", async () => { 110 110 (global.fetch as any).mockResolvedValueOnce({ 111 111 ok: false, 112 112 }); 113 113 114 114 (agent.getProfile as any).mockResolvedValueOnce({ 115 115 data: { 116 - indexedAt: "2025-01-12T10:00:00.000Z", 116 + createdAt: "2025-01-12T10:00:00.000Z", 117 117 }, 118 118 }); 119 119