mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
148 lines
3.3 KiB
JavaScript
148 lines
3.3 KiB
JavaScript
const BASE32_ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
|
|
|
|
function decodeBase32ToHex(value) {
|
|
if (typeof value !== "string" || !value) {
|
|
return "";
|
|
}
|
|
|
|
const normalized = value.toLowerCase();
|
|
let bits = 0;
|
|
let buffer = 0;
|
|
const bytes = [];
|
|
|
|
for (const char of normalized) {
|
|
const index = BASE32_ALPHABET.indexOf(char);
|
|
if (index === -1) {
|
|
return "";
|
|
}
|
|
buffer = (buffer << 5) | index;
|
|
bits += 5;
|
|
if (bits >= 8) {
|
|
bits -= 8;
|
|
const byte = (buffer >> bits) & 0xff;
|
|
bytes.push(byte);
|
|
}
|
|
}
|
|
|
|
if (bytes.length === 0) {
|
|
return "";
|
|
}
|
|
|
|
return bytes.map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
}
|
|
|
|
function extractInfoHashFromXt(value) {
|
|
if (typeof value !== "string") {
|
|
return "";
|
|
}
|
|
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return "";
|
|
}
|
|
|
|
const lower = trimmed.toLowerCase();
|
|
if (!lower.startsWith("urn:btih:")) {
|
|
return "";
|
|
}
|
|
|
|
const payload = trimmed.slice(9);
|
|
return normalizeInfoHash(payload);
|
|
}
|
|
|
|
function normalizeInfoHash(candidate) {
|
|
const trimmed = typeof candidate === "string" ? candidate.trim() : "";
|
|
if (!trimmed) {
|
|
return null;
|
|
}
|
|
if (/^[0-9a-f]{40}$/i.test(trimmed)) {
|
|
return trimmed.toLowerCase();
|
|
}
|
|
if (/^[a-z2-7]{32}$/i.test(trimmed)) {
|
|
const hex = decodeBase32ToHex(trimmed);
|
|
return hex ? hex.toLowerCase() : null;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function infoHashFromMagnet(magnet) {
|
|
if (typeof magnet !== "string") {
|
|
return null;
|
|
}
|
|
const direct = normalizeInfoHash(magnet);
|
|
if (direct) {
|
|
return direct;
|
|
}
|
|
const parsed = parseMagnetLite(magnet);
|
|
const hash = typeof parsed.infoHash === "string" ? parsed.infoHash : "";
|
|
return hash ? hash.toLowerCase() : null;
|
|
}
|
|
|
|
export function trackersFromMagnet(magnet) {
|
|
if (typeof magnet !== "string") {
|
|
return [];
|
|
}
|
|
const parsed = parseMagnetLite(magnet);
|
|
if (!parsed || !Array.isArray(parsed.announce)) {
|
|
return [];
|
|
}
|
|
const deduped = new Set();
|
|
parsed.announce.forEach((url) => {
|
|
if (typeof url !== "string") {
|
|
return;
|
|
}
|
|
const trimmed = url.trim();
|
|
if (!trimmed) {
|
|
return;
|
|
}
|
|
deduped.add(trimmed);
|
|
});
|
|
return Array.from(deduped);
|
|
}
|
|
|
|
function parseMagnetLite(magnet) {
|
|
if (typeof magnet !== "string") {
|
|
return { infoHash: "", announce: [] };
|
|
}
|
|
|
|
const trimmed = magnet.trim();
|
|
if (!trimmed) {
|
|
return { infoHash: "", announce: [] };
|
|
}
|
|
|
|
const lower = trimmed.toLowerCase();
|
|
const queryIndex = lower.indexOf("?");
|
|
const paramsPart = queryIndex >= 0 ? trimmed.slice(queryIndex + 1) : "";
|
|
const params = paramsPart.split("&");
|
|
const announce = [];
|
|
let infoHash = "";
|
|
|
|
for (const param of params) {
|
|
if (!param) {
|
|
continue;
|
|
}
|
|
|
|
const [rawKey, ...rawValueParts] = param.split("=");
|
|
const rawValue = rawValueParts.join("=");
|
|
const key = decodeURIComponent((rawKey || "").replace(/\+/g, " ")).toLowerCase();
|
|
const value = decodeURIComponent((rawValue || "").replace(/\+/g, " "));
|
|
|
|
if (!key) {
|
|
continue;
|
|
}
|
|
|
|
if (key === "xt") {
|
|
const derived = extractInfoHashFromXt(value);
|
|
if (derived && !infoHash) {
|
|
infoHash = derived;
|
|
}
|
|
} else if (key === "tr") {
|
|
if (value) {
|
|
announce.push(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
return { infoHash, announce };
|
|
}
|