import { EventKind, HexKey, NostrPrefix, NostrEvent, EventSigner, PowMiner } from "."; import { HashtagRegex, MentionNostrEntityRegex } from "./const"; import { getPublicKey, jitter, unixNow } from "@snort/shared"; import { EventExt } from "./event-ext"; import { tryParseNostrLink } from "./nostr-link"; export class EventBuilder { #kind?: EventKind; #content?: string; #createdAt?: number; #pubkey?: string; #tags: Array> = []; #pow?: number; #powMiner?: PowMiner; #jitter?: number; jitter(n: number) { this.#jitter = n; return this; } kind(k: EventKind) { this.#kind = k; return this; } content(c: string) { this.#content = c; return this; } createdAt(n: number) { this.#createdAt = n; return this; } pubKey(k: string) { this.#pubkey = k; return this; } tag(t: Array): EventBuilder { const duplicate = this.#tags.some(a => a.length === t.length && a.every((b, i) => b !== a[i])); if (duplicate) return this; this.#tags.push(t); return this; } pow(target: number, miner?: PowMiner) { this.#pow = target; this.#powMiner = miner ?? { minePow: (ev, target) => { EventExt.minePow(ev, target); return Promise.resolve(ev); }, }; return this; } /** * Extract mentions */ processContent() { if (this.#content) { this.#content = this.#content.replace(MentionNostrEntityRegex, m => this.#replaceMention(m)); const hashTags = [...this.#content.matchAll(HashtagRegex)]; hashTags.map(hashTag => { this.#addHashtag(hashTag[0]); }); } return this; } build() { this.#validate(); const ev = { id: "", pubkey: this.#pubkey ?? "", content: this.#content ?? "", kind: this.#kind, created_at: (this.#createdAt ?? unixNow()) + (this.#jitter ? jitter(this.#jitter) : 0), tags: this.#tags.sort((a, b) => a[0].localeCompare(b[0])), } as NostrEvent; ev.id = EventExt.createId(ev); return ev; } /** * Build and sign event * @param pk Private key to sign event with */ async buildAndSign(pk: HexKey | EventSigner) { if (typeof pk === "string") { const ev = this.pubKey(getPublicKey(pk)).build(); return EventExt.sign(await this.#mine(ev), pk); } else { const ev = this.pubKey(await pk.getPubKey()).build(); return await pk.sign(await this.#mine(ev)); } } async #mine(ev: NostrEvent) { if (this.#pow && this.#powMiner) { const ret = await this.#powMiner.minePow(ev, this.#pow); return ret; } return ev; } #validate() { if (this.#kind === undefined) { throw new Error("Kind must be set"); } if (this.#pubkey === undefined) { throw new Error("Pubkey must be set"); } } #replaceMention(match: string) { const npub = match.slice(1); const link = tryParseNostrLink(npub); if (link) { if (link.type === NostrPrefix.Profile || link.type === NostrPrefix.PublicKey) { this.tag(["p", link.id]); } return `nostr:${link.encode()}`; } else { return match; } } #addHashtag(match: string) { const tag = match.slice(1); this.tag(["t", tag.toLowerCase()]); } }