From d1b36b0aaae04094b1b40f441dd5e8b24d3ddde8 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Wed, 12 Aug 2026 02:43:36 +0200 Subject: [PATCH] fix: canonicalize Doodstream result links Accept current Doodstream result domains while keeping returned download and embed URLs on doodstream.com. Add safe confirmation diagnostics and regression coverage. --- lib/upload-confirmation.js | 46 +++++++++++++++++++++++++++++-- package-lock.json | 4 +-- package.json | 2 +- tests/upload-confirmation.test.js | 32 ++++++++++++++++++++- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/lib/upload-confirmation.js b/lib/upload-confirmation.js index 1743b63..754a304 100644 --- a/lib/upload-confirmation.js +++ b/lib/upload-confirmation.js @@ -7,19 +7,52 @@ const SUPPORTED_HOSTERS = new Set([ ]); const FILE_CODE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{2,127}$/; +const HOSTER_RESULT_DOMAINS = { + 'doodstream.com': ['doodstream.com', 'dood.to', 'dood.la', 'dood.so', 'dsvplay.com'] +}; function isExpectedHostUrl(value, expectedHost) { if (typeof value !== 'string' || value.trim() === '') return false; try { const url = new URL(value); const hostname = url.hostname.toLowerCase(); + const acceptedDomains = HOSTER_RESULT_DOMAINS[expectedHost] || [expectedHost]; return (url.protocol === 'http:' || url.protocol === 'https:') - && (hostname === expectedHost || hostname.endsWith(`.${expectedHost}`)); + && acceptedDomains.some(domain => hostname === domain || hostname.endsWith(`.${domain}`)); } catch { return false; } } +function getUrlHost(value) { + try { + return new URL(value).hostname.toLowerCase(); + } catch { + return 'invalid'; + } +} + +function normalizeDoodstreamUrl(value) { + if (typeof value !== 'string' || value.trim() === '') return value; + try { + const url = new URL(value); + const hostname = url.hostname.toLowerCase(); + if (hostname !== 'doodstream.com' && HOSTER_RESULT_DOMAINS['doodstream.com'].includes(hostname)) { + url.hostname = 'doodstream.com'; + return url.toString(); + } + } catch {} + return value; +} + +function normalizeConfirmedResult(result, hoster) { + if (hoster !== 'doodstream.com') return result; + const downloadUrl = normalizeDoodstreamUrl(result.download_url); + const embedUrl = normalizeDoodstreamUrl(result.embed_url); + if (downloadUrl === result.download_url && embedUrl === result.embed_url) return result; + return { ...result, download_url: downloadUrl, embed_url: embedUrl }; +} + function assertUploadConfirmation(result, hoster) { const expectedHost = typeof hoster === 'string' ? hoster.trim().toLowerCase() : ''; const fileCode = typeof result?.file_code === 'string' ? result.file_code.trim() : ''; @@ -31,9 +64,16 @@ function assertUploadConfirmation(result, hoster) { if (SUPPORTED_HOSTERS.has(expectedHost) && FILE_CODE_PATTERN.test(fileCode) && urls.every(value => isExpectedHostUrl(value, expectedHost))) { - return result; + return normalizeConfirmedResult(result, expectedHost); } - throw new Error(`Upload zu ${hoster || 'unbekanntem Hoster'} wurde nicht bestätigt`); + const error = new Error(`Upload zu ${hoster || 'unbekanntem Hoster'} wurde nicht bestätigt`); + error.diagnostic = { + payloadSnippet: JSON.stringify({ + fileCodeLength: fileCode.length, + urlHosts: urls.map(getUrlHost) + }) + }; + throw error; } module.exports = { assertUploadConfirmation }; diff --git a/package-lock.json b/package-lock.json index 47b0383..60600c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-hoster-uploader", - "version": "2.1.13", + "version": "2.1.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-hoster-uploader", - "version": "2.1.13", + "version": "2.1.14", "dependencies": { "chokidar": "^3.6.0", "undici": "^7.29.0", diff --git a/package.json b/package.json index 37bc1ab..5ba098a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multi-hoster-uploader", - "version": "2.1.13", + "version": "2.1.14", "description": "Upload files to doodstream, voe, vidmoly, byse simultaneously", "main": "main.js", "scripts": { diff --git a/tests/upload-confirmation.test.js b/tests/upload-confirmation.test.js index 688d687..3baced2 100644 --- a/tests/upload-confirmation.test.js +++ b/tests/upload-confirmation.test.js @@ -22,6 +22,32 @@ test('accepts upload URLs for every supported hoster and its subdomains', () => } }); +test('accepts Doodstream result links on its current public domains', () => { + const result = { + file_code: 'DOODCODE1234', + download_url: 'https://dood.to/d/DOODCODE1234', + embed_url: 'https://dood.la/e/DOODCODE1234' + }; + assert.deepEqual(assertUploadConfirmation(result, 'doodstream.com'), { + ...result, + download_url: 'https://doodstream.com/d/DOODCODE1234', + embed_url: 'https://doodstream.com/e/DOODCODE1234' + }); +}); + +test('accepts the Doodstream result domain returned by the current upload service', () => { + const result = { + file_code: 'DOODCODE1234', + download_url: 'https://dsvplay.com/d/DOODCODE1234', + embed_url: 'https://dsvplay.com/e/DOODCODE1234' + }; + assert.deepEqual(assertUploadConfirmation(result, 'doodstream.com'), { + ...result, + download_url: 'https://doodstream.com/d/DOODCODE1234', + embed_url: 'https://doodstream.com/e/DOODCODE1234' + }); +}); + test('rejects an upload URL from a different domain', () => { assert.throws( () => assertUploadConfirmation({ file_code: 'abc123', download_url: 'https://attacker.invalid/file/abc123' }, 'voe.sx'), @@ -39,7 +65,11 @@ test('rejects a syntactically invalid file code despite a valid hoster URL', () test('rejects a valid hoster URL without a file code', () => { assert.throws( () => assertUploadConfirmation({ download_url: 'https://byse.sx/d/abc123' }, 'byse.sx'), - /Upload zu byse\.sx wurde nicht bestätigt/ + (err) => { + assert.match(err.message, /Upload zu byse\.sx wurde nicht bestätigt/); + assert.equal(err.diagnostic.payloadSnippet, '{"fileCodeLength":0,"urlHosts":["byse.sx"]}'); + return true; + } ); });