From b41d4369d7fbfd28b264750e7b12bc253fcd0aab Mon Sep 17 00:00:00 2001 From: renaud gaudin Date: Mon, 2 Oct 2023 13:02:23 +0000 Subject: [PATCH] Fixed #938: added hack for mirrobrain magnet URIs Missing parameters are added to the magnet link for it to work properly given it is mainly based on webseeds. Each mirror serving the file is added as a webseed. Params that requires URIEncoding are now encoded. Introduces `makeURLSearchString()` to turn SearchParams into a string but only URIEncoding some specific params. --- static/skin/index.js | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/static/skin/index.js b/static/skin/index.js index b93253aaa..c8c346003 100644 --- a/static/skin/index.js +++ b/static/skin/index.js @@ -197,6 +197,58 @@ } } + function makeURLSearchString(params, keysToURIEncode) { + let output = ''; + for (const [key, value] of params.entries()) { + let finalValue = (keysToURIEncode.indexOf(key) >= 0) ? encodeURIComponent(value) : value; + output += `&${key}=${finalValue}`; + } + return output; + } + + /* hack for library.kiwix.org magnet links (created by MirrorBrain) + See https://github.com/kiwix/container-images/issues/242 */ + async function getFixedMirrorbrainMagnet(magnetLink) { + // parse as query parameters + const params = new URLSearchParams( + magnetLink.replaceAll('&', '&').replace(/^magnet:/, '')); + + const zimUrl = params.get('as'); // as= is fallback URL + // download metalink to build list of mirrored URLs + let mirrorUrls = []; + + const metalink = await fetch(`${zimUrl}.meta4`).then(response => { + return response.ok ? response.text() : ''; + }).catch((_error) => ''); + if (metalink) { + try { + const parser = new DOMParser(); + const doc = parser.parseFromString(metalink, "application/xml"); + doc.querySelectorAll("url").forEach((node) => { + if (node.hasAttribute("priority")) { // ensures its a mirror link + mirrorUrls.push(node.innerHTML); + } + }); + } catch (err) { + // not a big deal, magnet will only contain primary URL + console.debug(`Failed to parse mirror links for ${zimUrl}`); + } + } + + // set webseed (ws=) URL to primary download URL (redirects to mirror) + params.set('ws', zimUrl); + // if we got metalink mirror URLs, append them all + if (mirrorUrls) { + mirrorUrls.forEach((url) => { + params.append('ws', url); + }); + } + + params.set('xs', `${zimUrl}.torrent`); // adding xs= to point to torrent URL + + return 'magnet:?' + makeURLSearchString(params, ['ws', 'as', 'dn', 'xs', 'tr']); + } + async function getMagnetLink(downloadLink) { const magnetUrl = downloadLink + '.magnet'; const controller = new AbortController(); @@ -204,6 +256,9 @@ const magnetLink = await fetch(magnetUrl, { signal: controller.signal }).then(response => { return response.ok ? response.text() : ''; }).catch((_error) => ''); + if (magnetLink) { + return await getFixedMirrorbrainMagnet(magnetLink); + } return magnetLink; }