aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib/parse.ts
blob: b4bf837c0a1e75956a28bb988fbdeccbf91159f7 (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
import { PUBLIC_DOMAIN } from "$env/static/public";
import { decode } from "$lib/bip21";
import { get, isLiquid, post, sats } from "$lib/utils";
import { redirect } from "@sveltejs/kit";
import { validate } from "bitcoin-address-validation";

export default async (s, host) => {
	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.startsWith("lightning:")) t = t.replace("lightning:", "");
	if (t.endsWith(`@${PUBLIC_DOMAIN}`)) t = t.split("@")[0];
	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.includes("lightning=")) {
		const url = new URL(t);
		const params = new URLSearchParams(url.search);
		t = params.get("lightning");
	}

	if (t.toLowerCase().startsWith("ln")) {
		try {
			invoice = await get(`/invoice/${t}`);
			if (invoice.user.username === "mint") throw new Error("mint payment");
		} 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}`);
};