char-slop/city-role-bot
discord auto role applicator bot for charlotte polycule city
git clone https://git.t4t.associates/char-slop/city-role-bot
d72003f
main
1import { mkdirSync , readFileSync , writeFileSync } from 'node:fs' 2 3export interface RolePanel { 4channelID :string 5/** unicode emoji, or `name:id` for custom emoji */ 6emoji :string 7guildID :string 8/** users known to have the reaction, kept in step with role grants */ 9reactors :Set < string > 10roleID :string 11} 12 13type StoredPanel = Omit < RolePanel , 'reactors' > & { reactors ?:string [] } 14 15const DATA_DIR = new URL ( '../data/' , import . meta. url ) 16const FILE = new URL ( 'role-panels.json' , DATA_DIR ) 17 18function load () :Map < string , RolePanel > { 19let raw :string 20try { 21raw = readFileSync ( FILE , 'utf8' ) 22} catch ( error ) { 23if (( error as NodeJS . ErrnoException ). code === 'ENOENT' ) return new Map () 24throw error 25} 26return new Map ( 27Object . entries ( JSON . parse ( raw ) as Record < string , StoredPanel >). map (([ messageID , panel ]) => [ 28messageID , 29// files written before reactors were tracked 30{ ...panel , reactors :new Set ( panel . reactors ?? []) } 31]) 32) 33} 34 35function save () :void { 36mkdirSync ( DATA_DIR , { recursive :true }) 37const json = JSON . stringify ( 38Object . fromEntries ( panels ), 39( _ , value :unknown ) => ( value instanceof Set ?[ ...value ] :value ), 402 41) 42writeFileSync ( FILE , json ) 43} 44 45/** Panel registry, keyed by panel message ID. */ 46export const panels = load () 47 48export function setPanel ( messageID :string , panel :RolePanel ) :void { 49panels . set ( messageID , panel ) 50save () 51} 52 53export function deletePanel ( messageID :string ) :boolean { 54if ( ! panels . delete ( messageID )) return false 55save () 56return true 57} 58 59export function setReactor ( panel :RolePanel , userID :string , reacted :boolean ) :void { 60if ( reacted ) panel . reactors . add ( userID ) 61else panel . reactors . delete ( userID ) 62save () 63}