mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-04 03:42:26 +00:00
Merge pull request #36 from PR0M3TH3AN/codex/fix-magnet-url-encoding-issues
Fix magnet handling and legacy magnet fallback
This commit is contained in:
5 files changed
+117
-45
No files matched your search
@@ -720,12 +720,19 @@ class bitvidApp {
|
||||
}
|
||||
};
|
||||
|
||||
const encodedUrl = trigger.getAttribute("data-play-url") || "";
|
||||
const encodedMagnet = trigger.getAttribute("data-play-magnet") || "";
|
||||
const rawUrlValue =
|
||||
(trigger.dataset && typeof trigger.dataset.playUrl === "string"
|
||||
? trigger.dataset.playUrl
|
||||
: null) ?? trigger.getAttribute("data-play-url") ?? "";
|
||||
const rawMagnetValue =
|
||||
(trigger.dataset && typeof trigger.dataset.playMagnet === "string"
|
||||
? trigger.dataset.playMagnet
|
||||
: null) ?? trigger.getAttribute("data-play-magnet") ?? "";
|
||||
|
||||
const url = decodeDataValue(encodedUrl);
|
||||
const rawMagnet = decodeDataValue(encodedMagnet);
|
||||
const magnet = safeDecodeMagnet(rawMagnet) || rawMagnet;
|
||||
const url = decodeDataValue(rawUrlValue);
|
||||
const decodedMagnetValue = decodeDataValue(rawMagnetValue);
|
||||
const magnet =
|
||||
safeDecodeMagnet(decodedMagnetValue) || decodedMagnetValue;
|
||||
const eventId = trigger.getAttribute("data-video-id");
|
||||
|
||||
if (eventId) {
|
||||
@@ -1365,11 +1372,6 @@ class bitvidApp {
|
||||
const authorSet = new Set();
|
||||
|
||||
// 3) Build each card
|
||||
const encodeDataValue = (value) =>
|
||||
typeof value === "string" && value.length > 0
|
||||
? encodeURIComponent(value)
|
||||
: "";
|
||||
|
||||
videos.forEach((video, index) => {
|
||||
if (!video.id || !video.title) {
|
||||
console.error("Video missing ID/title:", video);
|
||||
@@ -1452,8 +1454,8 @@ class bitvidApp {
|
||||
const magnetCandidate = trimmedMagnet || legacyInfoHash;
|
||||
const magnetSupported = isValidMagnetUri(magnetCandidate);
|
||||
const magnetProvided = magnetCandidate.length > 0;
|
||||
const encodedMagnet = encodeDataValue(magnetCandidate);
|
||||
const encodedUrl = encodeDataValue(trimmedUrl);
|
||||
const playbackUrl = trimmedUrl;
|
||||
const playbackMagnet = magnetCandidate;
|
||||
const showUnsupportedTorrentBadge =
|
||||
!trimmedUrl && magnetProvided && !magnetSupported;
|
||||
const torrentBadge = showUnsupportedTorrentBadge
|
||||
@@ -1474,8 +1476,8 @@ class bitvidApp {
|
||||
<a
|
||||
href="${shareUrl}"
|
||||
data-video-id="${video.id}"
|
||||
data-play-url="${encodedUrl}"
|
||||
data-play-magnet="${encodedMagnet}"
|
||||
data-play-url=""
|
||||
data-play-magnet=""
|
||||
data-torrent-supported="${magnetSupported ? "true" : "false"}"
|
||||
class="block cursor-pointer relative group"
|
||||
>
|
||||
@@ -1492,8 +1494,8 @@ class bitvidApp {
|
||||
<h3
|
||||
class="text-lg font-bold text-white line-clamp-2 hover:text-blue-400 cursor-pointer mb-3"
|
||||
data-video-id="${video.id}"
|
||||
data-play-url="${encodedUrl}"
|
||||
data-play-magnet="${encodedMagnet}"
|
||||
data-play-url=""
|
||||
data-play-magnet=""
|
||||
data-torrent-supported="${magnetSupported ? "true" : "false"}"
|
||||
>
|
||||
${this.escapeHTML(video.title)}
|
||||
@@ -1537,6 +1539,15 @@ class bitvidApp {
|
||||
} else if (magnetProvided && magnetSupported) {
|
||||
cardEl.dataset.torrentSupported = "true";
|
||||
}
|
||||
const interactiveEls = cardEl.querySelectorAll("[data-video-id]");
|
||||
interactiveEls.forEach((el) => {
|
||||
if (!el.dataset) return;
|
||||
el.dataset.playUrl = playbackUrl || "";
|
||||
el.dataset.playMagnet = playbackMagnet || "";
|
||||
if (magnetProvided) {
|
||||
el.dataset.torrentSupported = magnetSupported ? "true" : "false";
|
||||
}
|
||||
});
|
||||
}
|
||||
fragment.appendChild(cardEl);
|
||||
});
|
||||
|
||||
+21
-13
@@ -8,6 +8,7 @@ import { isWhitelistEnabled } from "./config.js";
|
||||
import {
|
||||
deriveTitleFromEvent,
|
||||
parseVideoEventPayload,
|
||||
findLegacyMagnetInEvent,
|
||||
} from "./videoEventUtils.js";
|
||||
|
||||
/**
|
||||
@@ -244,11 +245,6 @@ async function loadUserVideos(pubkey) {
|
||||
const fragment = document.createDocumentFragment();
|
||||
const allKnownEventsArray = Array.from(nostrClient.allEvents.values());
|
||||
|
||||
const encodeDataValue = (value) =>
|
||||
typeof value === "string" && value.length > 0
|
||||
? encodeURIComponent(value)
|
||||
: "";
|
||||
|
||||
videos.forEach((video, index) => {
|
||||
// Decrypt if user owns a private video
|
||||
if (
|
||||
@@ -349,15 +345,16 @@ async function loadUserVideos(pubkey) {
|
||||
const legacyInfoHash =
|
||||
typeof video.infoHash === "string" ? video.infoHash.trim() : "";
|
||||
const magnetCandidate = trimmedMagnet || legacyInfoHash;
|
||||
const encodedUrl = encodeDataValue(video.url);
|
||||
const encodedMagnet = encodeDataValue(magnetCandidate);
|
||||
const playbackUrl =
|
||||
typeof video.url === "string" ? video.url : "";
|
||||
const playbackMagnet = magnetCandidate || "";
|
||||
|
||||
cardEl.innerHTML = `
|
||||
<div
|
||||
class="cursor-pointer relative group"
|
||||
data-video-id="${video.id}"
|
||||
data-play-url="${encodedUrl}"
|
||||
data-play-magnet="${encodedMagnet}"
|
||||
data-play-url=""
|
||||
data-play-magnet=""
|
||||
>
|
||||
<div class="ratio-16-9">
|
||||
<img
|
||||
@@ -372,8 +369,8 @@ async function loadUserVideos(pubkey) {
|
||||
<h3
|
||||
class="text-lg font-bold text-white mb-2 line-clamp-2"
|
||||
data-video-id="${video.id}"
|
||||
data-play-url="${encodedUrl}"
|
||||
data-play-magnet="${encodedMagnet}"
|
||||
data-play-url=""
|
||||
data-play-magnet=""
|
||||
>
|
||||
${safeTitle}
|
||||
</h3>
|
||||
@@ -385,6 +382,13 @@ async function loadUserVideos(pubkey) {
|
||||
</div>
|
||||
`;
|
||||
|
||||
const interactiveEls = cardEl.querySelectorAll("[data-video-id]");
|
||||
interactiveEls.forEach((el) => {
|
||||
if (!el.dataset) return;
|
||||
el.dataset.playUrl = playbackUrl || "";
|
||||
el.dataset.playMagnet = playbackMagnet || "";
|
||||
});
|
||||
|
||||
fragment.appendChild(cardEl);
|
||||
});
|
||||
|
||||
@@ -488,8 +492,12 @@ function localConvertEventToVideo(event) {
|
||||
|
||||
const trimmedUrl = typeof url === "string" ? url.trim() : "";
|
||||
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
|
||||
const legacyMagnet = findLegacyMagnetInEvent(event);
|
||||
const trimmedLegacyMagnet =
|
||||
typeof legacyMagnet === "string" ? legacyMagnet.trim() : "";
|
||||
const trimmedInfoHash = typeof infoHash === "string" ? infoHash.trim() : "";
|
||||
const playbackMagnet = trimmedMagnet || trimmedInfoHash;
|
||||
const playbackMagnet =
|
||||
trimmedMagnet || trimmedLegacyMagnet || trimmedInfoHash;
|
||||
const parsedVersion =
|
||||
typeof version === "number"
|
||||
? version
|
||||
@@ -533,7 +541,7 @@ function localConvertEventToVideo(event) {
|
||||
title: resolvedTitle,
|
||||
url: trimmedUrl,
|
||||
magnet: playbackMagnet,
|
||||
rawMagnet: trimmedMagnet,
|
||||
rawMagnet: trimmedMagnet || trimmedLegacyMagnet,
|
||||
infoHash: trimmedInfoHash,
|
||||
thumbnail: parsedContent.thumbnail ?? "",
|
||||
description: parsedContent.description ?? "",
|
||||
|
||||
+7
-2
@@ -5,6 +5,7 @@ import { accessControl } from "./accessControl.js";
|
||||
import {
|
||||
deriveTitleFromEvent,
|
||||
parseVideoEventPayload,
|
||||
findLegacyMagnetInEvent,
|
||||
} from "./videoEventUtils.js";
|
||||
|
||||
/**
|
||||
@@ -106,8 +107,12 @@ function convertEventToVideo(event) {
|
||||
|
||||
const trimmedUrl = typeof url === "string" ? url.trim() : "";
|
||||
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
|
||||
const legacyMagnet = findLegacyMagnetInEvent(event);
|
||||
const trimmedLegacyMagnet =
|
||||
typeof legacyMagnet === "string" ? legacyMagnet.trim() : "";
|
||||
const trimmedInfoHash = typeof infoHash === "string" ? infoHash.trim() : "";
|
||||
const playbackMagnet = trimmedMagnet || trimmedInfoHash;
|
||||
const playbackMagnet =
|
||||
trimmedMagnet || trimmedLegacyMagnet || trimmedInfoHash;
|
||||
|
||||
const numericVersion = Number.isFinite(version) ? version : 0;
|
||||
const hasPlayableSource = Boolean(trimmedUrl) || Boolean(playbackMagnet);
|
||||
@@ -148,7 +153,7 @@ function convertEventToVideo(event) {
|
||||
title: resolvedTitle,
|
||||
url: trimmedUrl,
|
||||
magnet: playbackMagnet,
|
||||
rawMagnet: trimmedMagnet,
|
||||
rawMagnet: trimmedMagnet || trimmedLegacyMagnet,
|
||||
infoHash: trimmedInfoHash,
|
||||
thumbnail: parsedContent.thumbnail ?? "",
|
||||
description: parsedContent.description ?? "",
|
||||
|
||||
+25
-14
@@ -3,6 +3,7 @@ import { nostrClient } from "./nostr.js";
|
||||
import {
|
||||
deriveTitleFromEvent,
|
||||
parseVideoEventPayload,
|
||||
findLegacyMagnetInEvent,
|
||||
} from "./videoEventUtils.js";
|
||||
|
||||
/**
|
||||
@@ -254,11 +255,6 @@ class SubscriptionsManager {
|
||||
// Only declare localAuthorSet once
|
||||
const localAuthorSet = new Set();
|
||||
|
||||
const encodeDataValue = (value) =>
|
||||
typeof value === "string" && value.length > 0
|
||||
? encodeURIComponent(value)
|
||||
: "";
|
||||
|
||||
videos.forEach((video, index) => {
|
||||
if (!video.id || !video.title) {
|
||||
console.error("Missing ID or title:", video);
|
||||
@@ -342,15 +338,17 @@ class SubscriptionsManager {
|
||||
|
||||
const safeTitle = window.app?.escapeHTML(video.title) || "Untitled";
|
||||
const safeThumb = window.app?.escapeHTML(video.thumbnail) || "";
|
||||
const encodedMagnet = encodeDataValue(video.magnet);
|
||||
const encodedUrl = encodeDataValue(video.url);
|
||||
const playbackUrl =
|
||||
typeof video.url === "string" ? video.url : "";
|
||||
const playbackMagnet =
|
||||
typeof video.magnet === "string" ? video.magnet : "";
|
||||
const cardHtml = `
|
||||
<div class="video-card bg-gray-900 rounded-lg overflow-hidden shadow-lg hover:shadow-2xl transition-all duration-300 ${highlightClass}">
|
||||
<a
|
||||
href="${shareUrl}"
|
||||
data-video-id="${video.id}"
|
||||
data-play-url="${encodedUrl}"
|
||||
data-play-magnet="${encodedMagnet}"
|
||||
data-play-url=""
|
||||
data-play-magnet=""
|
||||
class="block cursor-pointer relative group"
|
||||
>
|
||||
<div class="ratio-16-9">
|
||||
@@ -365,8 +363,8 @@ class SubscriptionsManager {
|
||||
<h3
|
||||
class="text-lg font-bold text-white line-clamp-2 hover:text-blue-400 cursor-pointer mb-3"
|
||||
data-video-id="${video.id}"
|
||||
data-play-url="${encodedUrl}"
|
||||
data-play-magnet="${encodedMagnet}"
|
||||
data-play-url=""
|
||||
data-play-magnet=""
|
||||
>
|
||||
${safeTitle}
|
||||
</h3>
|
||||
@@ -401,6 +399,14 @@ class SubscriptionsManager {
|
||||
const t = document.createElement("template");
|
||||
t.innerHTML = cardHtml.trim();
|
||||
const cardEl = t.content.firstElementChild;
|
||||
if (cardEl) {
|
||||
const interactiveEls = cardEl.querySelectorAll("[data-video-id]");
|
||||
interactiveEls.forEach((el) => {
|
||||
if (!el.dataset) return;
|
||||
el.dataset.playUrl = playbackUrl || "";
|
||||
el.dataset.playMagnet = playbackMagnet || "";
|
||||
});
|
||||
}
|
||||
fragment.appendChild(cardEl);
|
||||
});
|
||||
|
||||
@@ -516,8 +522,13 @@ class SubscriptionsManager {
|
||||
|
||||
const trimmedUrl = typeof url === "string" ? url.trim() : "";
|
||||
const trimmedMagnet = typeof magnet === "string" ? magnet.trim() : "";
|
||||
const trimmedInfoHash = typeof infoHash === "string" ? infoHash.trim() : "";
|
||||
const playbackMagnet = trimmedMagnet || trimmedInfoHash;
|
||||
const legacyMagnet = findLegacyMagnetInEvent(evt);
|
||||
const trimmedLegacyMagnet =
|
||||
typeof legacyMagnet === "string" ? legacyMagnet.trim() : "";
|
||||
const trimmedInfoHash =
|
||||
typeof infoHash === "string" ? infoHash.trim() : "";
|
||||
const playbackMagnet =
|
||||
trimmedMagnet || trimmedLegacyMagnet || trimmedInfoHash;
|
||||
const parsedVersion =
|
||||
typeof version === "number"
|
||||
? version
|
||||
@@ -571,7 +582,7 @@ class SubscriptionsManager {
|
||||
title: resolvedTitle,
|
||||
url: trimmedUrl,
|
||||
magnet: playbackMagnet,
|
||||
rawMagnet: trimmedMagnet,
|
||||
rawMagnet: trimmedMagnet || trimmedLegacyMagnet,
|
||||
infoHash: trimmedInfoHash,
|
||||
thumbnail: parsedContent.thumbnail ?? "",
|
||||
description: parsedContent.description ?? "",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
const MAGNET_URI_PATTERN = /^magnet:\?/i;
|
||||
const HEX_INFO_HASH_PATTERN = /\b[0-9a-f]{40}\b/gi;
|
||||
const LEGACY_MAGNET_PATTERN = /magnet:\?xt=urn:btih:[0-9a-z]{40,}[^\s"'<>]*/i;
|
||||
|
||||
function safeTrim(value) {
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
@@ -251,3 +252,39 @@ export function parseVideoEventPayload(event = {}) {
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
function magnetFromText(value) {
|
||||
if (typeof value !== "string") {
|
||||
return "";
|
||||
}
|
||||
const match = value.match(LEGACY_MAGNET_PATTERN);
|
||||
return match ? match[0] : "";
|
||||
}
|
||||
|
||||
export function findLegacyMagnetInEvent(event = {}) {
|
||||
const fromContent = magnetFromText(event?.content);
|
||||
if (fromContent) {
|
||||
return fromContent.trim();
|
||||
}
|
||||
|
||||
const tags = Array.isArray(event?.tags) ? event.tags : [];
|
||||
for (const tag of tags) {
|
||||
if (!Array.isArray(tag) || tag.length < 2) {
|
||||
continue;
|
||||
}
|
||||
const key = typeof tag[0] === "string" ? tag[0].trim().toLowerCase() : "";
|
||||
const value = typeof tag[1] === "string" ? tag[1].trim() : "";
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
if (key === "magnet" && value) {
|
||||
return value;
|
||||
}
|
||||
const fromTag = magnetFromText(value);
|
||||
if (fromTag) {
|
||||
return fromTag.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
Reference in new issue
Block a user