diff --git a/deno.json b/deno.json index 724d74ec..4b5982e9 100644 --- a/deno.json +++ b/deno.json @@ -62,7 +62,8 @@ "type-fest": "npm:type-fest@^4.3.0", "unfurl.js": "npm:unfurl.js@^6.4.0", "zod": "npm:zod@^3.23.8", - "~/fixtures/": "./fixtures/" + "~/fixtures/": "./fixtures/", + "light-bolt11-decoder": "npm:light-bolt11-decoder" }, "lint": { "include": ["src/", "scripts/"], diff --git a/deno.lock b/deno.lock index 9c44395b..03682e37 100644 --- a/deno.lock +++ b/deno.lock @@ -37,7 +37,6 @@ "npm:@scure/base@^1.1.6": "npm:@scure/base@1.1.6", "npm:@scure/bip32@^1.4.0": "npm:@scure/bip32@1.4.0", "npm:@scure/bip39@^1.3.0": "npm:@scure/bip39@1.3.0", - "npm:@types/node": "npm:@types/node@18.16.19", "npm:comlink@^4.4.1": "npm:comlink@4.4.1", "npm:entities@^4.5.0": "npm:entities@4.5.0", "npm:fast-stable-stringify@^1.0.0": "npm:fast-stable-stringify@1.0.0", @@ -47,6 +46,7 @@ "npm:kysely@0.27.3": "npm:kysely@0.27.3", "npm:kysely@^0.27.2": "npm:kysely@0.27.3", "npm:kysely@^0.27.3": "npm:kysely@0.27.3", + "npm:light-bolt11-decoder": "npm:light-bolt11-decoder@3.1.1", "npm:linkify-plugin-hashtag@^4.1.1": "npm:linkify-plugin-hashtag@4.1.3_linkifyjs@4.1.3", "npm:linkify-string@^4.1.1": "npm:linkify-string@4.1.3_linkifyjs@4.1.3", "npm:linkifyjs@^4.1.1": "npm:linkifyjs@4.1.3", @@ -313,10 +313,6 @@ "@types/trusted-types": "@types/trusted-types@2.0.7" } }, - "@types/node@18.16.19": { - "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", - "dependencies": {} - }, "@types/trusted-types@2.0.7": { "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "dependencies": {} @@ -636,6 +632,12 @@ "integrity": "sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==", "dependencies": {} }, + "light-bolt11-decoder@3.1.1": { + "integrity": "sha512-sLg/KCwYkgsHWkefWd6KqpCHrLFWWaXTOX3cf6yD2hAzL0SLpX+lFcaFK2spkjbgzG6hhijKfORDc9WoUHwX0A==", + "dependencies": { + "@scure/base": "@scure/base@1.1.1" + } + }, "lilconfig@3.0.0": { "integrity": "sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==", "dependencies": {} @@ -1382,6 +1384,7 @@ "npm:iso-639-1@2.1.15", "npm:isomorphic-dompurify@^2.11.0", "npm:kysely@^0.27.3", + "npm:light-bolt11-decoder", "npm:linkify-plugin-hashtag@^4.1.1", "npm:linkify-string@^4.1.1", "npm:linkifyjs@^4.1.1", diff --git a/src/controllers/api/statuses.ts b/src/controllers/api/statuses.ts index 7b9e82cd..28e0778a 100644 --- a/src/controllers/api/statuses.ts +++ b/src/controllers/api/statuses.ts @@ -8,8 +8,9 @@ import { z } from 'zod'; import { type AppController } from '@/app.ts'; import { Conf } from '@/config.ts'; import { DittoDB } from '@/db/DittoDB.ts'; -import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; +import { getAmount } from '@/utils/bolt11.ts'; import { getAncestors, getAuthor, getDescendants, getEvent } from '@/queries.ts'; +import { getUnattachedMediaByIds } from '@/db/unattached-media.ts'; import { renderEventAccounts } from '@/views.ts'; import { renderReblog, renderStatus } from '@/views/mastodon/statuses.ts'; import { Storages } from '@/storages.ts'; @@ -547,15 +548,22 @@ const zappedByController: AppController = async (c) => { const store = await Storages.db(); const amountSchema = z.coerce.number().int().nonnegative().catch(0); - const events: DittoEvent[] = (await store.query([{ kinds: [9735], '#e': [id], limit: 100 }])).map((event) => { - const zapRequest = event.tags.find(([name]) => name === 'description')?.[1]; - if (!zapRequest) return; + const events = (await store.query([{ kinds: [9735], '#e': [id], limit: 100 }])).map((event) => { + const zapRequestString = event.tags.find(([name]) => name === 'description')?.[1]; + if (!zapRequestString) return; try { - return JSON.parse(zapRequest); + const zapRequest = n.json().pipe(n.event()).parse(zapRequestString); + const amount = zapRequest?.tags.find(([name]: any) => name === 'amount')?.[1]; + if (!amount) { + const amount = getAmount(event?.tags.find(([name]) => name === 'bolt11')?.[1]); + if (!amount) return; + zapRequest.tags.push(['amount', amount]); + } + return zapRequest; } catch { return; } - }).filter(Boolean); + }).filter(Boolean) as DittoEvent[]; await hydrateEvents({ events, store }); diff --git a/src/utils/bolt11.test.ts b/src/utils/bolt11.test.ts new file mode 100644 index 00000000..9a9d1238 --- /dev/null +++ b/src/utils/bolt11.test.ts @@ -0,0 +1,19 @@ +import { assertEquals } from '@std/assert'; +import { getAmount } from '@/utils/bolt11.ts'; + +Deno.test('Invoice is invalid', () => { + assertEquals(getAmount('hello'), undefined); +}); + +Deno.test('Invoice is undefined', () => { + assertEquals(getAmount(undefined), undefined); +}); + +Deno.test('Amount is 200000', () => { + assertEquals( + getAmount( + 'lnbc2u1pn8qatypp5dweqaltlry2vgpxxyc0puxnc50335yznevj2g46wrhfm2694lhgqhp576ekte7lhhtsxdk6tfvkpyp8gdk2xccmuccdxwjd0fqdh34wfseqcqzzsxqyz5vqsp5n44zva7xndawg5l2r9d85v0tszwejtfzkc7v90d6c7d3nsdt0qds9qxpqysgqx2v2artsxmnfkpapdm9f5pahjs8etlpe7kcjue2kffhjg3jrtearstjvenr6lxzhpw3es4hpchzzeet7ul88elurfmvr9v94v0655rgpy7m7r5', + ), + '200000', + ); +}); diff --git a/src/utils/bolt11.ts b/src/utils/bolt11.ts new file mode 100644 index 00000000..eef60684 --- /dev/null +++ b/src/utils/bolt11.ts @@ -0,0 +1,17 @@ +import bolt11 from 'light-bolt11-decoder'; + +/** Decodes the invoice and returns the amount in millisatoshis */ +function getAmount(invoice: string | undefined): string | undefined { + if (!invoice) return; + + try { + const amount = (bolt11.decode(invoice).sections as { name: string; value: string }[]).find( + ({ name }) => name === 'amount', + )?.value; + return amount; + } catch { + return; + } +} + +export { getAmount };