diff --git a/packages/app/src/Element/NoteCreator.tsx b/packages/app/src/Element/NoteCreator.tsx index 825a7f94..2383363b 100644 --- a/packages/app/src/Element/NoteCreator.tsx +++ b/packages/app/src/Element/NoteCreator.tsx @@ -58,6 +58,7 @@ export function NoteCreator(props: NoteCreatorProps) { try { const svc = new LNURL(zapForward); await svc.load(); + extraTags = [svc.getZapTag()]; } catch { setError( formatMessage({ @@ -66,7 +67,6 @@ export function NoteCreator(props: NoteCreatorProps) { ); return; } - extraTags = [["zap", zapForward]]; } const ev = replyTo ? await publisher.reply(replyTo, note, extraTags) : await publisher.note(note, extraTags); console.debug("Sending note: ", ev); diff --git a/packages/app/src/Element/NoteFooter.tsx b/packages/app/src/Element/NoteFooter.tsx index f11e7cdb..cf7a35c8 100644 --- a/packages/app/src/Element/NoteFooter.tsx +++ b/packages/app/src/Element/NoteFooter.tsx @@ -154,11 +154,16 @@ export default function NoteFooter(props: NoteFooterProps) { } function getLNURL() { - return ev.Tags.find(a => a.Key === "zap")?.LNURL || author?.lud16 || author?.lud06; + return ev.tags.find(a => a[0] === "zap")?.[1] || author?.lud16 || author?.lud06; } function getTargetName() { - return ev.Tags.find(a => a.Key === "zap")?.LNURL || author?.display_name || author?.name; + const zapTarget = ev.tags.find(a => a[0] === "zap")?.[1]; + if (zapTarget) { + return new LNURL(zapTarget).name; + } else { + return author?.display_name || author?.name; + } } async function fastZap(e?: React.MouseEvent) { diff --git a/packages/app/src/LNURL.ts b/packages/app/src/LNURL.ts index 94b3485b..2f80f692 100644 --- a/packages/app/src/LNURL.ts +++ b/packages/app/src/LNURL.ts @@ -48,6 +48,62 @@ export class LNURL { } } + /** + * URL of this payService + */ + get url() { + return this.#url; + } + + /** + * Return the optimal formatted LNURL + */ + get lnurl() { + if (this.isLNAddress) { + return this.getLNAddress(); + } + return this.#url.toString(); + } + + /** + * Human readable name for this service + */ + get name() { + // LN Address formatted URL + if (this.isLNAddress) { + return this.getLNAddress(); + } + // Generic LUD-06 url + return this.#url.hostname; + } + + /** + * Is this LNURL a LUD-16 Lightning Address + */ + get isLNAddress() { + return this.#url.pathname.startsWith("/.well-known/lnurlp/"); + } + + /** + * Get the LN Address for this LNURL + */ + getLNAddress() { + const pathParts = this.#url.pathname.split("/"); + const username = pathParts[pathParts.length - 1]; + return `${username}@${this.#url.hostname}`; + } + + /** + * Create a NIP-57 zap tag from this LNURL + */ + getZapTag() { + if (this.isLNAddress) { + return ["zap", this.getLNAddress(), "lud16"]; + } else { + return ["zap", this.#url.toString(), "lud06"]; + } + } + async load() { const rsp = await fetch(this.#url); if (rsp.ok) { diff --git a/packages/app/src/Pages/ProfilePage.tsx b/packages/app/src/Pages/ProfilePage.tsx index 4c9500bf..970c8904 100644 --- a/packages/app/src/Pages/ProfilePage.tsx +++ b/packages/app/src/Pages/ProfilePage.tsx @@ -24,7 +24,7 @@ import useModeration from "Hooks/useModeration"; import useZapsFeed from "Feed/ZapsFeed"; import { default as ZapElement } from "Element/Zap"; import FollowButton from "Element/FollowButton"; -import { extractLnAddress, parseId, hexToBech32 } from "Util"; +import { parseId, hexToBech32 } from "Util"; import Avatar from "Element/Avatar"; import Timeline from "Element/Timeline"; import Text from "Element/Text"; @@ -43,9 +43,11 @@ import Modal from "Element/Modal"; import BadgeList from "Element/BadgeList"; import { ProxyImg } from "Element/ProxyImg"; import useHorizontalScroll from "Hooks/useHorizontalScroll"; -import messages from "./messages"; import { EmailRegex } from "Const"; -import { getNip05PubKey } from "./Login"; +import { getNip05PubKey } from "Pages/Login"; +import { LNURL } from "LNURL"; + +import messages from "./messages"; const NOTES = 0; const REACTIONS = 1; @@ -111,7 +113,13 @@ export default function ProfilePage() { }); const npub = !id?.startsWith(NostrPrefix.PublicKey) ? hexToBech32(NostrPrefix.PublicKey, id || undefined) : id; - const lnurl = extractLnAddress(user?.lud16 || user?.lud06 || ""); + const lnurl = (() => { + try { + return new LNURL(user?.lud16 || user?.lud06 || ""); + } catch { + // ignored + } + })(); const website_url = user?.website && !user.website.startsWith("http") ? "https://" + user.website : user?.website || ""; // feeds @@ -185,12 +193,12 @@ export default function ProfilePage() { {lnurl && (
setShowLnQr(true)}> - {lnurl} + {lnurl.name}
)} setShowLnQr(false)} author={id} diff --git a/packages/app/src/Util.ts b/packages/app/src/Util.ts index 1172cfc3..dbb277fe 100644 --- a/packages/app/src/Util.ts +++ b/packages/app/src/Util.ts @@ -146,26 +146,6 @@ export function getAllReactions(notes: readonly TaggedRawEvent[] | undefined, id return notes?.filter(a => a.kind === (kind ?? a.kind) && a.tags.some(a => a[0] === "e" && ids.includes(a[1]))) || []; } -/** - * Converts LNURL service to LN Address - */ -export function extractLnAddress(lnurl: string) { - // some clients incorrectly set this to LNURL service, patch this - if (lnurl.toLowerCase().startsWith("lnurl")) { - const url = bech32ToText(lnurl); - if (url.startsWith("http")) { - const parsedUri = new URL(url); - // is lightning address - if (parsedUri.pathname.startsWith("/.well-known/lnurlp/")) { - const pathParts = parsedUri.pathname.split("/"); - const username = pathParts[pathParts.length - 1]; - return `${username}@${parsedUri.hostname}`; - } - } - } - return lnurl; -} - export function unixNow() { return Math.floor(unixNowMs() / 1000); }