Compare commits

..

No commits in common. "35314ee3ed75ba1e7e75e04f568b15403070d247" and "13de55253b9a2be5c63b5b5594e0df84db7d4750" have entirely different histories.

5 changed files with 13 additions and 104 deletions

View File

@ -1,36 +0,0 @@
// Decides which credential an upload task should use for a given hoster.
// Extracted from main.js buildTaskFromAccount so the routing can be unit-tested
// without Electron.
//
// DOODSTREAM SPECIAL CASE: prefer the official doodapi.co API key whenever the
// account has one. The web-login path (username/password) drives doodstream's
// browser upload flow, which hands the filecode back inside an XFileSharing
// HTML form. On long/large uploads that form comes back empty (no fn) because a
// per-page-load sess_id token ages out over the multi-minute upload and/or the
// server-side file-registration callback times out — the upload then "succeeds"
// (bytes sent, HTTP 200) but yields no link. The JSON API returns the filecode
// directly in result[0].filecode and authenticates with a persistent api_key,
// so it has no empty-form failure mode for result retrieval. The API path was
// doodstream's ORIGINAL upload path (present since the initial commit); web
// login was added later only as an alternative for keyless accounts — so
// preferring the key here restores the intended primary path, it doesn't fight
// a deliberate choice. Keyless accounts keep using web login unchanged.
function selectUploadAuth(hoster, account) {
if (!account || typeof account !== 'object') return {};
if (hoster === 'doodstream.com' && account.apiKey) {
return { apiKey: account.apiKey };
}
if (account.authType === 'api' && account.apiKey) {
return { apiKey: account.apiKey };
}
if (account.username && account.password) {
return { username: account.username, password: account.password };
}
if (account.apiKey) {
return { apiKey: account.apiKey };
}
return {};
}
module.exports = { selectUploadAuth };

View File

