feat: parse magnet links
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user