mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
feat: improve magnet link reliability with webseeds
- Update `UploadModal.js` to pre-calculate R2 keys and public URLs before upload. - Update `torrentHash.js` to embed the calculated video URL as a webseed (`url-list`) in the `.torrent` file. - Update `r2Service.js` to accept forced keys/URLs and append `&xs=` (pointing to the .torrent file) to the magnet link. - Ensures magnet links work for peerless downloads via WebSeed (BEP19) and HTTP Seeding (BEP17).
This commit is contained in:
1 parent
f748f663c8
commit
4e840c6159
3 files changed
+68
-9
No files matched your search
@@ -551,6 +551,10 @@ class R2Service {
|
||||
settingsInput = null,
|
||||
publishVideoNote,
|
||||
onReset,
|
||||
forcedVideoKey = "",
|
||||
forcedVideoUrl = "",
|
||||
forcedTorrentKey = "",
|
||||
forcedTorrentUrl = "",
|
||||
} = {}) {
|
||||
if (settingsInput) {
|
||||
const saved = await this.handleCloudflareSettingsSubmit(settingsInput, {
|
||||
@@ -638,9 +642,12 @@ class R2Service {
|
||||
let statusMessage = `Uploading to ${bucketEntry.bucket}…`;
|
||||
this.setCloudflareUploadStatus(statusMessage, "info");
|
||||
|
||||
const key = buildR2Key(npub, file);
|
||||
const publicUrl = buildPublicUrl(bucketEntry.publicBaseUrl, key);
|
||||
// Use forced keys if provided, otherwise generate them
|
||||
const key = forcedVideoKey || buildR2Key(npub, file);
|
||||
const publicUrl = forcedVideoUrl || buildPublicUrl(bucketEntry.publicBaseUrl, key);
|
||||
|
||||
const buildTorrentKey = () => {
|
||||
if (forcedTorrentKey) return forcedTorrentKey;
|
||||
const baseKey = key.replace(/\.[^/.]+$/, "");
|
||||
if (baseKey && baseKey !== key) {
|
||||
return `${baseKey}.torrent`;
|
||||
@@ -687,7 +694,7 @@ class R2Service {
|
||||
});
|
||||
|
||||
let publishOutcome = true;
|
||||
let torrentUrl = "";
|
||||
let torrentUrl = forcedTorrentUrl || "";
|
||||
|
||||
if (torrentFile) {
|
||||
this.setCloudflareUploadStatus("Uploading torrent metadata...", "info");
|
||||
@@ -700,7 +707,9 @@ class R2Service {
|
||||
file: torrentFile,
|
||||
contentType: "application/x-bittorrent",
|
||||
});
|
||||
torrentUrl = buildPublicUrl(bucketEntry.publicBaseUrl, torrentKey);
|
||||
if (!torrentUrl) {
|
||||
torrentUrl = buildPublicUrl(bucketEntry.publicBaseUrl, torrentKey);
|
||||
}
|
||||
} catch (err) {
|
||||
userLogger.warn("Torrent metadata upload failed, continuing...", err);
|
||||
}
|
||||
@@ -722,7 +731,14 @@ class R2Service {
|
||||
if (hasValidInfoHash) {
|
||||
const encodedDn = encodeURIComponent(file.name);
|
||||
const encodedWs = encodeURIComponent(publicUrl);
|
||||
generatedMagnet = `magnet:?xt=urn:btih:${normalizedInfoHash}&dn=${encodedDn}&ws=${encodedWs}`;
|
||||
let magnet = `magnet:?xt=urn:btih:${normalizedInfoHash}&dn=${encodedDn}&ws=${encodedWs}`;
|
||||
|
||||
if (torrentUrl) {
|
||||
const encodedXs = encodeURIComponent(torrentUrl);
|
||||
magnet += `&xs=${encodedXs}`;
|
||||
}
|
||||
|
||||
generatedMagnet = magnet;
|
||||
generatedWs = publicUrl;
|
||||
} else {
|
||||
if (infoHash) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from "../../services/videoNotePayload.js";
|
||||
import { createTorrentMetadata } from "../../utils/torrentHash.js";
|
||||
import { sanitizeBucketName } from "../../storage/r2-mgmt.js";
|
||||
import { buildR2Key, buildPublicUrl } from "../../r2.js";
|
||||
|
||||
const INFO_HASH_PATTERN = /^[a-f0-9]{40}$/;
|
||||
|
||||
@@ -660,6 +661,39 @@ export class UploadModal {
|
||||
|
||||
const npub = this.safeEncodeNpub(pubkey);
|
||||
|
||||
// Pre-calculate Keys & URLs to support WebSeeding
|
||||
// We need the Public URL *before* we generate the torrent so we can embed it.
|
||||
let videoKey = "";
|
||||
let videoPublicUrl = "";
|
||||
let torrentKey = "";
|
||||
let torrentPublicUrl = "";
|
||||
|
||||
try {
|
||||
// Temporarily grab current bucket settings to calculate URL
|
||||
const bucketName = this.inputs.r2BucketName?.value || sanitizeBucketName(npub);
|
||||
// Note: The actual upload might re-verify this, but we need a best-guess for the torrent.
|
||||
// We rely on r2Service using the *same* logic or accepting our forced keys.
|
||||
// Since we can't easily get the 'final' bucket config here without doing the 'ensureBucket' dance first,
|
||||
// we'll do a lightweight check of the settings form.
|
||||
const currentSettings = this.collectSettingsForm();
|
||||
if (!currentSettings.baseDomain) {
|
||||
throw new Error("Missing Public URL (Base Domain) in settings.");
|
||||
}
|
||||
|
||||
videoKey = buildR2Key(npub, file);
|
||||
// 'buildPublicUrl' logic: ${baseDomain}/${key}
|
||||
videoPublicUrl = buildPublicUrl(currentSettings.baseDomain, videoKey);
|
||||
|
||||
// For the torrent file, we swap extension
|
||||
const baseKey = videoKey.replace(/\.[^/.]+$/, "");
|
||||
torrentKey = (baseKey && baseKey !== videoKey) ? `${baseKey}.torrent` : `${videoKey}.torrent`;
|
||||
torrentPublicUrl = buildPublicUrl(currentSettings.baseDomain, torrentKey);
|
||||
|
||||
} catch (prepErr) {
|
||||
userLogger.warn("Failed to pre-calculate R2 keys:", prepErr);
|
||||
// We continue, but WebSeed generation will fail or be skipped.
|
||||
}
|
||||
|
||||
// 1. Calculate Torrent Info Hash (Client-side)
|
||||
this.isUploading = true;
|
||||
this.submitButton.disabled = true;
|
||||
@@ -669,7 +703,10 @@ export class UploadModal {
|
||||
let infoHash = "";
|
||||
let torrentFile = null;
|
||||
try {
|
||||
const torrentMetadata = await createTorrentMetadata(file);
|
||||
// Pass the pre-calculated video URL as a WebSeed
|
||||
const urlList = videoPublicUrl ? [videoPublicUrl] : [];
|
||||
const torrentMetadata = await createTorrentMetadata(file, urlList);
|
||||
|
||||
infoHash = torrentMetadata?.infoHash || "";
|
||||
if (torrentMetadata?.torrentFile) {
|
||||
const baseName = file.name.replace(/\.[^/.]+$/, "") || file.name;
|
||||
@@ -715,6 +752,11 @@ export class UploadModal {
|
||||
settingsInput: this.collectSettingsForm(),
|
||||
publishVideoNote: this.publishVideoNote,
|
||||
onReset: () => this.resetForm(),
|
||||
// Pass forced keys/URLs to ensure what's in the torrent matches where we upload
|
||||
forcedVideoKey: videoKey,
|
||||
forcedVideoUrl: videoPublicUrl,
|
||||
forcedTorrentKey: torrentKey,
|
||||
forcedTorrentUrl: torrentPublicUrl,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ const TORRENT_SEED_OPTIONS = {
|
||||
destroyStoreOnDestroy: true,
|
||||
};
|
||||
|
||||
function seedTorrentForFile(file) {
|
||||
function seedTorrentForFile(file, urlList = []) {
|
||||
if (!file) {
|
||||
return Promise.reject(new Error("No file provided for hashing."));
|
||||
}
|
||||
@@ -25,6 +25,7 @@ function seedTorrentForFile(file) {
|
||||
{
|
||||
...TORRENT_SEED_OPTIONS,
|
||||
name: file.name,
|
||||
urlList: urlList,
|
||||
},
|
||||
(torrent) => {
|
||||
const infoHash = torrent.infoHash;
|
||||
@@ -66,6 +67,6 @@ export async function calculateTorrentInfoHash(file) {
|
||||
return infoHash;
|
||||
}
|
||||
|
||||
export async function createTorrentMetadata(file) {
|
||||
return seedTorrentForFile(file);
|
||||
export async function createTorrentMetadata(file, urlList = []) {
|
||||
return seedTorrentForFile(file, urlList);
|
||||
}
|
||||
Reference in new issue
Block a user