A tool for parsing traffic on the jetstream and applying a moderation workstream based on regexp based rules
at main 4.8 kB view raw
1import { ACCOUNT_THRESHOLD_CONFIGS } from "../rules/accountThreshold.js"; 2import { 3 createAccountComment, 4 createAccountLabel, 5 createAccountReport, 6} from "./accountModeration.js"; 7import { logger } from "./logger.js"; 8import { 9 accountLabelsThresholdAppliedCounter, 10 accountThresholdChecksCounter, 11 accountThresholdMetCounter, 12} from "./metrics.js"; 13import { 14 getPostLabelCountInWindow, 15 trackPostLabelForAccount, 16} from "./redis.js"; 17import type { AccountThresholdConfig } from "./types.js"; 18 19function normalizeLabels(labels: string | string[]): string[] { 20 return Array.isArray(labels) ? labels : [labels]; 21} 22 23function validateAndLoadConfigs(): AccountThresholdConfig[] { 24 if (ACCOUNT_THRESHOLD_CONFIGS.length === 0) { 25 logger.warn( 26 { process: "ACCOUNT_THRESHOLD" }, 27 "No account threshold configs found", 28 ); 29 return []; 30 } 31 32 for (const config of ACCOUNT_THRESHOLD_CONFIGS) { 33 const labels = normalizeLabels(config.labels); 34 if (labels.length === 0) { 35 throw new Error( 36 `Invalid account threshold config: labels cannot be empty`, 37 ); 38 } 39 if (config.threshold <= 0) { 40 throw new Error( 41 `Invalid account threshold config: threshold must be positive`, 42 ); 43 } 44 if (config.window <= 0) { 45 throw new Error( 46 `Invalid account threshold config: window must be positive`, 47 ); 48 } 49 } 50 51 logger.info( 52 { process: "ACCOUNT_THRESHOLD", count: ACCOUNT_THRESHOLD_CONFIGS.length }, 53 "Loaded account threshold configs", 54 ); 55 56 return ACCOUNT_THRESHOLD_CONFIGS; 57} 58 59// Load and cache configs at module initialization 60const cachedConfigs = validateAndLoadConfigs(); 61 62export function loadThresholdConfigs(): AccountThresholdConfig[] { 63 return cachedConfigs; 64} 65 66export async function checkAccountThreshold( 67 did: string, 68 uri: string, 69 postLabel: string, 70 timestamp: number, 71): Promise<void> { 72 try { 73 const configs = loadThresholdConfigs(); 74 75 const matchingConfigs = configs.filter((config) => { 76 const labels = normalizeLabels(config.labels); 77 return labels.includes(postLabel); 78 }); 79 80 if (matchingConfigs.length === 0) { 81 logger.debug( 82 { process: "ACCOUNT_THRESHOLD", did, postLabel }, 83 "No matching threshold configs for post label", 84 ); 85 return; 86 } 87 88 accountThresholdChecksCounter.inc({ post_label: postLabel }); 89 90 for (const config of matchingConfigs) { 91 const labels = normalizeLabels(config.labels); 92 93 await trackPostLabelForAccount( 94 did, 95 postLabel, 96 timestamp, 97 config.window, 98 config.windowUnit, 99 ); 100 101 const count = await getPostLabelCountInWindow( 102 did, 103 labels, 104 config.window, 105 config.windowUnit, 106 timestamp, 107 ); 108 109 logger.debug( 110 { 111 process: "ACCOUNT_THRESHOLD", 112 did, 113 labels, 114 count, 115 threshold: config.threshold, 116 window: config.window, 117 windowUnit: config.windowUnit, 118 }, 119 "Checked account threshold", 120 ); 121 122 if (count >= config.threshold) { 123 accountThresholdMetCounter.inc({ account_label: config.accountLabel }); 124 125 logger.info( 126 { 127 process: "ACCOUNT_THRESHOLD", 128 did, 129 postLabel, 130 accountLabel: config.accountLabel, 131 count, 132 threshold: config.threshold, 133 }, 134 "Account threshold met", 135 ); 136 137 const shouldLabel = config.toLabel !== false; 138 139 const formattedComment = `${config.accountComment}\n\nThreshold: ${count.toString()}/${config.threshold.toString()} in ${config.window.toString()} ${config.windowUnit}\n\nPost: ${uri}\n\nPost Label: ${postLabel}`; 140 141 if (shouldLabel) { 142 await createAccountLabel(did, config.accountLabel, formattedComment); 143 accountLabelsThresholdAppliedCounter.inc({ 144 account_label: config.accountLabel, 145 action: "label", 146 }); 147 } 148 149 if (config.reportAcct) { 150 await createAccountReport(did, formattedComment); 151 accountLabelsThresholdAppliedCounter.inc({ 152 account_label: config.accountLabel, 153 action: "report", 154 }); 155 } 156 157 if (config.commentAcct) { 158 const atURI = `threshold-comment:${config.accountLabel}:${timestamp.toString()}`; 159 await createAccountComment(did, formattedComment, atURI); 160 accountLabelsThresholdAppliedCounter.inc({ 161 account_label: config.accountLabel, 162 action: "comment", 163 }); 164 } 165 } 166 } 167 } catch (error) { 168 logger.error( 169 { process: "ACCOUNT_THRESHOLD", did, postLabel, error }, 170 "Error checking account threshold", 171 ); 172 throw error; 173 } 174}