feat: add automatic account cooldown recovery
Replace session-long account failure pauses with classified 15, 30, 60, and 120 minute cooldowns for temporary account problems. Keep credential, OTP, banned, and disabled states manual while ignoring unknown, file, network, hoster, and bare WAF errors. Reset escalation after confirmed uploads, deduplicate parallel failures, publish revisioned pause snapshots, and show a stable localized countdown with automatic reactivation.
This commit is contained in:
+30
-3
@@ -12,6 +12,7 @@ const Semaphore = require('./semaphore');
|
||||
const Throttle = require('./throttle');
|
||||
const { probeFileHead } = require('./file-probe');
|
||||
const { normalizeFailureDetails } = require('./upload-diagnostics');
|
||||
const { classifyAccountFailure } = require('./account-rotation');
|
||||
|
||||
const DEFAULT_SETTINGS = {
|
||||
retries: 3,
|
||||
@@ -117,6 +118,10 @@ class UploadManager extends EventEmitter {
|
||||
return n;
|
||||
}
|
||||
|
||||
_emitAccountSucceeded(task) {
|
||||
if (task?.hoster && task?.accountId) this.emit('account-succeeded', { hoster: task.hoster, accountId: task.accountId });
|
||||
}
|
||||
|
||||
// True if the hoster has a usable override stored that differs from the
|
||||
// account currently in the task and isn't itself already marked failed.
|
||||
// Used by the retry loop to decide "retry on same account vs break to
|
||||
@@ -742,6 +747,7 @@ class UploadManager extends EventEmitter {
|
||||
attempt
|
||||
});
|
||||
recordFinalResult('done', { result });
|
||||
this._emitAccountSucceeded(task);
|
||||
return;
|
||||
} catch (err) {
|
||||
this.activeJobs.delete(uploadId);
|
||||
@@ -872,6 +878,7 @@ class UploadManager extends EventEmitter {
|
||||
}
|
||||
emitFinalStatus('done', { result: alt.result, speedKbs: alt.speedKbs, elapsed: alt.elapsed, attempt: 1 });
|
||||
recordFinalResult('done', { result: alt.result });
|
||||
this._emitAccountSucceeded(task);
|
||||
return;
|
||||
}
|
||||
const stoppedInAlternates = this.stopAfterActive && !signal.aborted;
|
||||
@@ -932,13 +939,19 @@ class UploadManager extends EventEmitter {
|
||||
});
|
||||
break;
|
||||
}
|
||||
const pauseMode = classifyAccountFailure(lastError);
|
||||
const alreadyMarked = this._failedAccounts.has(task.hoster + ':' + task.accountId);
|
||||
if (!alreadyMarked) {
|
||||
if (pauseMode !== 'none' && !alreadyMarked) {
|
||||
this._failedAccounts.set(task.hoster + ':' + task.accountId, true);
|
||||
this._rotLog('mark-failed', {
|
||||
jobId, hoster: task.hoster, fileName, accountId: task.accountId,
|
||||
lastError: lastError ? lastError.message : null
|
||||
});
|
||||
this.emit('account-paused', {
|
||||
hoster: task.hoster,
|
||||
accountId: task.accountId,
|
||||
mode: pauseMode
|
||||
});
|
||||
this.emit('account-failed', { hoster: task.hoster, accountId: task.accountId });
|
||||
await this._sleep(800, signal);
|
||||
// Re-check after the await: the user could have cancelled while
|
||||
@@ -946,10 +959,15 @@ class UploadManager extends EventEmitter {
|
||||
// this, rotation proceeds another full attempt-loop's worth of
|
||||
// work before the next signal-check inside _executeUpload notices.
|
||||
if (signal.aborted || this.stopAfterActive) break;
|
||||
} else {
|
||||
} else if (alreadyMarked) {
|
||||
this._rotLog('already-marked', {
|
||||
jobId, hoster: task.hoster, fileName, accountId: task.accountId
|
||||
});
|
||||
} else {
|
||||
this._rotLog('skip-account-pause', {
|
||||
jobId, hoster: task.hoster, fileName, accountId: task.accountId,
|
||||
lastError: lastError ? lastError.message : null
|
||||
});
|
||||
}
|
||||
const override = this._accountOverrides.get(task.hoster);
|
||||
if (!override) {
|
||||
@@ -1045,6 +1063,7 @@ class UploadManager extends EventEmitter {
|
||||
this.sessionBytes += fileSize;
|
||||
emitFinalStatus('done', { result, speedKbs: currentSpeedKbs, elapsed: Math.round((Date.now() - jobStart) / 1000), attempt });
|
||||
recordFinalResult('done', { result });
|
||||
this._emitAccountSucceeded(task);
|
||||
return;
|
||||
} catch (err) {
|
||||
this.activeJobs.delete(uploadId);
|
||||
@@ -1212,12 +1231,20 @@ class UploadManager extends EventEmitter {
|
||||
// override and reroute normal-sized files away from a primary that
|
||||
// still works for them.
|
||||
if (err && err.accountError === true) {
|
||||
this._failedAccounts.set(task.hoster + ':' + account.id, true);
|
||||
const key = task.hoster + ':' + account.id;
|
||||
const pauseMode = classifyAccountFailure(err);
|
||||
if (pauseMode === 'none' || this._failedAccounts.has(key)) continue;
|
||||
this._failedAccounts.set(key, true);
|
||||
this._rotLog('mark-failed', {
|
||||
jobId, hoster: task.hoster, fileName, accountId: account.id,
|
||||
lastError: err && err.message ? err.message : String(err),
|
||||
suspectAlternate: true
|
||||
});
|
||||
this.emit('account-paused', {
|
||||
hoster: task.hoster,
|
||||
accountId: account.id,
|
||||
mode: pauseMode
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user