Files
bitvid/js/magnets.js
T
google-labs-jules[bot]andPR0M3TH3AN 2bebaa6465 fix(webtorrent): ensure magnet webseeds are used in stream
Extracts `ws` (web seed) parameters from the magnet link and merges them with the explicit `urlList` (e.g., from direct URL) when initializing the WebTorrent stream. This fixes a regression where magnet-embedded web seeds were ignored if a direct URL was provided.

- Updates `js/magnets.js` to parse `ws` parameters and export `webSeedsFromMagnet`.
- Updates `js/webtorrent.js` to merge magnet web seeds with `opts.urlList`.
- Adds regression test `tests/webtorrent-webseeds.test.mjs`.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-01-30 22:01:13 +00:00

124 lines
2.7 KiB
JavaScript

import { extractBtihFromMagnet, normalizeInfoHash } from "./magnetShared.js";
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);
}
export function infoHashFromMagnet(magnet) {
if (typeof magnet !== "string") {
return null;
}
const extracted = extractBtihFromMagnet(magnet);
return extracted || 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);
}
export function webSeedsFromMagnet(magnet) {
if (typeof magnet !== "string") {
return [];
}
const parsed = parseMagnetLite(magnet);
if (!parsed || !Array.isArray(parsed.ws)) {
return [];
}
const deduped = new Set();
parsed.ws.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: [], ws: [] };
}
const trimmed = magnet.trim();
if (!trimmed) {
return { infoHash: "", announce: [], ws: [] };
}
const lower = trimmed.toLowerCase();
const queryIndex = lower.indexOf("?");
const paramsPart = queryIndex >= 0 ? trimmed.slice(queryIndex + 1) : "";
const params = paramsPart.split("&");
const announce = [];
const ws = [];
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);
}
} else if (key === "ws" || key === "webseed") {
if (value) {
ws.push(value);
}
}
}
return { infoHash, announce, ws };
}