fix(doodstream): upload via the doodapi API when an API key exists
Root cause of the recurring "kein Filecode — Server gab leeren Link zurueck": the web-session upload flow gets the filecode back inside an XFileSharing HTML form, and on long/large uploads that form comes back empty (no fn). Verified research: doodstream's server-side file-registration callback times out under large-file load, so the upload "succeeds" (bytes sent, HTTP 200) but no filecode is minted — and because registration failed, the file is NOT in the file list either, so polling can't recover it. The web path also rides a per-page-load sess_id token that ages over the multi-minute upload. The official doodapi.co JSON API has no such failure mode for result retrieval: the upload response returns result[0].filecode directly, and it authenticates with a persistent api_key (no aging sess_id). Git history confirms the API was doodstream's ORIGINAL upload path (initial commit); web login was added later only "as an alternative to API key" — so preferring the key restores the intended primary path rather than fighting a deliberate choice. - lib/account-auth.js (new, pure, unit-tested): selectUploadAuth() prefers the doodstream API key over username/password; all other hosters unchanged. - main.js buildTaskFromAccount delegates to it → a doodstream account with an apiKey now routes through hosters.uploadFile (doodapi API) instead of the web uploader; keyless accounts keep using web login. - hosters.js: drop the stale hardcoded fallback node from the doodstream API config (same dead tr1128ve host removed from the web path) so a failed server lookup throws cleanly instead of uploading into a dead end. - Tests: 8 routing cases (doodstream key-preference, keyless fallback, voe unaffected, authType=api, null-safety). Full suite 173/173. This eliminates the empty-form failure mode for result retrieval when a key is configured. It does NOT change doodstream's backend — whether the large-file timeout recurs (now as a structured JSON error, not a silent empty form) is for the server run to confirm. Requires a doodstream API key on the account. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
13de55253b
commit
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
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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