import { adventurerNeutral } from "@dicebear/collection"; import { createAvatar } from "@dicebear/core"; import { $ } from "bun"; import { finalizeEvent, getPublicKey, type NostrEvent } from "nostr-tools"; import type { SubCloser } from "nostr-tools/abstract-pool"; import { fetchRemoteArxlets } from "./src/arxlets"; import { CCN } from "./src/ccns"; import arxletDocs from "./src/pages/docs/arxlets/arxlet-docs.html"; import homePage from "./src/pages/home/home.html"; import { getColorFromPubkey } from "./src/utils/color"; import { decryptEvent } from "./src/utils/encryption"; import { loadSeenEvents, saveSeenEvent } from "./src/utils/files"; import { queryRemoteEvent, queryRemoteRelays, sendUnencryptedEventToLocalRelay, } from "./src/utils/general"; import { DEFAULT_PERIOD_MINUTES, RollingIndex } from "./src/rollingIndex"; let currentActiveSub: SubCloser | undefined; let currentSubInterval: ReturnType | undefined; async function restartCCN() { currentActiveSub?.close(); let ccn = await CCN.getActive(); if (!ccn) { const allCCNs = await CCN.list(); if (allCCNs.length > 0) { await allCCNs[0]!.setActive(); ccn = allCCNs[0]!; } else return; } async function handleNewEvent(original: NostrEvent) { if (!ccn) return process.exit(1); const seenEvents = await loadSeenEvents(); if (seenEvents.includes(original.id)) return; await saveSeenEvent(original); const keyAtTime = ccn.getPrivateKeyAt( RollingIndex.at(original.created_at * 1000), ); const decrypted = await decryptEvent(original, keyAtTime); if (seenEvents.includes(decrypted.id)) return; await saveSeenEvent(decrypted); await sendUnencryptedEventToLocalRelay(decrypted); } await $`killall -9 strfry`.nothrow().quiet(); await ccn.writeStrfryConfig(); const strfry = Bun.spawn([ "strfry", "--config", ccn.strfryConfigPath, "relay", ]); process.on("exit", () => strfry.kill()); const allKeysForCCN = ccn.allPubkeys; function resetActiveSub() { console.log(`Setting new subscription for ${allKeysForCCN.join(", ")}`); currentActiveSub?.close(); currentActiveSub = queryRemoteRelays( { kinds: [1060], "#p": allKeysForCCN }, handleNewEvent, ); } resetActiveSub(); currentSubInterval = setInterval( () => { resetActiveSub(); }, DEFAULT_PERIOD_MINUTES * 60 * 1000, ); } restartCCN(); class CorsResponse extends Response { constructor(body?: BodyInit, init?: ResponseInit) { super(body, init); this.headers.set("Access-Control-Allow-Origin", "*"); this.headers.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT"); this.headers.set("Access-Control-Allow-Headers", "Content-Type"); } static override json(json: unknown, init?: ResponseInit) { const res = Response.json(json, init); res.headers.set("Access-Control-Allow-Origin", "*"); res.headers.set("Access-Control-Allow-Methods", "OPTIONS, GET, POST, PUT"); res.headers.set("Access-Control-Allow-Headers", "Content-Type"); return res; } } const invalidRequest = CorsResponse.json( { error: "Invalid Request" }, { status: 400 }, ); const httpServer = Bun.serve({ routes: { "/": homePage, "/docs/arxlets": arxletDocs, "/api/ccns": { GET: async () => { const ccns = await CCN.list(); return CorsResponse.json(ccns.map((x) => x.toPublicJson())); }, }, "/api/ccns/active": { GET: async () => { const ccn = await CCN.getActive(); if (!ccn) return CorsResponse.json({ error: "Not found" }, { status: 404 }); return CorsResponse.json(ccn.toPublicJson()); }, POST: async (req) => { if (!req.body) return invalidRequest; const body = await req.body.json(); if (!body.pubkey) return invalidRequest; const ccn = await CCN.fromPublicKey(body.pubkey); if (!ccn) return invalidRequest; await ccn.setActive(); restartCCN(); return CorsResponse.json(ccn.toPublicJson()); }, }, "/api/ccns/active/invite": { GET: async () => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const invite = await ccn.generateInvite(); return CorsResponse.json({ invite, }); }, }, "/api/ccns/new": { POST: async (req) => { if (!req.body) return invalidRequest; const body = await req.body.json(); if (!body.name || !body.description) return invalidRequest; const ccn = await CCN.create(body.name, body.description); const activeCCN = await CCN.getActive(); if (!activeCCN) { await ccn.setActive(); restartCCN(); } return CorsResponse.json(ccn.toPublicJson()); }, }, "/api/ccns/join": { POST: async (req) => { if (!req.body) return invalidRequest; const body = await req.body.json(); if (!body.name || !body.description || !body.key) return invalidRequest; const version = body.version ? body.version : 1; const startIndex = body.startIndex ? RollingIndex.fromHex(body.startIndex) : RollingIndex.get(); const ccn = await CCN.join( version, startIndex, body.name, body.description, new Uint8Array(body.key), ); return CorsResponse.json(ccn.toPublicJson()); }, }, "/api/ccns/:pubkey": async (req) => { const ccns = await CCN.list(); const ccnWithPubkey = ccns.find((x) => x.publicKey === req.params.pubkey); if (!ccnWithPubkey) return CorsResponse.json({ error: "Not Found" }, { status: 404 }); return CorsResponse.json(ccnWithPubkey.toPublicJson()); }, "/api/ccns/icon/:pubkey": async (req) => { const pubkey = req.params.pubkey; if (!pubkey) return invalidRequest; const ccn = await CCN.fromPublicKey(pubkey); if (!ccn) return invalidRequest; const avatar = ccn.getCommunityIcon(); return new CorsResponse(avatar, { headers: { "Content-Type": "image/svg+xml" }, }); }, "/api/ccns/name/:pubkey": async (req) => { const pubkey = req.params.pubkey; if (!pubkey) return invalidRequest; const ccn = await CCN.fromPublicKey(pubkey); if (!ccn) return invalidRequest; const profile = await ccn.getProfile(); return new CorsResponse(profile.name || ccn.name); }, "/api/ccns/avatar/:pubkey": async (req) => { const pubkey = req.params.pubkey; if (!pubkey) return invalidRequest; const ccn = await CCN.fromPublicKey(pubkey); if (!ccn) return invalidRequest; const profile = await ccn.getProfile(); if (profile.picture) return CorsResponse.redirect(profile.picture); const avatar = ccn.getCommunityIcon(); return new CorsResponse(avatar, { headers: { "Content-Type": "image/svg+xml" }, }); }, "/api/profile/:pubkey": async (req) => { const pubkey = req.params.pubkey; if (!pubkey) return invalidRequest; const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const profileEvent = await ccn.getFirstEvent({ kinds: [0], authors: [pubkey], }); try { if (!profileEvent) throw "No profile"; return new CorsResponse(profileEvent.content, { headers: { "Content-Type": "text/json" }, }); } catch { return CorsResponse.json( { error: "profile not found" }, { headers: { "Content-Type": "text/json" }, status: 404 }, ); } }, "/api/avatars/:pubkey": async (req) => { const pubkey = req.params.pubkey; if (!pubkey) return invalidRequest; const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const profileEvent = await ccn.getFirstEvent({ kinds: [0], authors: [pubkey], }); try { if (!profileEvent) throw "No profile"; const content = JSON.parse(profileEvent.content); if (!content.picture) throw "No picture"; return CorsResponse.redirect(content.picture); } catch { const avatar = createAvatar(adventurerNeutral, { seed: pubkey, backgroundColor: [getColorFromPubkey(pubkey)], }); return new CorsResponse(avatar.toString(), { headers: { "Content-Type": "image/svg+xml" }, }); } }, "/api/events": { POST: async (req) => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; if (!req.body) return invalidRequest; const { search, ...query } = await req.body.json(); let events = await ccn.getEvents(query); if (search) events = events.filter( (e) => e.content.includes(search) || e.tags.some((t) => t[1]?.includes(search)), ); return CorsResponse.json(events); }, PUT: async (req) => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; if (!req.body) return invalidRequest; const event = await req.body.json(); try { await ccn.publish(event); return CorsResponse.json({ success: true, message: "Event published successfully", }); } catch { return CorsResponse.json({ success: false, message: "Failed to publish event", }); } }, }, "/api/events/:id": async (req) => { const id = req.params.id; if (!id) return invalidRequest; const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const event = await ccn.getFirstEvent({ ids: [id] }); if (!event) return CorsResponse.json({ error: "Event Not Found" }, { status: 404 }); return CorsResponse.json(event); }, "/api/sign": { POST: async (req) => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const userKey = await ccn.getUserKey(); if (!req.body) return invalidRequest; const event = await req.body.json(); const signedEvent = finalizeEvent(event, userKey); return CorsResponse.json(signedEvent); }, }, "/api/pubkey": async () => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const userKey = await ccn.getUserKey(); return CorsResponse.json({ pubkey: getPublicKey(userKey) }); }, "/api/arxlets": async () => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const arxlets = await ccn.getArxlets(); return CorsResponse.json(arxlets); }, "/api/arxlets/:id": async (req) => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const arxlet = await ccn.getArxletById(req.params.id); if (!arxlet) return CorsResponse.json( { error: "Arxlet not found" }, { status: 404 }, ); return CorsResponse.json(arxlet); }, "/api/arxlets-available": async () => { const remoteArxlets = await fetchRemoteArxlets(); return CorsResponse.json(remoteArxlets); }, "/api/clone-remote-event/:id": async (req) => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const remoteEvent = await queryRemoteEvent(req.params.id); if (!remoteEvent) return CorsResponse.json({ error: "Event not found" }, { status: 404 }); await ccn.publish(remoteEvent); return CorsResponse.json(remoteEvent); }, "/api/reputation/:user": async (req) => { const ccn = await CCN.getActive(); if (!ccn) return invalidRequest; const reputation = await ccn.getReputation(req.params.user); return CorsResponse.json({ reputation }); }, "/systemapi/timezone": { GET: async () => { const timezone = ( await $`timedatectl show --property=Timezone --value`.text() ).trim(); return CorsResponse.json({ timezone }); }, POST: async (req) => { if (!req.body) return invalidRequest; const { timezone } = await req.body.json(); await $`sudo timedatectl set-timezone ${timezone}`; // this is fine, bun escapes it return CorsResponse.json({ timezone }); }, }, "/systemapi/wifi": { GET: async () => { const nmcliLines = ( await $`nmcli -f ssid,bssid,mode,freq,chan,rate,signal,security,active -t dev wifi`.text() ) .trim() .split("\n") .map((l) => l.split(/(? ({ ssid, bssid: bssid!.replace(/\\:/g, ":"), mode, freq: parseInt(freq!, 10), chan: parseInt(chan!, 10), rate, signal: parseInt(signal!, 10), security, active: active === "yes", }), ) .filter((network) => network.ssid !== "") // filter out hidden networks .filter((network) => network.security !== "") // filter out open networks .sort((a, b) => (a.active ? -1 : b.active ? 1 : b.signal - a.signal)); return CorsResponse.json(wifi); }, POST: async (req) => { if (!req.body) return invalidRequest; const { ssid, password } = await req.body.json(); await $`nmcli device wifi connect ${ssid} password ${password}`; // this is fine, bun escapes it return CorsResponse.json({ ssid }); }, }, }, fetch() { return new CorsResponse("Eve Lite v0.0.1"); }, port: 4269, }); console.log(`Listening on ${httpServer.url.host}`);