diff options
| author | Adam Soltys <[email protected]> | 2025-01-28 22:15:52 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2025-01-28 22:15:52 -0800 |
| commit | 972c2499fb508424a3a61125f3e45c2d06cfbb43 (patch) | |
| tree | 9594199bd848e5e282a402a233db210237a4bda1 | |
| parent | 0b019a93a57f6f8fc9c3b9af8f003a61ebaee7de (diff) | |
| download | coinos-server-972c2499fb508424a3a61125f3e45c2d06cfbb43.tar.gz coinos-server-972c2499fb508424a3a61125f3e45c2d06cfbb43.zip | |
nostr work
| -rw-r--r-- | lib/nostr.ts | 30 | ||||
| -rw-r--r-- | lib/strfry.ts | 102 | ||||
| -rw-r--r-- | lib/utils.ts | 1 | ||||
| -rw-r--r-- | routes/nostr.ts | 72 | ||||
| -rwxr-xr-x | strsrv | bin | 0 -> 420516 bytes |
5 files changed, 97 insertions, 108 deletions
diff --git a/lib/nostr.ts b/lib/nostr.ts index 0bb01521..4fb92b86 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -1,13 +1,15 @@ import config from "$config"; import { db, g, s } from "$lib/db"; import { l, warn } from "$lib/logging"; -import { count, scan, sync } from "$lib/strfry"; +import { scan } from "$lib/strfry"; import { fail, fields, getUser, pick } from "$lib/utils"; import { finalizeEvent, getPublicKey, nip19 } from "nostr-tools"; +import type { Event } from "nostr-tools"; import { AbstractSimplePool } from "nostr-tools/abstract-pool"; import { Relay } from "nostr-tools/relay"; export const EX = 60 * 60 * 24; +const coinos = await Relay.connect(config.nostr); export const serverPubkey = getPublicKey( nip19.decode(config.nostrKey).data as Uint8Array, @@ -158,7 +160,8 @@ export const getCount = async (pubkey) => { if (follows === null) { const result = await get({ authors: [pubkey], kinds: [3] }); follows = result ? result.tags.filter((t) => t[0] === "p").length : 0; - if (follows?.length) await db.set(`${pubkey}:follows:n`, JSON.stringify(follows), { EX }); + if (follows?.length) + await db.set(`${pubkey}:follows:n`, JSON.stringify(follows), { EX }); } const k = `${pubkey}:followers:n`; @@ -215,19 +218,28 @@ export const getNostrUser = async (key) => { }; export const q = async (f) => { - const events = await scan(f); + let events = (await scan(f)) as Event[]; const k = JSON.stringify(f).replace(/[^a-zA-Z0-9]/g, ""); const since = await g(`${k}:since}`); if (since) f.since = since; - const p = sync("wss://relay.primal.net", f).then(async () => { - const r = await scan(f); - if (r.length) await s(`${k}:since`, r[r.length - 1].created_at); - return r; + const p = new Promise((resolve) => { + const r = []; + pool.subscribeMany(["wss://relay.primal.net"], [f], { + onevent(e) { + r.push(e); + coinos.publish(e); + }, + oneose() { + resolve(r); + }, + }); }); - if (events.length > 0) return events; - return p; + if (events.length) return events; + events = (await p) as Event[]; + if (events.length) db.set(`${k}:since`, events[events.length - 1].created_at); + return events; }; export const get = async (f) => { diff --git a/lib/strfry.ts b/lib/strfry.ts index 65b77709..b2791e74 100644 --- a/lib/strfry.ts +++ b/lib/strfry.ts @@ -1,83 +1,29 @@ -import { randomUUID } from "crypto"; -import { connect, createServer } from "net"; +import * as net from "net"; import type { Event } from "nostr-tools"; -const exec = async (cmd: string, data = ""): Promise<Event[]> => - new Promise((resolve, reject) => { - const c = `${cmd} 2>/dev/null`; - const id = randomUUID(); - const resultSocketPath = `/sockets/result_${id}`; - - const resultServer = createServer((socket) => { - let resultBuffer = ""; - - const results = []; - socket.on("data", (chunk) => { - resultBuffer += chunk.toString(); - const parts = resultBuffer.split("\n"); - - for (let i = 0; i < parts.length - 1; i++) { - try { - const parsedObject = JSON.parse(parts[i]); - results.push(parsedObject); - } catch (e) { - console.error("Failed to parse:", parts[i]); - } - } +const controlSocketPath = "/home/bun/app/data/sockets/strsrv.sock"; - resultBuffer = parts[parts.length - 1]; - }); - - socket.on("end", () => { - socket.write("DONE"); - resolve(results); - socket.end(); - - resultServer.close(); - }); - }); - - resultServer.listen(resultSocketPath, () => { - const controlClient = connect("/sockets/ctrl", () => { - // Send the command and data payload - const message = `${resultSocketPath} ${c}\n${data}\n`; - controlClient.write(message); - controlClient.end(); - }); - - controlClient.on("error", (e) => { - reject(`Control socket error: ${e.message}`); +export const scan = (f: object): Promise<Event[]> => + new Promise((resolve, reject) => { + const result = []; + const client = net + .createConnection(controlSocketPath) + .on("connect", () => { + client.write(`${JSON.stringify(f)}\n`); + }) + .on("data", (data) => { + result.push(data); + }) + .on("end", () => { + resolve( + Buffer.concat(result) + .toString("utf8") + .split("\n") + .filter((l) => l) + .map((l) => JSON.parse(l) as Event), + ); + }) + .on("error", (e) => { + reject(e.message); }); - }); - - resultServer.on("error", (e) => { - reject(`Result server error: ${e.message}`); - }); }); - -// const filter2params = (f) => { -// let r = ""; -// -// const { limit, since, until } = f; -// -// if (limit) r += `-l ${limit} `; -// if (since) r += `-s ${since} `; -// if (until) r += `-u ${until} `; -// -// for (const k of f.kinds || []) r += `-k ${k} `; -// for (const a of f.authors || []) r += `-a ${a} `; -// for (const e of f["#e"] || []) r += `-e ${e} `; -// for (const p of f["#p"] || []) r += `-p ${p} `; -// -// return r; -// }; - -export const load = (data) => - exec( - `echo '${data.replace(/'/g, "'\\''")}' | /app/strfry import --no-verify\n`, - ); -export const count = (f) => - exec(`/app/strfry scan --count '${JSON.stringify(f)}'\n`); -export const scan = (f) => exec(`/app/strfry scan '${JSON.stringify(f)}'\n`); -export const sync = (r, f) => - exec(`/app/strfry sync ${r} --dir down --filter '${JSON.stringify(f)}'\n`); diff --git a/lib/utils.ts b/lib/utils.ts index a0a831d9..79b95e62 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -2,7 +2,6 @@ import config from "$config"; import { g, s } from "$lib/db"; import locales from "$lib/locales/index"; import migrate from "$lib/migrate"; -import whitelist from "$lib/whitelist"; import { bytesToHex, randomBytes } from "@noble/hashes/utils"; import { getPublicKey } from "nostr"; diff --git a/routes/nostr.ts b/routes/nostr.ts index 962e45f9..5000ccc7 100644 --- a/routes/nostr.ts +++ b/routes/nostr.ts @@ -1,3 +1,4 @@ +import config from "$config"; import { db, g, s } from "$lib/db"; import ln from "$lib/ln"; import { @@ -6,11 +7,12 @@ import { getCount, getNostrUser, getProfile, + pool, q, serverPubkey, } from "$lib/nostr"; import { parseContent } from "$lib/notes"; -import { load, scan, sync } from "$lib/strfry"; +import { scan } from "$lib/strfry"; import { bail, fail, fields, getUser } from "$lib/utils"; import got from "got"; import type { Event } from "nostr-tools"; @@ -36,18 +38,12 @@ export default { if (!full) return res.send(event); const parts = parseContent(event); - const names = {}; - for (const { type, value } of parts) { - if (type.includes("nprofile") || type.includes("npub")) { - const { name } = await getProfile(value.pubkey); - names[value.pubkey] = name; - } - } - - event.parts = parts; - event.names = names; - event.author = await getProfile(event.pubkey); + let pubkeys = parts + .filter( + ({ type }) => type.includes("nprofile") || type.includes("npub"), + ) + .map(({ value }) => value.pubkey); const zapEvents = await scan({ kinds: [9735], "#e": [id] }); const zaps = []; @@ -61,16 +57,52 @@ export default { if (amount_msat) amount = Math.round(amount_msat / 1000); } catch (e) {} - let user; - if (pubkey) { - try { - user = await getNostrUser(pubkey); - zaps.push({ amount, user }); - } catch (e) {} - } + zaps.push({ amount, pubkey }); } - event.zaps = zaps.filter((z) => z.amount > 0); + pubkeys.push(event.pubkey); + pubkeys.push(...zaps.map((z) => z.pubkey)); + pubkeys = [...new Set(pubkeys)]; + const profiles = await scan({ kinds: [0], authors: pubkeys }); + const found = profiles.map((p) => p.pubkey); + const missing = pubkeys.filter((p) => !found.includes(p)); + + const missingProfiles = ( + await pool.querySync(config.relays, { + kinds: [0], + authors: missing, + }) + ) + .reduce((a, b) => { + a.set( + b.pubkey, + b.created_at > (a.get(b.pubkey)?.created_at || 0) + ? b + : a.get(b.pubkey), + ); + return a; + }, new Map()) + .values(); + + profiles.push(...missingProfiles); + + event.parts = parts; + event.names = profiles.reduce((a, b) => { + const { content } = b; + const { name } = JSON.parse(content); + a[b.pubkey] = name; + return a; + }, {}); + + event.author = profiles.find((p) => p.pubkey === event.pubkey); + + event.zaps = zaps + .filter((z) => z.amount > 0) + .map(({ amount, pubkey }) => ({ + amount, + user: profiles.find((p) => p.pubkey === pubkey), + })); + await db.set(k, JSON.stringify(event), { EX }); res.send(event); Binary files differ |