@ -34,10 +34,7 @@ const HOSTER_CONFIGS = {
'doodstream.com': {
apiBase: 'https://doodapi.co',
serverEndpoints: ['/api/upload/server'],
// No hardcoded fallback node: that stale CDN host (tr1128ve.cloudatacdn.com)
// accepts the bytes but returns an empty result form with no filecode, so a
// failed server lookup must throw cleanly rather than upload ~1 GB into a
// dead end. (Same reasoning as the web-session path's fail-fast.)
fallbackUploadServers: ['https://tr1128ve.cloudatacdn.com/upload/01'],
buildUploadUrl: (url, key) => appendRawQuery(url, key),
formFields: (key) => ({ api_key: key }),
parseResult: parseDoodstreamResult
@ -378,13 +375,7 @@ async function getUploadServer(hosterName, hosterConfig, apiKey, signal) {
}
if (lastMessage) {
const e = new Error(`Kein Upload-Server erhalten: ${lastMessage}`);
// "no servers available" / busy / try-again is a transient hoster-side
// condition, not an account fault — tag it so the account isn't blacklisted.
// Genuine auth failures (invalid key / unauthorized / forbidden) make
// shouldRetryServerLookup return false and stay classified as account errors.
if (shouldRetryServerLookup(lastMessage)) e.hosterTransient = true;
throw e;
throw new Error(`Kein Upload-Server erhalten: ${lastMessage}`);
}
throw new Error('Kein Upload-Server erhalten. API-Key pruefen.');
}
@ -551,18 +542,9 @@ async function uploadFile(hosterName, filePath, apiKey, onProgress, signal, thro
const isOkishNoPayload = /^(ok|success|done|accepted)$/i.test(msg);
if (isOkishNoPayload || !msg) {
const snippet = JSON.stringify(payload).slice(0, 400);
// 2xx with no filecode: the hoster accepted the upload (bytes sent, status
// OK) but returned no usable link. For doodstream this is the API-path
// analog of the web empty-form — the backend file-registration timing out
// under large-file load. It's a hoster-side flake, NOT an account problem,
// so tag it hosterTransient: the upload-manager then fails this file WITHOUT
// blacklisting the account (same protection the web path got in 3.3.29) and
// the account stays usable for the next retry/batch.
const err = new Error(
throw new Error(
`Upload zu ${hosterName} lieferte keine file_code-Antwort (Payload: ${snippet})`
);
err.hosterTransient = true;
throw err;
}
throw new Error(msg);
}

11
main.js
View File

@ -8,7 +8,6 @@ const { HOSTER_CONFIGS } = require('./lib/hosters');
const VidmolyUploader = require('./lib/vidmoly-upload');
const VoeUploader = require('./lib/voe-upload');
const DoodstreamUploader = require('./lib/doodstream-upload');
const { selectUploadAuth } = require('./lib/account-auth');
const ClouddropUploader = require('./lib/clouddrop-upload');
const { checkForUpdate, installUpdate, abortUpdate } = require('./lib/updater');
const backupCrypto = require('./lib/backup-crypto');
@ -569,7 +568,15 @@ function getNextFallbackAccount(config, hosterName, failedAccountId) {
}
function buildTaskFromAccount(hoster, account, extra) {
const task = { ...extra, hoster, accountId: account.id, ...selectUploadAuth(hoster, account) };
const task = { ...extra, hoster, accountId: account.id };
if (account.authType === 'api' && account.apiKey) {
task.apiKey = account.apiKey;
} else if (account.username && account.password) {
task.username = account.username;
task.password = account.password;
} else if (account.apiKey) {
task.apiKey = account.apiKey;
}
return task;
}

View File

@ -1,6 +1,6 @@
{
"name": "multi-hoster-uploader",
"version": "3.3.30",
"version": "3.3.29",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js",
"scripts": {

View File

@ -1,44 +0,0 @@
const { test } = require('node:test');
const assert = require('node:assert');
const { selectUploadAuth } = require('../lib/account-auth');
test('doodstream prefers the API key even when username/password are also set', () => {
const auth = selectUploadAuth('doodstream.com', {
apiKey: 'KEY123', username: 'u', password: 'p'
});
assert.deepEqual(auth, { apiKey: 'KEY123' }); // API path — no username leaks through
});
test('doodstream with only username/password uses web login (keyless fallback)', () => {
const auth = selectUploadAuth('doodstream.com', { username: 'u', password: 'p' });
assert.deepEqual(auth, { username: 'u', password: 'p' });
});
test('doodstream with empty apiKey + creds falls back to web login (no false API route)', () => {
const auth = selectUploadAuth('doodstream.com', { apiKey: '', username: 'u', password: 'p' });
assert.deepEqual(auth, { username: 'u', password: 'p' });
});
test('doodstream with nothing usable returns empty', () => {
assert.deepEqual(selectUploadAuth('doodstream.com', { apiKey: '', username: '', password: '' }), {});
});
test('voe.sx is unaffected by the doodstream special-case: username/password wins', () => {
// voe also supports both, but the empty-form bug is doodstream-specific; do
// not change voe routing.
const auth = selectUploadAuth('voe.sx', { apiKey: 'VKEY', username: 'u', password: 'p' });
assert.deepEqual(auth, { username: 'u', password: 'p' });
});
test('authType=api forces the API key for any hoster', () => {
assert.deepEqual(selectUploadAuth('voe.sx', { authType: 'api', apiKey: 'K', username: 'u', password: 'p' }), { apiKey: 'K' });
});
test('api-key-only account (no creds) uses the key', () => {
assert.deepEqual(selectUploadAuth('byse.sx', { apiKey: 'BKEY' }), { apiKey: 'BKEY' });
});
test('null / non-object account does not throw', () => {
assert.deepEqual(selectUploadAuth('doodstream.com', null), {});
assert.deepEqual(selectUploadAuth('doodstream.com', undefined), {});
});