import { createHash } from 'node:crypto' /** * Upload signatures (CONCEPT.md D42): which tool posted an article, read from * its Message-ID, and the group it was posted to first. Together they predict * whether a random-named upload will ever get a name: ngPost uploads in * a.b.ath almost never do, nyuu uploads in a.b.friends always do (LOG.md * 2026-10-08 (5)). */ const TOOLS: [string, RegExp][] = [ // FmFwIaTpGgUiSfYcMwFlMeRo-1790637260478@nyuu: 12 random capital+small letter pairs, a ms timestamp ['nyuu', /^(?:[A-Z][a-z]){12}-\d{13}@nyuu$/], ['nyuu-host', /^(?:[A-Z][a-z]){12}-\d{13}@.+$/], // b94268bc702040f7b811d5318f727e4f@ngPost: a UUID's hex ['ngPost', /^[0-9a-f]{32}@ngPost$/], ['yuun', /^[0-9a-f]{32}@[0-9a-f]{32}\.yuun$/], ['hex32', /^[0-9a-f]{32}@[A-Za-z0-9.-]+$/], ['JBinUp', /@JBin(?:Up|Down)\.local$/], // part1of69.lOlWzvSHabJCBTVrs0ts@AdultPoster.local ['partNofM', /^part\d+of\d+\./i], // 1790923856877$1729905757859$1@localhost ['ts-dollar', /^\d{13}\$\d+\$\d+@localhost$/], // t3Y4mV92YCp0McxDc5@zLZlsw.RYqes: random local part and random host ['random', /^[A-Za-z0-9]{10,40}@[A-Za-z0-9]{3,12}\.[A-Za-z0-9]{2,6}$/], ] /** The posting tool of a Message-ID (without angle brackets), or 'other'. */ export function uploadTool(messageId: string | null | undefined): string { if (!messageId) return 'other' for (const [tool, pattern] of TOOLS) if (pattern.test(messageId)) return tool return 'other' } /** The group of a signature: the first the upload was posted to, as stored in `groups`. */ export function signatureGroup(groups: unknown): string { return Array.isArray(groups) && typeof groups[0] === 'string' ? groups[0] : '' } /** The key of a signature in a Set. */ export function signatureKey(tool: string, group: string) { return `${tool}\t${group}` } /** * Whether an upload is in the share of dropped signatures that is still kept * (and probed), so their rates keep being measured. Decided per release key, * so a release is kept or dropped as a whole. */ export function explored(key: string, percent: number): boolean { if (percent <= 0) return false return createHash('md5').update(key).digest().readUInt32BE(0) % 10_000 < percent * 100 }