aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/parse.ts
blob: 663b2e42ed60fcc14bcd05f8be3e427526451c9e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { PUBLIC_DOMAIN } from "$env/static/public";
import { decode } from "$lib/bip21";
import { auth, get, isLiquid, post, sats } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
import { validate } from "bitcoin-address-validation";

export default async (s, host, cookies) => {
	if (!s) return;
	let t = s.trim();
	let amount;
	let invoice;
	let user;

	if (t.startsWith("http")) redirect(307, t);
	if (t.startsWith(host)) redirect(307, `http://${t}`);

	if (t.includes("lightning=") || t.includes("lno=")) {
		const url = new URL(t);
		const params = new URLSearchParams(url.search);
		t = params.get("lightning") || params.get("lno");
	}

	if (t.startsWith("lightning:")) t = t.replace("lightning:", "");
	// Our own domain is no longer only ours: coinos v3 payment addresses live
	// here too and are paid over lightning like anyone else's. Only strip the
	// domain (and pay internally) when the name really is a local account.
	if (t.includes("@") && t.split("@").pop() === PUBLIC_DOMAIN) {
		const name = t.split("@")[0];
		try {
			const u = await get(`/users/${name}`);
			if (u && !u.anon) t = name;
		} catch (e) {}
	}
	if (t.includes("@") && t.includes(".")) {
		try {
			t = await get(`/encode?address=${t}`);
		} catch (e) {}
	}

	if (t.toLowerCase().startsWith("nostr:")) {
		t = t.split(":")[1];
	}

	if (["note", "nevent"].some((p) => t.startsWith(p))) redirect(307, `/e/${t}`);
	if (["nprofile", "npub"].some((p) => t.startsWith(p))) redirect(307, `/${t}`);

	// bitcoin
	if (
		t.toLowerCase().startsWith("bitcoin:") ||
		t.toLowerCase().startsWith("liquidnetwork:")
	) {
		({
			address: t,
			options: { amount },
		} = decode(t));
	}

	if (validate(t) || isLiquid(t)) {
		let r = `/send/bitcoin/${t}`;
		if (amount) r += `/${Math.round(amount * sats)}`;
		redirect(307, r);
	}

	// lightning
	if (t.toLowerCase().startsWith("lightning:"))
		t = t.toLowerCase().replace("lightning:", "");
	if (t.toLowerCase().startsWith("lnurl")) redirect(307, `/ln/${t}`);
	if (t.includes(":")) t = t.split(":")[1];

	if (t.toLowerCase().startsWith("lnr")) {
		const { iid } = await post("/sendinvoice", { invreq: t }, auth(cookies));
		invoice = { id: iid };
	} else if (t.toLowerCase().startsWith("ln")) {
		try {
			invoice = await get(`/invoice/${t}`);
		} catch (e) {
			if (t.toLowerCase().startsWith("lno")) {
				const { offer_amount_msat: a } = await get(`/decode/${t}`);
				if (a) ({ invoice: t } = await post("/fetchinvoice", { offer: t }));
			}
			redirect(307, `/send/lightning/${t}`);
		}
	}

	if (invoice) redirect(307, `/invoice/${invoice.id}`);

	if (t.match(/^🥜([\uFE00-\uFE0F]|[\uE0100-\uE01EF]+)$/)) {
		t = Array.from(t)
			.slice(1)
			.map((char) => {
				const codePoint = char.codePointAt(0);

				// Handle Variation Selectors (VS1-VS16): U+FE00 to U+FE0F
				if (codePoint >= 0xfe00 && codePoint <= 0xfe0f) {
					const byteValue = codePoint - 0xfe00; // Maps FE00->0, FE01->1, ..., FE0F->15
					return String.fromCharCode(byteValue);
				}

				// Handle Variation Selectors Supplement (VS17-VS256): U+E0100 to U+E01EF
				if (codePoint >= 0xe0100 && codePoint <= 0xe01ef) {
					const byteValue = codePoint - 0xe0100 + 16; // Maps E0100->16, E0101->17, ..., E01EF->255
					return String.fromCharCode(byteValue);
				}
			})
			.join("");
	}

	if (t.startsWith("cashu")) {
		const { id } = await post("/cash", { token: t });
		redirect(307, `/ecash/${id}`);
	}

	if (t.startsWith("creq")) redirect(307, `/send/ecash/${t}`);

	// user
	try {
		user = await get(`/users/${t.split("/")[0]}`);
		if (user.anon) user = null;
	} catch (e) {}

	if (user) redirect(307, `/pay/${t}`);

	let fund;
	if (t.includes("/fund")) redirect(307, t.substring(t.indexOf("/fund")));

	try {
		fund = await get(`/fund/${t}`);
	} catch (e) {}

	if (fund) redirect(307, `/send/fund/${t}`);

	try {
		invoice ||= await get(`/invoice/${t}`);
	} catch (e) {}

	if (invoice) redirect(307, `/invoice/${invoice.id}`);
};