feat: parse magnet links

This commit is contained in:
2023-03-03 19:01:30 +00:00
parent 4325c49435
commit 8e69342a0c
7 changed files with 189 additions and 7 deletions

View File

@ -3,6 +3,7 @@ import { sha256 as hash } from "@noble/hashes/sha256";
import { bytesToHex } from "@noble/hashes/utils";
import { decode as invoiceDecode } from "light-bolt11-decoder";
import { bech32 } from "bech32";
import base32Decode from "base32-decode";
import { HexKey, TaggedRawEvent, u256, EventKind, encodeTLV, NostrPrefix } from "@snort/nostr";
import { MetadataCache } from "State/Users";
@ -271,3 +272,110 @@ export function decodeInvoice(pr: string) {
console.error(e);
}
}
export interface Magnet {
dn?: string | string[];
tr?: string | string[];
xs?: string | string[];
as?: string | string[];
ws?: string | string[];
kt?: string[];
ix?: number | number[];
xt?: string | string[];
infoHash?: string;
raw?: string;
}
/**
* Parse a magnet URI and return an object of keys/values
*/
export function magnetURIDecode(uri: string): Magnet | undefined {
try {
const result: Record<string, string | number | number[] | string[] | undefined> = {
raw: uri,
};
// Support 'magnet:' and 'stream-magnet:' uris
const data = uri.trim().split("magnet:?")[1];
const params = data && data.length > 0 ? data.split("&") : [];
params.forEach(param => {
const split = param.split("=");
const key = split[0];
const val = decodeURIComponent(split[1]);
if (!result[key]) {
result[key] = [];
}
switch (key) {
case "dn": {
(result[key] as string[]).push(val.replace(/\+/g, " "));
break;
}
case "kt": {
val.split("+").forEach(e => {
(result[key] as string[]).push(e);
});
break;
}
case "ix": {
(result[key] as number[]).push(Number(val));
break;
}
case "so": {
// todo: not implemented yet
break;
}
default: {
(result[key] as string[]).push(val);
break;
}
}
});
// Convenience properties for parity with `parse-torrent-file` module
let m;
if (result.xt) {
const xts = Array.isArray(result.xt) ? result.xt : [result.xt];
xts.forEach(xt => {
if (typeof xt === "string") {
if ((m = xt.match(/^urn:btih:(.{40})/))) {
result.infoHash = [m[1].toLowerCase()];
} else if ((m = xt.match(/^urn:btih:(.{32})/))) {
const decodedStr = base32Decode(m[1], "RFC4648-HEX");
result.infoHash = [bytesToHex(new Uint8Array(decodedStr))];
} else if ((m = xt.match(/^urn:btmh:1220(.{64})/))) {
result.infoHashV2 = [m[1].toLowerCase()];
}
}
});
}
if (result.xs) {
const xss = Array.isArray(result.xs) ? result.xs : [result.xs];
xss.forEach(xs => {
if (typeof xs === "string" && (m = xs.match(/^urn:btpk:(.{64})/))) {
if (!result.publicKey) {
result.publicKey = [];
}
(result.publicKey as string[]).push(m[1].toLowerCase());
}
});
}
for (const [k, v] of Object.entries(result)) {
if (Array.isArray(v)) {
if (v.length === 1) {
result[k] = v[0];
} else if (v.length === 0) {
result[k] = undefined;
}
}
}
return result;
} catch (e) {
console.warn("Failed to parse magnet link", e);
}
}