diff options
| author | Adam Soltys <[email protected]> | 2025-01-21 23:28:00 -0800 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2025-01-21 23:28:00 -0800 |
| commit | 9662c2754834599a1074d12565279674c20af467 (patch) | |
| tree | 1d77aaedf19b035378236305af582c903e025467 | |
| parent | eb4e16b5599e67a65962db2a07629dfdcdda57dc (diff) | |
| download | coinos-server-9662c2754834599a1074d12565279674c20af467.tar.gz coinos-server-9662c2754834599a1074d12565279674c20af467.zip | |
notes and stuff
| -rw-r--r-- | index.ts | 1 | ||||
| -rw-r--r-- | lib/lightning.ts | 12 | ||||
| -rw-r--r-- | lib/nostr.ts | 38 | ||||
| -rw-r--r-- | lib/nwc.ts | 2 | ||||
| -rw-r--r-- | lib/payments.ts | 2 | ||||
| -rw-r--r-- | lib/strfry.ts | 39 | ||||
| -rw-r--r-- | routes/nostr.ts | 109 | ||||
| -rw-r--r-- | routes/payments.ts | 1 | ||||
| -rwxr-xr-x | server | bin | 16960 -> 17208 bytes | |||
| -rw-r--r-- | server.c | 236 |
10 files changed, 275 insertions, 165 deletions
@@ -53,6 +53,7 @@ app.get("/profile/:profile", nostr.profile); app.get("/:pubkey/count", nostr.count); app.get("/:pubkey/followers", nostr.followers); app.get("/:pubkey/follows", nostr.follows); +app.get("/:pubkey/events", nostr.events); app.get("/event/:id", nostr.event); app.post("/event", auth, nostr.publish); app.post("/parseEvent", nostr.parse); diff --git a/lib/lightning.ts b/lib/lightning.ts index 19f6949d..3888a405 100644 --- a/lib/lightning.ts +++ b/lib/lightning.ts @@ -3,7 +3,7 @@ import ln from "$lib/ln"; import { err, warn } from "$lib/logging"; import { handleZap } from "$lib/nostr"; import { credit, types } from "$lib/payments"; -import { getPayment } from "$lib/utils"; +import { getPayment, getUser } from "$lib/utils"; export async function listenForLightning() { const inv = await ln.waitanyinvoice((await g("pay_index")) || 0); @@ -35,7 +35,10 @@ export async function listenForLightning() { if (invoice?.memo) { try { - if (JSON.parse(description).kind === 9734) handleZap(inv); + if (JSON.parse(description).kind === 9734) { + const { pubkey } = await getUser(invoice.uid); + handleZap(inv, pubkey); + } } catch (e) { if (!e.message.includes("Unexpected")) warn("failed to handle zap", e.message); @@ -81,7 +84,10 @@ export async function replay(index) { if (invoice?.memo) { try { - if (JSON.parse(description).kind === 9734) handleZap(inv); + if (JSON.parse(description).kind === 9734) { + const { pubkey } = await getUser(invoice.uid); + handleZap(inv, pubkey); + } } catch (e) { if (!e.message.includes("Unexpected")) warn("failed to handle zap", e.message); diff --git a/lib/nostr.ts b/lib/nostr.ts index 190873e2..21c254e3 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -33,7 +33,7 @@ export async function send(ev, url = config.nostr) { r.close(); } -export async function handleZap(invoice) { +export async function handleZap(invoice, sender = undefined) { try { const pubkey = serverPubkey; const sk = nip19.decode(config.nostrKey).data as Uint8Array; @@ -59,6 +59,10 @@ export async function handleZap(invoice) { fail("Expected none or 1 e tags"); } + const atags = zapreq.tags.filter( + (t) => t?.length && t.length >= 2 && t[0] === "a", + ); + const relays_tag = zapreq.tags.find( (t) => t?.length && t.length >= 2 && t[0] === "relays", ); @@ -69,6 +73,7 @@ export async function handleZap(invoice) { const relays = relays_tag.slice(1).filter((r) => r?.startsWith("ws")); const etag = etags.length > 0 && etags[0]; + const atag = atags.length > 0 && atags[0]; const ptag = ptags[0]; const kind = 9735; @@ -77,6 +82,8 @@ export async function handleZap(invoice) { const tags = [ptag]; if (etag) tags.push(etag); + if (atag) tags.push(atag); + if (sender) tags.push(["P", sender]); tags.push(["bolt11", invoice.bolt11]); tags.push(["description", invoice.description]); @@ -129,11 +136,10 @@ export const getProfile = async (pubkey) => { let profile = await g(`profile:${pubkey}`); if (profile) return profile; - const filter = { authors: [pubkey], kinds: [0] }; - const events = await scan(filter); + const event = await get({ authors: [pubkey], kinds: [0] }); - if (events.length) { - profile = JSON.parse(events[0].content); + if (event) { + profile = JSON.parse(event.content); } else { profile = anon(pubkey); } @@ -191,3 +197,25 @@ export const getNostrUser = async (key) => { return pick(user, fields); }; + +export const q = async (f) => { + const events = await scan(f); + 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; + }); + + if (events.length > 0) return events; + return p; +}; + +export const get = async (f) => { + f.limit = 1; + const events = await q(f); + return events[0]; +}; @@ -113,7 +113,7 @@ const handle = (method, params, user, ev) => inv.payment_preimage = preimage; inv.paid_at = Math.floor(Date.now() / 1000); try { - await handleZap(inv); + await handleZap(inv, pubkey); } catch (e) { console.log("zap receipt failed", e); } diff --git a/lib/payments.ts b/lib/payments.ts index 5f818ae4..1468a198 100644 --- a/lib/payments.ts +++ b/lib/payments.ts @@ -567,7 +567,7 @@ export const sendInternal = async ({ const inv = invoices[0]; inv.payment_preimage = p.id; inv.paid_at = Math.floor(Date.now() / 1000); - handleZap(inv).catch(console.log); + handleZap(inv, sender.pubkey).catch(console.log); } return p; diff --git a/lib/strfry.ts b/lib/strfry.ts index 231e3533..926ca429 100644 --- a/lib/strfry.ts +++ b/lib/strfry.ts @@ -1,9 +1,10 @@ import { randomUUID } from "crypto"; import { connect, createServer } from "net"; +import type { Event } from "nostr-tools"; -const exec = async (cmd) => +const exec = async (cmd: string, data = ""): Promise<Event[]> => new Promise((resolve, reject) => { - const c = `/app/strfry ${cmd} 2>/dev/null`; + const c = `${cmd} 2>/dev/null`; const id = randomUUID(); const resultSocketPath = `/sockets/result_${id}`; @@ -11,8 +12,8 @@ const exec = async (cmd) => let resultBuffer = ""; const results = []; - socket.on("data", (data) => { - resultBuffer += data.toString(); + socket.on("data", (chunk) => { + resultBuffer += chunk.toString(); const parts = resultBuffer.split("\n"); for (let i = 0; i < parts.length - 1; i++) { @@ -20,7 +21,7 @@ const exec = async (cmd) => const parsedObject = JSON.parse(parts[i]); results.push(parsedObject); } catch (e) { - console.error(parts[i]); + console.error("Failed to parse:", parts[i]); } } @@ -38,7 +39,8 @@ const exec = async (cmd) => resultServer.listen(resultSocketPath, () => { const controlClient = connect("/sockets/ctrl", () => { - const message = `${resultSocketPath} ${c}\n`; + // Send the command and data payload + const message = `${resultSocketPath} ${c}\n${data}\n`; controlClient.write(message); controlClient.end(); }); @@ -53,7 +55,26 @@ const exec = async (cmd) => }); }); -export const count = (f) => exec(`scan --count '${JSON.stringify(f)}'\n`); -export const scan = (f) => exec(`scan '${JSON.stringify(f)}'\n`); +// 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}' | /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(`sync ${r} --dir down --filter '${JSON.stringify(f)}'\n`); + exec(`/app/strfry sync ${r} --dir down --filter '${JSON.stringify(f)}'\n`); diff --git a/routes/nostr.ts b/routes/nostr.ts index 61e23c30..0bfe9970 100644 --- a/routes/nostr.ts +++ b/routes/nostr.ts @@ -3,35 +3,31 @@ import { db } from "$lib/db"; import ln from "$lib/ln"; import { anon, + get, getCount, getNostrUser, getProfile, getRelays, + q, send, serverPubkey, } from "$lib/nostr"; import { parseContent } from "$lib/notes"; -import { scan, sync } from "$lib/strfry"; +import { load, scan, sync } from "$lib/strfry"; import { bail, fail, fields, getUser } from "$lib/utils"; import got from "got"; import type { Event } from "nostr-tools"; +import { verifyEvent } from "nostr-tools"; import { decode } from "nostr-tools/nip19"; import type { ProfilePointer } from "nostr-tools/nip19"; import { getZapEndpoint, makeZapRequest } from "nostr-tools/nip57"; -import { Relay } from "nostr-tools/relay"; export default { async event(req, res) { let { id } = req.params; if (id.startsWith("nevent")) id = decode(id).data.id; if (id.startsWith("note")) id = decode(id).data; - const filter = { ids: [id] }; - let events = await scan(filter); - - if (!events.length) { - await sync("wss://relay.primal.net", filter); - events = await scan(filter); - } + const events = await q({ ids: [id] }); if (!events.length) return bail(res, "event not found"); @@ -56,41 +52,22 @@ export default { async thread(req, res) { try { const { id } = req.params; - const relay = await Relay.connect("wss://relay.primal.net"); - console.log("ID", id); - const event: Event = await new Promise((resolve) => { - const sub = relay.subscribe([{ ids: [id] }], { - async onevent(event) { - resolve(event); - }, - oneose() { - sub.close(); - }, - }); - }); + const event = await get({ ids: [id] }); - const thread = []; + const rootId = event.tags.find( + (tag) => tag[0] === "e" && tag[3] === "root", + )?.[1]; - const root = - event.tags.find((tag) => tag[0] === "e" && tag[3] === "root")?.[1] || - event.id; + let root; + if (rootId) root = await get({ ids: [rootId] }); + else root = event; - await new Promise<void>((resolve) => { - const sub = relay.subscribe([{ kinds: [1], "#e": [root] }], { - async onevent(event) { - thread.push(event); - }, - oneose() { - sub.close(); - resolve(); - }, - }); - }); + const thread = [root, ...(await q({ kinds: [1], "#e": [root.id] }))]; - for (const e of thread) { + for (const t of thread) { + const e = t as any; e.author = await getNostrUser(e.pubkey); - console.log("AUTHOR", e.author); e.parts = parseContent(e); e.names = {}; for (const { type, value } of e.parts) { @@ -135,13 +112,10 @@ export default { let user; if (pubkey) { try { - user = await getUser(pubkey, fields); - if (!user) user = await getProfile(pubkey); - user.pubkey = pubkey; + user = await getNostrUser(pubkey); + zaps.push({ amount, user }); } catch (e) {} } - - zaps.push({ amount, user }); } res.send(zaps.filter((z) => z.amount > 0)); @@ -155,20 +129,10 @@ export default { try { const { event } = req.body; const { pubkey } = req.user; - const { write: relays } = await getRelays(pubkey); - if (!relays.includes(config.nostr)) relays.push(config.nostr); - - let ok; - for (const url of relays) { - try { - await send(event, url); - ok = true; - } catch (e) { - console.log("failed to publish to", url); - } - } - - if (!ok) fail("failed to publish"); + // const { write: relays } = await getRelays(pubkey); + // if (!relays.includes(config.nostr)) relays.push(config.nostr); + if (!verifyEvent(event)) fail("Invalid event"); + await load(JSON.stringify(event)); if (event.kind === 3) db.del(`${pubkey}:follows`); res.send({}); @@ -223,6 +187,33 @@ export default { } }, + async events(req, res) { + const { + params: { pubkey }, + } = req; + try { + const events = await q({ kinds: [1], authors: [pubkey], limit: 20 }); + + for (const v of events) { + const e = v as any; + e.author = await getNostrUser(e.pubkey); + e.parts = parseContent(e); + e.names = {}; + for (const { type, value } of e.parts) { + if (type.includes("nprofile") || type.includes("npub")) { + const { name } = await getProfile(value.pubkey); + e.names[value.pubkey] = name; + } + } + } + + res.send(events); + } catch (e) { + console.log(e); + bail(res, e.message); + } + }, + async followers(req, res) { const { params: { pubkey }, @@ -306,7 +297,8 @@ export default { async zapRequest(req, res) { try { - const { amount, pubkey, id } = req.body; + const { amount, id } = req.body; + const { pubkey } = await get({ ids: [id] }); const event = await makeZapRequest({ profile: pubkey, event: id, @@ -328,6 +320,7 @@ export default { const pubkey = event.tags.find((t) => t[0] === "p")[1]; const content = JSON.stringify(await getProfile(pubkey)); const callback = await getZapEndpoint({ content } as Event); + if (!callback || callback === "null") fail("Lightning address not found"); const encodedEvent = encodeURI(JSON.stringify(event)); const url = `${callback}?amount=${amount}&nostr=${encodedEvent}`; diff --git a/routes/payments.ts b/routes/payments.ts index 1ff0be05..93e5d3da 100644 --- a/routes/payments.ts +++ b/routes/payments.ts @@ -40,6 +40,7 @@ export default { async create(req, res) { const { body, user } = req; + let { amount, hash, fee, fund, memo, payreq } = body; const balance = await g(`balance:${user.id}`); @@ -6,119 +6,179 @@ #include <unistd.h> #define SOCKET_PATH "/sockets/ctrl" -#define BUFFER_SIZE 262144 +#define INITIAL_BUFFER_SIZE 1024 -void handle_client(const char *result_socket, const char *command); +void handle_client(const char *result_socket, const char *command, const char *data); int main() { - int server_fd, client_fd; - struct sockaddr_un server_addr; - char buffer[BUFFER_SIZE]; + int server_fd, client_fd; + struct sockaddr_un server_addr; - unlink(SOCKET_PATH); + // Unlink the socket file in case it exists + unlink(SOCKET_PATH); - server_fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (server_fd == -1) { - perror("socket failed"); - exit(EXIT_FAILURE); - } + server_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (server_fd == -1) { + perror("socket failed"); + exit(EXIT_FAILURE); + } - memset(&server_addr, 0, sizeof(struct sockaddr_un)); - server_addr.sun_family = AF_UNIX; - strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1); + memset(&server_addr, 0, sizeof(struct sockaddr_un)); + server_addr.sun_family = AF_UNIX; + strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(server_addr.sun_path) - 1); - if (bind(server_fd, (struct sockaddr *)&server_addr, - sizeof(struct sockaddr_un)) == -1) { - perror("bind failed"); - close(server_fd); - exit(EXIT_FAILURE); - } + if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un)) == -1) { + perror("bind failed"); + close(server_fd); + exit(EXIT_FAILURE); + } - if (listen(server_fd, 5) == -1) { - perror("listen failed"); - close(server_fd); - exit(EXIT_FAILURE); - } + if (listen(server_fd, 5) == -1) { + perror("listen failed"); + close(server_fd); + exit(EXIT_FAILURE); + } - printf("Server listening on %s\n", SOCKET_PATH); + printf("Server listening on %s\n", SOCKET_PATH); - while (1) { + while (1) { + client_fd = accept(server_fd, NULL, NULL); + if (client_fd == -1) { + perror("accept failed"); + continue; + } - client_fd = accept(server_fd, NULL, NULL); - if (client_fd == -1) { - perror("accept failed"); - continue; - } + size_t buffer_size = INITIAL_BUFFER_SIZE; + char *buffer = malloc(buffer_size); + if (!buffer) { + perror("malloc failed"); + close(client_fd); + continue; + } - memset(buffer, 0, BUFFER_SIZE); - ssize_t num_bytes = read(client_fd, buffer, BUFFER_SIZE - 1); - if (num_bytes > 0) { + size_t total_bytes_read = 0; + ssize_t num_bytes; - buffer[num_bytes] = '\0'; + while ((num_bytes = read(client_fd, buffer + total_bytes_read, buffer_size - total_bytes_read - 1)) > 0) { + total_bytes_read += num_bytes; - printf("Received: %s\n", buffer); + // Expand buffer if necessary + if (total_bytes_read >= buffer_size - 1) { + buffer_size *= 2; + char *new_buffer = realloc(buffer, buffer_size); + if (!new_buffer) { + perror("realloc failed"); + free(buffer); + close(client_fd); + continue; + } + buffer = new_buffer; + } + } - char result_socket[BUFFER_SIZE], command[BUFFER_SIZE]; - if (sscanf(buffer, "%s %[^\n]", result_socket, command) == 2) { - printf("Result socket: %s\n", result_socket); - printf("Command: %s\n", command); + if (num_bytes == -1) { + perror("read failed"); + free(buffer); + close(client_fd); + continue; + } - handle_client(result_socket, command); - } else { - fprintf(stderr, "Invalid input format\n"); - } - } + buffer[total_bytes_read] = '\0'; // Null-terminate the string + + printf("Received: %s\n", buffer); + + // Split message into result_socket, command, and data + char *result_socket = strtok(buffer, " "); + char *command = strtok(NULL, "\n"); + char *data = strtok(NULL, ""); - close(client_fd); - } + if (result_socket && command) { + printf("Result socket: %s\n", result_socket); + printf("Command: %s\n", command); + if (data) { + printf("Data: %s\n", data); + } + handle_client(result_socket, command, data); + } else { + fprintf(stderr, "Invalid input format\n"); + } + + free(buffer); + close(client_fd); + } - close(server_fd); - unlink(SOCKET_PATH); - return 0; + close(server_fd); + unlink(SOCKET_PATH); + return 0; } -void handle_client(const char *result_socket, const char *command) { - pid_t pid = fork(); +void handle_client(const char *result_socket, const char *command, const char *data) { + pid_t pid = fork(); - if (pid == 0) { + if (pid == 0) { + int result_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (result_fd == -1) { + perror("result socket failed"); + exit(EXIT_FAILURE); + } - int result_fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (result_fd == -1) { - perror("result socket failed"); - exit(EXIT_FAILURE); - } + struct sockaddr_un result_addr; + memset(&result_addr, 0, sizeof(struct sockaddr_un)); + result_addr.sun_family = AF_UNIX; + strncpy(result_addr.sun_path, result_socket, sizeof(result_addr.sun_path) - 1); - struct sockaddr_un result_addr; - memset(&result_addr, 0, sizeof(struct sockaddr_un)); - result_addr.sun_family = AF_UNIX; - strncpy(result_addr.sun_path, result_socket, - sizeof(result_addr.sun_path) - 1); + if (connect(result_fd, (struct sockaddr *)&result_addr, sizeof(struct sockaddr_un)) == -1) { + perror("connect to result socket failed"); + close(result_fd); + exit(EXIT_FAILURE); + } - if (connect(result_fd, (struct sockaddr *)&result_addr, - sizeof(struct sockaddr_un)) == -1) { - perror("connect to result socket failed"); - close(result_fd); - exit(EXIT_FAILURE); - } + int pipe_fd[2]; + if (pipe(pipe_fd) == -1) { + perror("pipe failed"); + close(result_fd); + exit(EXIT_FAILURE); + } - FILE *cmd_output = popen(command, "r"); - if (cmd_output == NULL) { - perror("popen failed"); - close(result_fd); - exit(EXIT_FAILURE); - } + pid_t cmd_pid = fork(); + if (cmd_pid == 0) { + // Child process: run the command + dup2(pipe_fd[0], STDIN_FILENO); // Redirect stdin + close(pipe_fd[0]); + close(pipe_fd[1]); + close(result_fd); - char output_buffer[BUFFER_SIZE]; - while (fgets(output_buffer, sizeof(output_buffer), cmd_output) != NULL) { - write(result_fd, output_buffer, strlen(output_buffer)); - } + execlp("/bin/sh", "sh", "-c", command, (char *)NULL); + perror("execlp failed"); + exit(EXIT_FAILURE); + } - pclose(cmd_output); - shutdown(result_fd, SHUT_WR); - close(result_fd); - unlink(result_socket); - exit(0); - } else if (pid < 0) { - perror("fork failed"); - } + // Parent process: send data and collect output + close(pipe_fd[0]); // Close read end of the pipe + + if (data) { + write(pipe_fd[1], data, strlen(data)); + } + close(pipe_fd[1]); // Close write end of the pipe + + FILE *cmd_output = fdopen(result_fd, "w"); + if (!cmd_output) { + perror("fdopen failed"); + close(result_fd); + exit(EXIT_FAILURE); + } + + char output_buffer[INITIAL_BUFFER_SIZE]; + while (fgets(output_buffer, sizeof(output_buffer), cmd_output) != NULL) { + write(result_fd, output_buffer, strlen(output_buffer)); + } + + fclose(cmd_output); + shutdown(result_fd, SHUT_WR); + close(result_fd); + unlink(result_socket); + exit(0); + } else if (pid < 0) { + perror("fork failed"); + } } |
