Compare commits
3 Commits
13de55253b
...
35314ee3ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
35314ee3ed | ||
|
|
76c56cf13b | ||
|
|
a8d81cbf0d |
36
lib/account-auth.js
Normal file
36
lib/account-auth.js
Normal file
@ -0,0 +1,36 @@
|
||||
// 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 };
|
||||
@ -34,7 +34,10 @@ const HOSTER_CONFIGS = {
|
||||
'doodstream.com': {
|
||||
apiBase: 'https://doodapi.co',
|
||||
serverEndpoints: ['/api/upload/server'],
|
||||
fallbackUploadServers: ['https://tr1128ve.cloudatacdn.com/upload/01'],
|
||||
// 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.)
|
||||
buildUploadUrl: (url, key) => appendRawQuery(url, key),
|
||||
formFields: (key) => ({ api_key: key }),
|
||||
parseResult: parseDoodstreamResult
|
||||
@ -375,7 +378,13 @@ async function getUploadServer(hosterName, hosterConfig, apiKey, signal) {
|
||||
}
|
||||
|
||||
if (lastMessage) {
|
||||
throw new Error(`Kein Upload-Server erhalten: ${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. API-Key pruefen.');
|
||||
}
|
||||
@ -542,9 +551,18 @@ 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);
|
||||
throw new Error(
|
||||
// 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(
|
||||
`Upload zu ${hosterName} lieferte keine file_code-Antwort (Payload: ${snippet})`
|
||||
);
|
||||
err.hosterTransient = true;
|
||||
throw err;
|
||||
}
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
11
main.js
11
main.js
@ -8,6 +8,7 @@ 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');
|
||||
@ -568,15 +569,7 @@ function getNextFallbackAccount(config, hosterName, failedAccountId) {
|
||||
}
|
||||
|
||||
function buildTaskFromAccount(hoster, account, extra) {
|
||||
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;
|
||||
}
|
||||
const task = { ...extra, hoster, accountId: account.id, ...selectUploadAuth(hoster, account) };
|
||||
return task;
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "multi-hoster-uploader",
|
||||
"version": "3.3.29",
|
||||
"version": "3.3.30",
|
||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
44
tests/account-auth.test.js
Normal file
44
tests/account-auth.test.js
Normal file
@ -0,0 +1,44 @@
|
||||
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), {});
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user