import type { Database } from 'jsr:@db/sqlite'; import { bytesToHex } from '@noble/ciphers/utils'; import { nip19 } from '@nostr/tools'; import { bech32m } from '@scure/base'; import { sql } from './queries.ts'; export class InviteTree { public root: InviteNode; constructor(npub: string) { this.root = new InviteNode(npub); } public addChild(npub: string) { const child = new InviteNode(npub); this.root.children.push(child); } } export class InviteNode { public readonly npub: string; public readonly children: InviteNode[]; constructor(npub: string) { this.npub = npub; this.children = []; } public addChild(npub: string) { const child = new InviteNode(npub); this.children.push(child); } } export function buildInviteTree(db: Database, ccnPubkey: string) { const ccnCreator = sql` SELECT pubkey FROM allowed_writes WHERE ccn_pubkey = ${ccnPubkey} AND pubkey NOT IN ( SELECT invitee_pubkey FROM inviter_invitee WHERE ccn_pubkey = ${ccnPubkey} ) `(db)[0]?.pubkey; if (!ccnCreator) { throw new Error('CCN creator not found'); } const inviteTree = new InviteTree(ccnCreator); const invitees = sql` SELECT inviter_pubkey, invitee_pubkey FROM inviter_invitee WHERE ccn_pubkey = ${ccnPubkey} `(db); // populate the invite tree by traversing the inviters for (const invitee of invitees) { let inviterNode = inviteTree.root.children.find( (child) => child.npub === invitee.inviter_pubkey, ); if (!inviterNode) { inviterNode = new InviteNode(invitee.inviter_pubkey); inviteTree.root.children.push(inviterNode); } inviterNode.addChild(invitee.invitee_pubkey); } return inviteTree; } export function readInvite(invite: `${string}1${string}`) { const decoded = bech32m.decode(invite, false); if (decoded.prefix !== 'eveinvite') return false; const hexBytes = bech32m.fromWords(decoded.words); const npub = nip19.npubEncode(bytesToHex(hexBytes.slice(0, 32))); const inviteCode = bytesToHex(hexBytes.slice(32)); return { npub, invite: inviteCode }; }