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

Fix: Add error handling and cleanup

- Added comprehensive error handling throughout the codebase to ensure
robustness. - Enhanced logging for improved debugging capabilities. -
Improved shutdown process with graceful exit and cleanup.

+3 -4
src/agent.ts
··· 1 - import { AtpAgent } from "@atproto/api"; 2 - 3 - import { setGlobalDispatcher, Agent as Agent } from "undici"; 1 + import { AtpAgent } from '@atproto/api'; 2 + import { setGlobalDispatcher, Agent as Agent } from 'undici'; 4 3 5 4 setGlobalDispatcher(new Agent({ connect: { timeout: 20_000 } })); 6 - import { BSKY_HANDLE, BSKY_PASSWORD, OZONE_PDS } from "./config.js"; 5 + import { BSKY_HANDLE, BSKY_PASSWORD, OZONE_PDS } from './config.js'; 7 6 8 7 export const agent = new AtpAgent({ 9 8 service: `https://${OZONE_PDS}`,
+46 -41
src/checkHandles.ts
··· 11 11 handle: string, 12 12 time: number, 13 13 ) => { 14 - // Get a list of labels 15 - const labels: string[] = Array.from( 16 - HANDLE_CHECKS, 17 - (handleCheck) => handleCheck.label, 18 - ); 19 - 20 - // iterate through the labels 21 - labels.forEach((label) => { 22 - const checkList = HANDLE_CHECKS.find( 23 - (handleCheck) => handleCheck.label === label, 14 + try { 15 + // Get a list of labels 16 + const labels: string[] = Array.from( 17 + HANDLE_CHECKS, 18 + (handleCheck) => handleCheck.label, 24 19 ); 25 20 26 - if (checkList?.ignoredDIDs) { 27 - if (checkList.ignoredDIDs.includes(did)) { 28 - logger.info(`Whitelisted DID: ${did}`); 29 - return; 30 - } 31 - } 21 + // iterate through the labels 22 + labels.forEach((label) => { 23 + const checkList = HANDLE_CHECKS.find( 24 + (handleCheck) => handleCheck.label === label, 25 + ); 32 26 33 - if (checkList!.check.test(handle)) { 34 - // False-positive checks 35 - if (checkList?.whitelist) { 36 - if (checkList?.whitelist.test(handle)) { 37 - logger.info(`Whitelisted phrase found for: ${handle}`); 27 + if (checkList?.ignoredDIDs) { 28 + if (checkList.ignoredDIDs.includes(did)) { 29 + logger.info(`Whitelisted DID: ${did}`); 38 30 return; 39 31 } 40 32 } 41 33 42 - if (checkList?.toLabel === true) { 43 - logger.info(`[CHECKHANDLE]: Labeling ${did} for ${checkList!.label}`); 44 - { 45 - createAccountLabel( 46 - did, 47 - `${checkList!.label}`, 48 - `${time}: ${checkList!.comment} - ${handle}`, 49 - ); 34 + if (checkList!.check.test(handle)) { 35 + // False-positive checks 36 + if (checkList?.whitelist) { 37 + if (checkList?.whitelist.test(handle)) { 38 + logger.info(`Whitelisted phrase found for: ${handle}`); 39 + return; 40 + } 41 + } 42 + 43 + if (checkList?.toLabel === true) { 44 + logger.info(`[CHECKHANDLE]: Labeling ${did} for ${checkList.label}`); 45 + { 46 + createAccountLabel( 47 + did, 48 + checkList.label, 49 + `${time}: ${checkList.comment} - ${handle}`, 50 + ); 51 + } 50 52 } 51 - } 52 53 53 - if (checkList?.reportAcct === true) { 54 - logger.info(`[CHECKHANDLE]: Reporting ${did} for ${checkList!.label}`); 55 - createAccountReport(did, `${time}: ${checkList!.comment} - ${handle}`); 56 - } 54 + if (checkList?.reportAcct === true) { 55 + logger.info(`[CHECKHANDLE]: Reporting ${did} for ${checkList.label}`); 56 + createAccountReport(did, `${time}: ${checkList.comment} - ${handle}`); 57 + } 57 58 58 - if (checkList?.commentAcct === true) { 59 - logger.info( 60 - `[CHECKHANDLE]: Commenting on ${did} for ${checkList!.label}`, 61 - ); 62 - createAccountComment(did, `${time}: ${checkList!.comment} - ${handle}`); 59 + if (checkList?.commentAcct === true) { 60 + logger.info( 61 + `[CHECKHANDLE]: Commenting on ${did} for ${checkList.label}`, 62 + ); 63 + createAccountComment(did, `${time}: ${checkList.comment} - ${handle}`); 64 + } 63 65 } 64 - } 65 - }); 66 + }); 67 + } catch (error) { 68 + logger.error(`Error in checkHandle for ${did}:`, error); 69 + throw error; 70 + } 66 71 };
+94 -89
src/checkPosts.ts
··· 10 10 import { getFinalUrl, getLanguage } from './utils.js'; 11 11 12 12 export const checkPosts = async (post: Post[]) => { 13 - // Get a list of labels 14 - const labels: string[] = Array.from( 15 - POST_CHECKS, 16 - (postCheck) => postCheck.label, 17 - ); 13 + try { 14 + // Get a list of labels 15 + const labels: string[] = Array.from( 16 + POST_CHECKS, 17 + (postCheck) => postCheck.label, 18 + ); 18 19 19 - const urlRegex = /https?:\/\/[^\s]+/g; 20 + const urlRegex = /https?:\/\/[^\s]+/g; 20 21 21 - // Check for link shorteners 22 - if (LINK_SHORTENER.test(post[0].text)) { 23 - try { 24 - const url = post[0].text.match(urlRegex); 25 - if (url && LINK_SHORTENER.test(url[0])) { 26 - logger.info(`[CHECKPOSTS]: Checking shortened URL: ${url[0]}`); 27 - const finalUrl = await getFinalUrl(url[0]); 28 - if (finalUrl) { 29 - const originalUrl = post[0].text; 30 - post[0].text = post[0].text.replace(url[0], finalUrl); 31 - logger.info( 32 - `[CHECKPOSTS]: Shortened URL resolved: ${originalUrl} -> ${finalUrl}`, 33 - ); 22 + // Check for link shorteners 23 + if (LINK_SHORTENER.test(post[0].text)) { 24 + try { 25 + const url = post[0].text.match(urlRegex); 26 + if (url && LINK_SHORTENER.test(url[0])) { 27 + logger.info(`[CHECKPOSTS]: Checking shortened URL: ${url[0]}`); 28 + const finalUrl = await getFinalUrl(url[0]); 29 + if (finalUrl) { 30 + const originalUrl = post[0].text; 31 + post[0].text = post[0].text.replace(url[0], finalUrl); 32 + logger.info( 33 + `[CHECKPOSTS]: Shortened URL resolved: ${originalUrl} -> ${finalUrl}`, 34 + ); 35 + } 34 36 } 35 - } 36 - } catch (error) { 37 - logger.error( 38 - `[CHECKPOSTS]: Failed to resolve shortened URL: ${post[0].text}`, 39 - error, 40 - ); 37 + } catch (error) { 38 + logger.error( 39 + `[CHECKPOSTS]: Failed to resolve shortened URL: ${post[0].text}`, 40 + error, 41 + ); 41 42 // Keep the original URL if resolution fails 43 + } 42 44 } 43 - } 44 45 45 - // Get the post's language 46 - const lang = await getLanguage(post[0].text); 46 + // Get the post's language 47 + const lang = await getLanguage(post[0].text); 47 48 48 - // iterate through the labels 49 - labels.forEach((label) => { 50 - const checkPost = POST_CHECKS.find( 51 - (postCheck) => postCheck.label === label, 52 - ); 49 + // iterate through the labels 50 + labels.forEach((label) => { 51 + const checkPost = POST_CHECKS.find( 52 + (postCheck) => postCheck.label === label, 53 + ); 53 54 54 - if (checkPost?.language || checkPost?.language !== undefined) { 55 - if (!checkPost?.language.includes(lang)) { 56 - return; 55 + if (checkPost?.language || checkPost?.language !== undefined) { 56 + if (!checkPost?.language.includes(lang)) { 57 + return; 58 + } 57 59 } 58 - } 59 60 60 - if (checkPost?.ignoredDIDs) { 61 - if (checkPost?.ignoredDIDs.includes(post[0].did)) { 62 - logger.info(`[CHECKPOSTS]: Whitelisted DID: ${post[0].did}`); 63 - return; 61 + if (checkPost?.ignoredDIDs) { 62 + if (checkPost?.ignoredDIDs.includes(post[0].did)) { 63 + logger.info(`[CHECKPOSTS]: Whitelisted DID: ${post[0].did}`); 64 + return; 65 + } 64 66 } 65 - } 66 67 67 - if (checkPost!.check.test(post[0].text)) { 68 + if (checkPost!.check.test(post[0].text)) { 68 69 // Check if post is whitelisted 69 - if (checkPost?.whitelist) { 70 - if (checkPost?.whitelist.test(post[0].text)) { 71 - logger.info('[CHECKPOSTS]: Whitelisted phrase found"'); 72 - return; 70 + if (checkPost?.whitelist) { 71 + if (checkPost?.whitelist.test(post[0].text)) { 72 + logger.info('[CHECKPOSTS]: Whitelisted phrase found"'); 73 + return; 74 + } 73 75 } 74 - } 75 76 76 - if (checkPost!.toLabel === true) { 77 - logger.info( 78 - `[CHECKPOSTS]: Labeling ${post[0].atURI} for ${checkPost!.label}`, 79 - ); 80 - createPostLabel( 81 - post[0].atURI, 82 - post[0].cid, 83 - `${checkPost!.label}`, 84 - `${post[0].time}: ${checkPost!.comment} at ${post[0].atURI} with text "${post[0].text}"`, 85 - ); 86 - } 77 + if (checkPost!.toLabel) { 78 + logger.info( 79 + `[CHECKPOSTS]: Labeling ${post[0].atURI} for ${checkPost!.label}`, 80 + ); 81 + createPostLabel( 82 + post[0].atURI, 83 + post[0].cid, 84 + checkPost!.label, 85 + `${post[0].time}: ${checkPost!.comment} at ${post[0].atURI} with text "${post[0].text}"`, 86 + ); 87 + } 87 88 88 - if (checkPost!.reportPost === true) { 89 - logger.info( 90 - `[CHECKPOSTS]: Reporting ${post[0].atURI} for ${checkPost!.label}`, 91 - ); 92 - logger.info(`Reporting: ${post[0].atURI}`); 93 - createPostReport( 94 - post[0].atURI, 95 - post[0].cid, 96 - `${post[0].time}: ${checkPost!.comment} at ${post[0].atURI} with text "${post[0].text}"`, 97 - ); 98 - } 89 + if (checkPost!.reportPost === true) { 90 + logger.info( 91 + `[CHECKPOSTS]: Reporting ${post[0].atURI} for ${checkPost!.label}`, 92 + ); 93 + logger.info(`Reporting: ${post[0].atURI}`); 94 + createPostReport( 95 + post[0].atURI, 96 + post[0].cid, 97 + `${post[0].time}: ${checkPost!.comment} at ${post[0].atURI} with text "${post[0].text}"`, 98 + ); 99 + } 99 100 100 - if (checkPost!.reportAcct === true) { 101 - logger.info( 102 - `[CHECKPOSTS]: Reporting on ${post[0].did} for ${checkPost!.label} in ${post[0].atURI}`, 103 - ); 104 - createAccountReport( 105 - post[0].did, 106 - `${post[0].time}: ${checkPost?.comment} at ${post[0].atURI} with text "${post[0].text}"`, 107 - ); 108 - } 101 + if (checkPost!.reportAcct) { 102 + logger.info( 103 + `[CHECKPOSTS]: Reporting on ${post[0].did} for ${checkPost!.label} in ${post[0].atURI}`, 104 + ); 105 + createAccountReport( 106 + post[0].did, 107 + `${post[0].time}: ${checkPost?.comment} at ${post[0].atURI} with text "${post[0].text}"`, 108 + ); 109 + } 109 110 110 - if (checkPost!.commentAcct === true) { 111 - logger.info( 112 - `[CHECKPOSTS]: Commenting on ${post[0].did} for ${checkPost!.label} in ${post[0].atURI}`, 113 - ); 114 - createAccountComment( 115 - post[0].did, 116 - `${post[0].time}: ${checkPost?.comment} at ${post[0].atURI} with text "${post[0].text}"`, 117 - ); 111 + if (checkPost!.commentAcct) { 112 + logger.info( 113 + `[CHECKPOSTS]: Commenting on ${post[0].did} for ${checkPost!.label} in ${post[0].atURI}`, 114 + ); 115 + createAccountComment( 116 + post[0].did, 117 + `${post[0].time}: ${checkPost?.comment} at ${post[0].atURI} with text "${post[0].text}"`, 118 + ); 119 + } 118 120 } 119 - } 120 - }); 121 + }); 122 + } catch (error) { 123 + logger.error(`Error in checkPosts for ${post[0]?.did}:`, error); 124 + throw error; 125 + } 121 126 };
+125 -115
src/checkProfiles.ts
··· 14 14 displayName: string, 15 15 description: string, 16 16 ) => { 17 - const lang = await getLanguage(description); 17 + try { 18 + const lang = await getLanguage(description); 18 19 19 - const labels: string[] = Array.from( 20 - PROFILE_CHECKS, 21 - (profileCheck) => profileCheck.label, 22 - ); 23 - 24 - // iterate through the labels 25 - labels.forEach((label) => { 26 - const checkProfiles = PROFILE_CHECKS.find( 27 - (profileCheck) => profileCheck.label === label, 20 + const labels: string[] = Array.from( 21 + PROFILE_CHECKS, 22 + (profileCheck) => profileCheck.label, 28 23 ); 29 24 30 - if (checkProfiles?.language || checkProfiles?.language !== undefined) { 31 - if (!checkProfiles?.language.includes(lang)) { 32 - return; 25 + // iterate through the labels 26 + labels.forEach((label) => { 27 + const checkProfiles = PROFILE_CHECKS.find( 28 + (profileCheck) => profileCheck.label === label, 29 + ); 30 + 31 + if (checkProfiles?.language || checkProfiles?.language !== undefined) { 32 + if (!checkProfiles?.language.includes(lang)) { 33 + return; 34 + } 33 35 } 34 - } 35 36 36 - // Check if DID is whitelisted 37 - if (checkProfiles?.ignoredDIDs) { 38 - if (checkProfiles.ignoredDIDs.includes(did)) { 39 - logger.info(`[CHECKDESCRIPTION]: Whitelisted DID: ${did}`); 40 - return; 37 + // Check if DID is whitelisted 38 + if (checkProfiles?.ignoredDIDs) { 39 + if (checkProfiles.ignoredDIDs.includes(did)) { 40 + logger.info(`[CHECKDESCRIPTION]: Whitelisted DID: ${did}`); 41 + return; 42 + } 41 43 } 42 - } 43 44 44 - if (description) { 45 - if (checkProfiles?.description === true) { 46 - if (checkProfiles!.check.test(description)) { 45 + if (description) { 46 + if (checkProfiles?.description === true) { 47 + if (checkProfiles.check.test(description)) { 47 48 // Check if description is whitelisted 48 - if (checkProfiles!.whitelist) { 49 - if (checkProfiles!.whitelist.test(description)) { 50 - logger.info('[CHECKDESCRIPTION]: Whitelisted phrase found.'); 51 - return; 49 + if (checkProfiles.whitelist) { 50 + if (checkProfiles.whitelist.test(description)) { 51 + logger.info('[CHECKDESCRIPTION]: Whitelisted phrase found.'); 52 + return; 53 + } 52 54 } 53 - } 54 55 55 - if (checkProfiles!.toLabel === true) { 56 - createAccountLabel( 57 - did, 58 - `${checkProfiles!.label}`, 59 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 60 - ); 61 - logger.info( 62 - `[CHECKDESCRIPTION]: Labeling ${did} for ${checkProfiles!.label}`, 63 - ); 64 - } 56 + if (checkProfiles.toLabel) { 57 + createAccountLabel( 58 + did, 59 + checkProfiles.label, 60 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 61 + ); 62 + logger.info( 63 + `[CHECKDESCRIPTION]: Labeling ${did} for ${checkProfiles.label}`, 64 + ); 65 + } 65 66 66 - if (checkProfiles!.reportAcct === true) { 67 - createAccountReport( 68 - did, 69 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 70 - ); 71 - logger.info( 72 - `[CHECKDESCRIPTION]: Reporting ${did} for ${checkProfiles!.label}`, 73 - ); 74 - } 67 + if (checkProfiles.reportAcct) { 68 + createAccountReport( 69 + did, 70 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 71 + ); 72 + logger.info( 73 + `[CHECKDESCRIPTION]: Reporting ${did} for ${checkProfiles.label}`, 74 + ); 75 + } 75 76 76 - if (checkProfiles!.commentAcct === true) { 77 - createAccountComment( 78 - did, 79 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 80 - ); 81 - logger.info( 82 - `[CHECKDESCRIPTION]: Commenting on ${did} for ${checkProfiles!.label}`, 83 - ); 77 + if (checkProfiles.commentAcct) { 78 + createAccountComment( 79 + did, 80 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 81 + ); 82 + logger.info( 83 + `[CHECKDESCRIPTION]: Commenting on ${did} for ${checkProfiles.label}`, 84 + ); 85 + } 84 86 } 85 87 } 86 88 } 87 - } 88 - }); 89 + }); 90 + } catch (error) { 91 + logger.error(`Error in checkDescription for ${did}:`, error); 92 + throw error; 93 + } 89 94 }; 90 95 91 96 export const checkDisplayName = async ( ··· 94 99 displayName: string, 95 100 description: string, 96 101 ) => { 97 - const lang = await getLanguage(description); 102 + try { 103 + const lang = await getLanguage(description); 98 104 99 - // Get a list of labels 100 - const labels: string[] = Array.from( 101 - PROFILE_CHECKS, 102 - (profileCheck) => profileCheck.label, 103 - ); 104 - 105 - // iterate through the labels 106 - labels.forEach((label) => { 107 - const checkProfiles = PROFILE_CHECKS.find( 108 - (profileCheck) => profileCheck.label === label, 105 + // Get a list of labels 106 + const labels: string[] = Array.from( 107 + PROFILE_CHECKS, 108 + (profileCheck) => profileCheck.label, 109 109 ); 110 110 111 - if (checkProfiles?.language || checkProfiles?.language !== undefined) { 112 - if (!checkProfiles?.language.includes(lang)) { 113 - return; 111 + // iterate through the labels 112 + labels.forEach((label) => { 113 + const checkProfiles = PROFILE_CHECKS.find( 114 + (profileCheck) => profileCheck.label === label, 115 + ); 116 + 117 + if (checkProfiles?.language || checkProfiles?.language !== undefined) { 118 + if (!checkProfiles?.language.includes(lang)) { 119 + return; 120 + } 114 121 } 115 - } 116 122 117 - // Check if DID is whitelisted 118 - if (checkProfiles?.ignoredDIDs) { 119 - if (checkProfiles.ignoredDIDs.includes(did)) { 120 - logger.info(`[CHECKDISPLAYNAME]: Whitelisted DID: ${did}`); 121 - return; 123 + // Check if DID is whitelisted 124 + if (checkProfiles?.ignoredDIDs) { 125 + if (checkProfiles.ignoredDIDs.includes(did)) { 126 + logger.info(`[CHECKDISPLAYNAME]: Whitelisted DID: ${did}`); 127 + return; 128 + } 122 129 } 123 - } 124 130 125 - if (displayName) { 126 - if (checkProfiles?.displayName === true) { 127 - if (checkProfiles!.check.test(displayName)) { 131 + if (displayName) { 132 + if (checkProfiles?.displayName === true) { 133 + if (checkProfiles.check.test(displayName)) { 128 134 // Check if displayName is whitelisted 129 - if (checkProfiles!.whitelist) { 130 - if (checkProfiles!.whitelist.test(displayName)) { 131 - logger.info('[CHECKDISPLAYNAME]: Whitelisted phrase found.'); 132 - return; 135 + if (checkProfiles.whitelist) { 136 + if (checkProfiles.whitelist.test(displayName)) { 137 + logger.info('[CHECKDISPLAYNAME]: Whitelisted phrase found.'); 138 + return; 139 + } 133 140 } 134 - } 135 141 136 - if (checkProfiles!.toLabel === true) { 137 - createAccountLabel( 138 - did, 139 - `${checkProfiles!.label}`, 140 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 141 - ); 142 - logger.info( 143 - `[CHECKDISPLAYNAME]: Labeling ${did} for ${checkProfiles!.label}`, 144 - ); 145 - } 142 + if (checkProfiles.toLabel) { 143 + createAccountLabel( 144 + did, 145 + checkProfiles.label, 146 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 147 + ); 148 + logger.info( 149 + `[CHECKDISPLAYNAME]: Labeling ${did} for ${checkProfiles.label}`, 150 + ); 151 + } 146 152 147 - if (checkProfiles!.reportAcct === true) { 148 - createAccountReport( 149 - did, 150 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 151 - ); 152 - logger.info( 153 - `[CHECKDISPLAYNAME]: Reporting ${did} for ${checkProfiles!.label}`, 154 - ); 155 - } 153 + if (checkProfiles.reportAcct) { 154 + createAccountReport( 155 + did, 156 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 157 + ); 158 + logger.info( 159 + `[CHECKDISPLAYNAME]: Reporting ${did} for ${checkProfiles.label}`, 160 + ); 161 + } 156 162 157 - if (checkProfiles!.commentAcct === true) { 158 - createAccountComment( 159 - did, 160 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 161 - ); 162 - logger.info( 163 - `[CHECKDISPLAYNAME]: Commenting on ${did} for ${checkProfiles!.label}`, 164 - ); 163 + if (checkProfiles.commentAcct) { 164 + createAccountComment( 165 + did, 166 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 167 + ); 168 + logger.info( 169 + `[CHECKDISPLAYNAME]: Commenting on ${did} for ${checkProfiles.label}`, 170 + ); 171 + } 165 172 } 166 173 } 167 174 } 168 - } 169 - }); 175 + }); 176 + } catch (error) { 177 + logger.error(`Error in checkDisplayName for ${did}:`, error); 178 + throw error; 179 + } 170 180 };
+77 -67
src/checkStarterPack.ts
··· 11 11 time: number, 12 12 atURI: string, 13 13 ) => { 14 - // Get a list of labels 15 - const labels: string[] = Array.from( 16 - PROFILE_CHECKS, 17 - (profileCheck) => profileCheck.label, 18 - ); 19 - 20 - // iterate through the labels 21 - labels.forEach((label) => { 22 - const checkProfiles = PROFILE_CHECKS.find( 23 - (profileCheck) => profileCheck.label === label, 14 + try { 15 + // Get a list of labels 16 + const labels: string[] = Array.from( 17 + PROFILE_CHECKS, 18 + (profileCheck) => profileCheck.label, 24 19 ); 25 20 26 - // Check if DID is whitelisted 27 - if (checkProfiles?.ignoredDIDs) { 28 - if (checkProfiles.ignoredDIDs.includes(did)) { 29 - logger.info(`Whitelisted DID: ${did}`); return; 21 + // iterate through the labels 22 + labels.forEach((label) => { 23 + const checkProfiles = PROFILE_CHECKS.find( 24 + (profileCheck) => profileCheck.label === label, 25 + ); 26 + 27 + // Check if DID is whitelisted 28 + if (checkProfiles?.ignoredDIDs) { 29 + if (checkProfiles.ignoredDIDs.includes(did)) { 30 + logger.info(`Whitelisted DID: ${did}`); return; 31 + } 30 32 } 31 - } 32 33 33 - if (atURI) { 34 - if (checkProfiles?.starterPacks) { 35 - if (checkProfiles?.starterPacks.includes(atURI)) { 36 - logger.info(`Account joined via starter pack at: ${atURI}`); 37 - createAccountLabel( 38 - did, 39 - `${checkProfiles!.label}`, 40 - `${time}: ${checkProfiles!.comment} - Account joined via starter pack at: ${atURI}`, 41 - ); 34 + if (atURI) { 35 + if (checkProfiles?.starterPacks) { 36 + if (checkProfiles?.starterPacks.includes(atURI)) { 37 + logger.info(`Account joined via starter pack at: ${atURI}`); 38 + createAccountLabel( 39 + did, 40 + checkProfiles.label, 41 + `${time}: ${checkProfiles.comment} - Account joined via starter pack at: ${atURI}`, 42 + ); 43 + } 42 44 } 43 45 } 44 - } 45 - }); 46 + }); 47 + } catch (error) { 48 + logger.error(`Error in checkStarterPack for ${did}:`, error); 49 + throw error; 50 + } 46 51 }; 47 52 48 53 export const checkNewStarterPack = async ( ··· 53 58 packName: string | undefined, 54 59 description: string | undefined, 55 60 ) => { 56 - const labels: string[] = Array.from( 57 - STARTERPACK_CHECKS, 58 - (SPCheck) => SPCheck.label, 59 - ); 60 - 61 - labels.forEach((label) => { 62 - const checkList = PROFILE_CHECKS.find((SPCheck) => SPCheck.label === label); 61 + try { 62 + const labels: string[] = Array.from( 63 + STARTERPACK_CHECKS, 64 + (SPCheck) => SPCheck.label, 65 + ); 63 66 64 - if (checkList?.knownVectors?.includes(did)) { 65 - createPostLabel( 66 - atURI, 67 - cid, 68 - `${checkList!.label}`, 69 - `${time}: Starter pack created by known vector for ${checkList!.label} at: ${atURI}"`, 70 - ); 71 - createAccountReport( 72 - did, 73 - `${time}: Starter pack created by known vector for ${checkList!.label} at: ${atURI}"`, 74 - ); 75 - } 67 + labels.forEach((label) => { 68 + const checkList = PROFILE_CHECKS.find((SPCheck) => SPCheck.label === label); 76 69 77 - if (description) { 78 - if (checkList!.check.test(description)) { 79 - logger.info(`Labeling post: ${atURI}`); 70 + if (checkList?.knownVectors?.includes(did)) { 80 71 createPostLabel( 81 72 atURI, 82 73 cid, 83 - `${checkList!.label}`, 84 - `${time}: ${checkList!.comment} at ${atURI} with text "${description}"`, 74 + checkList.label, 75 + `${time}: Starter pack created by known vector for ${checkList.label} at: ${atURI}"`, 85 76 ); 86 77 createAccountReport( 87 78 did, 88 - `${time}: ${checkList!.comment} at ${atURI} with text "${description}"`, 79 + `${time}: Starter pack created by known vector for ${checkList.label} at: ${atURI}"`, 89 80 ); 90 81 } 91 - } 92 82 93 - if (packName) { 94 - if (checkList!.check.test(packName)) { 95 - logger.info(`Labeling post: ${atURI}`); 96 - createPostLabel( 97 - atURI, 98 - cid, 99 - `${checkList!.label}`, 100 - `${time}: ${checkList!.comment} at ${atURI} with pack name "${packName}"`, 101 - ); 102 - createAccountReport( 103 - did, 104 - `${time}: ${checkList!.comment} at ${atURI} with pack name "${packName}"`, 105 - ); 83 + if (description) { 84 + if (checkList!.check.test(description)) { 85 + logger.info(`Labeling post: ${atURI}`); 86 + createPostLabel( 87 + atURI, 88 + cid, 89 + checkList!.label, 90 + `${time}: ${checkList!.comment} at ${atURI} with text "${description}"`, 91 + ); 92 + createAccountReport( 93 + did, 94 + `${time}: ${checkList!.comment} at ${atURI} with text "${description}"`, 95 + ); 96 + } 106 97 } 107 - } 108 - }); 98 + 99 + if (packName) { 100 + if (checkList!.check.test(packName)) { 101 + logger.info(`Labeling post: ${atURI}`); 102 + createPostLabel( 103 + atURI, 104 + cid, 105 + checkList!.label, 106 + `${time}: ${checkList!.comment} at ${atURI} with pack name "${packName}"`, 107 + ); 108 + createAccountReport( 109 + did, 110 + `${time}: ${checkList!.comment} at ${atURI} with pack name "${packName}"`, 111 + ); 112 + } 113 + } 114 + }); 115 + } catch (error) { 116 + logger.error(`Error in checkNewStarterPack for ${did}:`, error); 117 + throw error; 118 + } 109 119 };
+173 -93
src/main.ts
··· 90 90 91 91 jetstream.onCreate( 92 92 'app.bsky.feed.post', 93 - (event: CommitCreateEvent<'app.bsky.feed.post'>) => { 94 - const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`; 95 - const hasFacets = event.commit.record.hasOwnProperty('facets'); 96 - const hasText = event.commit.record.hasOwnProperty('text'); 93 + async (event: CommitCreateEvent<'app.bsky.feed.post'>) => { 94 + try { 95 + const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`; 96 + const hasFacets = event.commit.record.hasOwnProperty('facets'); 97 + const hasText = event.commit.record.hasOwnProperty('text'); 97 98 98 - const tasks: Promise<void>[] = []; 99 - 100 - // Check if the record has facets 101 - if (hasFacets) { 102 - const hasLinkType = event.commit.record.facets!.some((facet) => 103 - facet.features.some( 104 - (feature) => feature.$type === 'app.bsky.richtext.facet#link', 105 - ), 106 - ); 99 + const tasks: Promise<void>[] = []; 107 100 108 - if (hasLinkType) { 109 - const urls = event.commit.record 110 - .facets!.flatMap((facet) => 111 - facet.features.filter( 101 + // Check if the record has facets 102 + if (hasFacets && event.commit.record.facets) { 103 + const hasLinkType = event.commit.record.facets.some((facet) => 104 + facet.features.some( 112 105 (feature) => feature.$type === 'app.bsky.richtext.facet#link', 113 106 ), 114 - ) 115 - .map((feature: LinkFeature) => feature.uri); 107 + ); 108 + 109 + if (hasLinkType) { 110 + const urls = event.commit.record.facets.flatMap((facet) => 111 + facet.features.filter( 112 + (feature) => feature.$type === 'app.bsky.richtext.facet#link', 113 + ), 114 + ) 115 + .map((feature: LinkFeature) => feature.uri); 116 + 117 + urls.forEach((url) => { 118 + const posts: Post[] = [ 119 + { 120 + did: event.did, 121 + time: event.time_us, 122 + rkey: event.commit.rkey, 123 + atURI, 124 + text: url, 125 + cid: event.commit.cid, 126 + }, 127 + ]; 128 + tasks.push(checkPosts(posts).catch((error) => { 129 + logger.error(`Error checking post links for ${event.did}:`, error); 130 + })); 131 + }); 132 + } 133 + } else if (hasText && event.commit.record.text) { 134 + const posts: Post[] = [ 135 + { 136 + did: event.did, 137 + time: event.time_us, 138 + rkey: event.commit.rkey, 139 + atURI, 140 + text: event.commit.record.text, 141 + cid: event.commit.cid, 142 + }, 143 + ]; 144 + tasks.push(checkPosts(posts).catch((error) => { 145 + logger.error(`Error checking post text for ${event.did}:`, error); 146 + })); 147 + } 116 148 117 - urls.forEach((url) => { 118 - const posts: Post[] = [ 119 - { 120 - did: event.did, 121 - time: event.time_us, 122 - rkey: event.commit.rkey, 123 - atURI, 124 - text: url, 125 - cid: event.commit.cid, 126 - }, 127 - ]; 128 - tasks.push(checkPosts(posts)); 129 - }); 149 + // Wait for all tasks to complete 150 + if (tasks.length > 0) { 151 + await Promise.allSettled(tasks); 130 152 } 131 - } else if (hasText) { 132 - const posts: Post[] = [ 133 - { 134 - did: event.did, 135 - time: event.time_us, 136 - rkey: event.commit.rkey, 137 - atURI, 138 - text: event.commit.record.text, 139 - cid: event.commit.cid, 140 - }, 141 - ]; 142 - tasks.push(checkPosts(posts)); 153 + } catch (error) { 154 + logger.error(`Error processing post event for ${event.did}:`, error); 143 155 } 144 156 }, 145 157 ); ··· 149 161 'app.bsky.actor.profile', 150 162 async (event: CommitUpdateEvent<'app.bsky.actor.profile'>) => { 151 163 try { 164 + const tasks: Promise<void>[] = []; 165 + 152 166 if (event.commit.record.displayName || event.commit.record.description) { 153 - checkDescription( 154 - event.did, 155 - event.time_us, 156 - event.commit.record.displayName!, 157 - event.commit.record.description!, 167 + const displayName = event.commit.record.displayName ?? ''; 168 + const description = event.commit.record.description ?? ''; 169 + 170 + tasks.push( 171 + checkDescription(event.did, event.time_us, displayName, description) 172 + .catch((error) => { 173 + logger.error(`Error checking profile description for ${event.did}:`, error); 174 + }) 158 175 ); 159 - checkDisplayName( 160 - event.did, 161 - event.time_us, 162 - event.commit.record.displayName!, 163 - event.commit.record.description!, 176 + 177 + tasks.push( 178 + checkDisplayName(event.did, event.time_us, displayName, description) 179 + .catch((error) => { 180 + logger.error(`Error checking profile display name for ${event.did}:`, error); 181 + }) 164 182 ); 165 183 } 166 184 167 185 if (event.commit.record.joinedViaStarterPack) { 168 - checkStarterPack( 169 - event.did, 170 - event.time_us, 171 - event.commit.record.joinedViaStarterPack.uri, 186 + tasks.push( 187 + checkStarterPack(event.did, event.time_us, event.commit.record.joinedViaStarterPack.uri) 188 + .catch((error) => { 189 + logger.error(`Error checking starter pack for ${event.did}:`, error); 190 + }) 172 191 ); 173 192 } 193 + 194 + // Wait for all tasks to complete 195 + if (tasks.length > 0) { 196 + await Promise.allSettled(tasks); 197 + } 174 198 } catch (error) { 175 - logger.error(`Error checking profile: ${error}`); 199 + logger.error(`Error processing profile update event for ${event.did}:`, error); 176 200 } 177 201 }, 178 202 ); ··· 183 207 'app.bsky.actor.profile', 184 208 async (event: CommitCreateEvent<'app.bsky.actor.profile'>) => { 185 209 try { 210 + const tasks: Promise<void>[] = []; 211 + 186 212 if (event.commit.record.displayName || event.commit.record.description) { 187 - checkDescription( 188 - event.did, 189 - event.time_us, 190 - event.commit.record.displayName!, 191 - event.commit.record.description!, 213 + const displayName = event.commit.record.displayName ?? ''; 214 + const description = event.commit.record.description ?? ''; 215 + 216 + tasks.push( 217 + checkDescription(event.did, event.time_us, displayName, description) 218 + .catch((error) => { 219 + logger.error(`Error checking profile description for ${event.did}:`, error); 220 + }) 192 221 ); 193 - checkDisplayName( 194 - event.did, 195 - event.time_us, 196 - event.commit.record.displayName!, 197 - event.commit.record.description!, 222 + 223 + tasks.push( 224 + checkDisplayName(event.did, event.time_us, displayName, description) 225 + .catch((error) => { 226 + logger.error(`Error checking profile display name for ${event.did}:`, error); 227 + }) 198 228 ); 199 229 200 230 if (event.commit.record.joinedViaStarterPack) { 201 - checkStarterPack( 202 - event.did, 203 - event.time_us, 204 - event.commit.record.joinedViaStarterPack.uri, 231 + tasks.push( 232 + checkStarterPack(event.did, event.time_us, event.commit.record.joinedViaStarterPack.uri) 233 + .catch((error) => { 234 + logger.error(`Error checking starter pack for ${event.did}:`, error); 235 + }) 205 236 ); 206 237 } 207 - } else { 208 - return; 238 + 239 + // Wait for all tasks to complete 240 + if (tasks.length > 0) { 241 + await Promise.allSettled(tasks); 242 + } 209 243 } 210 244 } catch (error) { 211 - logger.error(`Error checking profile: ${error}`); 245 + logger.error(`Error processing profile creation event for ${event.did}:`, error); 212 246 } 213 247 }, 214 248 ); ··· 218 252 async (event: CommitCreateEvent<'app.bsky.graph.starterpack'>) => { 219 253 try { 220 254 const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`; 255 + const name = event.commit.record.name ?? ''; 256 + const description = event.commit.record.description ?? ''; 221 257 222 - checkNewStarterPack( 258 + await checkNewStarterPack( 223 259 event.did, 224 260 event.time_us, 225 261 atURI, 226 262 event.commit.cid, 227 - event.commit.record.name, 228 - event.commit.record.description, 229 - ); 263 + name, 264 + description, 265 + ).catch((error) => { 266 + logger.error(`Error checking new starter pack for ${event.did}:`, error); 267 + }); 230 268 } catch (error) { 231 - logger.error(`Error checking starterpack: ${error}`); 269 + logger.error(`Error processing starter pack creation event for ${event.did}:`, error); 232 270 } 233 271 }, 234 272 ); ··· 238 276 async (event: CommitUpdateEvent<'app.bsky.graph.starterpack'>) => { 239 277 try { 240 278 const atURI = `at://${event.did}/app.bsky.feed.post/${event.commit.rkey}`; 279 + const name = event.commit.record.name ?? ''; 280 + const description = event.commit.record.description ?? ''; 241 281 242 - checkNewStarterPack( 282 + await checkNewStarterPack( 243 283 event.did, 244 284 event.time_us, 245 285 atURI, 246 286 event.commit.cid, 247 - event.commit.record.name, 248 - event.commit.record.description, 249 - ); 287 + name, 288 + description, 289 + ).catch((error) => { 290 + logger.error(`Error checking updated starter pack for ${event.did}:`, error); 291 + }); 250 292 } catch (error) { 251 - logger.error(`Error checking starterpack: ${error}`); 293 + logger.error(`Error processing starter pack update event for ${event.did}:`, error); 252 294 } 253 295 }, 254 296 ); 255 297 256 298 // Check for handle updates 257 299 jetstream.on('identity', async (event: IdentityEvent) => { 258 - if (event.identity.handle) { 259 - checkHandle(event.identity.did, event.identity.handle, event.time_us); 300 + try { 301 + if (event.identity.handle) { 302 + await checkHandle(event.identity.did, event.identity.handle, event.time_us) 303 + .catch((error) => { 304 + logger.error(`Error checking handle for ${event.identity.did}:`, error); 305 + }); 306 + } 307 + } catch (error) { 308 + logger.error(`Error processing identity event for ${event.identity.did}:`, error); 260 309 } 261 310 }); 262 311 263 - const metricsServer = startMetricsServer(METRICS_PORT); 312 + // Start metrics server with error handling 313 + let metricsServer; 314 + try { 315 + metricsServer = startMetricsServer(METRICS_PORT); 316 + logger.info(`Metrics server started on port ${METRICS_PORT}`); 317 + } catch (error) { 318 + logger.error('Failed to start metrics server:', error); 319 + process.exit(1); 320 + } 264 321 265 322 /* labelerServer.app.listen({ port: PORT, host: HOST }, (error, address) => { 266 323 if (error) { ··· 270 327 } 271 328 });*/ 272 329 273 - jetstream.start(); 330 + // Start jetstream with error handling 331 + try { 332 + jetstream.start(); 333 + logger.info('Jetstream started successfully'); 334 + } catch (error) { 335 + logger.error('Failed to start jetstream:', error); 336 + process.exit(1); 337 + } 274 338 275 339 function shutdown() { 276 340 try { 277 341 logger.info('Shutting down gracefully...'); 278 - fs.writeFileSync('cursor.txt', jetstream.cursor!.toString(), 'utf8'); 342 + if (jetstream.cursor) { 343 + fs.writeFileSync('cursor.txt', jetstream.cursor.toString(), 'utf8'); 344 + } 279 345 jetstream.close(); 280 - metricsServer.close(); 346 + if (metricsServer) { 347 + metricsServer.close(); 348 + } 349 + logger.info('Shutdown completed successfully'); 281 350 } catch (error) { 282 - logger.error(`Error shutting down gracefully: ${error}`); 351 + logger.error('Error shutting down gracefully:', error); 283 352 process.exit(1); 284 353 } 285 354 } 355 + 356 + // Global error handlers 357 + process.on('unhandledRejection', (reason, promise) => { 358 + logger.error('Unhandled Promise Rejection at:', promise, 'reason:', reason); 359 + // Don't exit the process for unhandled rejections, just log them 360 + }); 361 + 362 + process.on('uncaughtException', (error) => { 363 + logger.error('Uncaught Exception:', error); 364 + shutdown(); 365 + }); 286 366 287 367 process.on('SIGINT', shutdown); 288 368 process.on('SIGTERM', shutdown);
+18 -12
src/moderation.ts
··· 40 40 }, 41 41 }, 42 42 ); 43 - } catch (e) { 44 - console.error(e); 43 + } catch (error) { 44 + logger.error(`Error creating post label for URI ${uri}:`, error); 45 + throw error; 45 46 } 46 47 }); 47 48 }; ··· 80 81 }, 81 82 }, 82 83 ); 83 - } catch (e) { 84 - console.error(e); 84 + } catch (error) { 85 + logger.error(`Error creating account label for DID ${did}:`, error); 86 + throw error; 85 87 } 86 88 }); 87 89 }; ··· 120 122 }, 121 123 }, 122 124 ); 123 - } catch (e) { 124 - console.error(e); 125 + } catch (error) { 126 + logger.error(`Error creating post report for URI ${uri}:`, error); 127 + throw error; 125 128 } 126 129 }); 127 130 }; ··· 154 157 }, 155 158 }, 156 159 ); 157 - } catch (e) { 158 - console.error(e); 160 + } catch (error) { 161 + logger.error(`Error creating account comment for DID ${did}:`, error); 162 + throw error; 159 163 } 160 164 }); 161 165 }; ··· 189 193 }, 190 194 }, 191 195 ); 192 - } catch (e) { 193 - console.error(e); 196 + } catch (error) { 197 + logger.error(`Error creating account report for DID ${did}:`, error); 198 + throw error; 194 199 } 195 200 }); 196 201 }; ··· 220 225 createdAt: new Date().toISOString(), 221 226 }, 222 227 }); 223 - } catch (e) { 224 - console.error(e); 228 + } catch (error) { 229 + logger.error(`Error adding DID ${did} to list ${label}:`, error); 230 + throw error; 225 231 } 226 232 }); 227 233 };
+16 -16
src/monitor.ts
··· 31 31 32 32 if (description) { 33 33 if (checkProfiles?.description === true) { 34 - if (checkProfiles!.check.test(description)) { 35 - if (checkProfiles!.whitelist) { 36 - if (checkProfiles!.whitelist.test(description)) { 34 + if (checkProfiles.check.test(description)) { 35 + if (checkProfiles.whitelist) { 36 + if (checkProfiles.whitelist.test(description)) { 37 37 logger.info('Whitelisted phrase found.'); 38 38 return; 39 39 } 40 40 } else { 41 - logger.info(`${checkProfiles!.label} in description for ${did}`); 41 + logger.info(`${checkProfiles.label} in description for ${did}`); 42 42 } 43 43 44 - if (checkProfiles!.reportOnly === true) { 44 + if (checkProfiles.reportOnly === true) { 45 45 createAccountReport( 46 46 did, 47 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 47 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 48 48 ); 49 49 return; 50 50 } else { 51 51 createAccountLabel( 52 52 did, 53 - `${checkProfiles!.label}`, 54 - `${time}: ${checkProfiles!.comment}`, 53 + checkProfiles.label, 54 + `${time}: ${checkProfiles.comment}`, 55 55 ); 56 56 } 57 57 } ··· 87 87 88 88 if (displayName) { 89 89 if (checkProfiles?.displayName === true) { 90 - if (checkProfiles!.check.test(displayName)) { 91 - if (checkProfiles!.whitelist) { 92 - if (checkProfiles!.whitelist.test(displayName)) { 90 + if (checkProfiles.check.test(displayName)) { 91 + if (checkProfiles.whitelist) { 92 + if (checkProfiles.whitelist.test(displayName)) { 93 93 logger.info('Whitelisted phrase found.'); 94 94 return; 95 95 } 96 96 } else { 97 - logger.info(`${checkProfiles!.label} in displayName for ${did}`); 97 + logger.info(`${checkProfiles.label} in displayName for ${did}`); 98 98 } 99 99 100 - if (checkProfiles!.reportOnly === true) { 100 + if (checkProfiles.reportOnly === true) { 101 101 createAccountReport( 102 102 did, 103 - `${time}: ${checkProfiles!.comment} - ${displayName} - ${description}`, 103 + `${time}: ${checkProfiles.comment} - ${displayName} - ${description}`, 104 104 ); 105 105 return; 106 106 } else { 107 107 createAccountLabel( 108 108 did, 109 - `${checkProfiles!.label}`, 110 - `${time}: ${checkProfiles!.comment}`, 109 + checkProfiles.label, 110 + `${time}: ${checkProfiles.comment}`, 111 111 ); 112 112 } 113 113 }
+13 -8
src/utils.ts
··· 73 73 return 'eng'; 74 74 } 75 75 76 - const lande = (await import('lande')).default; 77 - const langsProbabilityMap = lande(profileText); 76 + try { 77 + const lande = (await import('lande')).default; 78 + const langsProbabilityMap = lande(profileText); 78 79 79 - // Sort by probability in descending order 80 - langsProbabilityMap.sort( 81 - (a: [string, number], b: [string, number]) => b[1] - a[1], 82 - ); 80 + // Sort by probability in descending order 81 + langsProbabilityMap.sort( 82 + (a: [string, number], b: [string, number]) => b[1] - a[1], 83 + ); 83 84 84 - // Return the language code with the highest probability 85 - return langsProbabilityMap[0][0]; 85 + // Return the language code with the highest probability 86 + return langsProbabilityMap[0][0]; 87 + } catch (error) { 88 + logger.error('Error detecting language, defaulting to \'eng\':', error); 89 + return 'eng'; // Fallback to English on error 90 + } 86 91 }