Compare commits
No commits in common. "d67cfc0b51a0528f84ac800945fb915b8d87107b" and "f323f434ce6da57f4c27b6c30461b67e7f4b6bc0" have entirely different histories.
d67cfc0b51
...
f323f434ce
@ -600,14 +600,12 @@ async function uploadFile(hosterName, filePath, apiKey, onProgress, signal, thro
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const explicitlyRejected = parseErr && (parseErr.fileRejected === true || parseErr.accountError === true);
|
|
||||||
|
|
||||||
// Byse-specific async handling: server accepts the file but responds with
|
// Byse-specific async handling: server accepts the file but responds with
|
||||||
// filecode="" + misleading status ("Not video file format"). The file shows
|
// filecode="" + misleading status ("Not video file format"). The file shows
|
||||||
// up in the account shortly after — poll the list to claim it. User observed
|
// up in the account shortly after — poll the list to claim it. User observed
|
||||||
// this with 2+ GB MKV uploads that appeared as "OK" on the byse dashboard
|
// this with 2+ GB MKV uploads that appeared as "OK" on the byse dashboard
|
||||||
// even after our uploader gave up.
|
// even after our uploader gave up.
|
||||||
if (hosterName === 'byse.sx' && byseBaseline && !explicitlyRejected) {
|
if (hosterName === 'byse.sx' && byseBaseline) {
|
||||||
const fileName = path.basename(filePath);
|
const fileName = path.basename(filePath);
|
||||||
const polled = await _resolveByseUploadByName(apiKey, fileName, byseBaseline, signal);
|
const polled = await _resolveByseUploadByName(apiKey, fileName, byseBaseline, signal);
|
||||||
if (polled) return polled;
|
if (polled) return polled;
|
||||||
@ -616,7 +614,7 @@ async function uploadFile(hosterName, filePath, apiKey, onProgress, signal, thro
|
|||||||
// Doodstream: the doodapi upload POST returned no filecode (the same backend
|
// Doodstream: the doodapi upload POST returned no filecode (the same backend
|
||||||
// hiccup that empties the web form). Poll the account file list by name — if
|
// hiccup that empties the web form). Poll the account file list by name — if
|
||||||
// the file did register, claim its code instead of failing the upload.
|
// the file did register, claim its code instead of failing the upload.
|
||||||
if (hosterName === 'doodstream.com' && doodBaseline && !explicitlyRejected) {
|
if (hosterName === 'doodstream.com' && doodBaseline) {
|
||||||
const fileName = path.basename(filePath);
|
const fileName = path.basename(filePath);
|
||||||
const polled = await _resolveDoodstreamUploadByName(apiKey, fileName, doodBaseline, signal);
|
const polled = await _resolveDoodstreamUploadByName(apiKey, fileName, doodBaseline, signal);
|
||||||
if (polled) return polled;
|
if (polled) return polled;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-hoster-uploader",
|
"name": "multi-hoster-uploader",
|
||||||
"version": "3.3.60",
|
"version": "3.3.59",
|
||||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -67,8 +67,8 @@ let _historySortClicked = false;
|
|||||||
|
|
||||||
// Session-specific files for the "Files" panel (resets each session)
|
// Session-specific files for the "Files" panel (resets each session)
|
||||||
let sessionFilesData = [];
|
let sessionFilesData = [];
|
||||||
let _recentSeqCounter = 0;
|
|
||||||
const recentSortState = { key: 'date', direction: 'desc' };
|
const recentSortState = { key: 'date', direction: 'desc' };
|
||||||
|
let _recentSortClicked = false;
|
||||||
const selectedRecentIds = new Set();
|
const selectedRecentIds = new Set();
|
||||||
// Maintained incrementally — avoids O(n) filter() scans every 250ms in the status bar.
|
// Maintained incrementally — avoids O(n) filter() scans every 250ms in the status bar.
|
||||||
let _sessionDoneCount = 0;
|
let _sessionDoneCount = 0;
|
||||||
@ -2521,7 +2521,7 @@ function maybeAddSessionFile(job) {
|
|||||||
host: job.hoster || '',
|
host: job.hoster || '',
|
||||||
link,
|
link,
|
||||||
isError: false,
|
isError: false,
|
||||||
order: _recentSeqCounter++
|
order: sessionFilesData.length
|
||||||
});
|
});
|
||||||
_sessionDoneCount++;
|
_sessionDoneCount++;
|
||||||
// Drop oldest entries past the cap to keep render cost bounded.
|
// Drop oldest entries past the cap to keep render cost bounded.
|
||||||
@ -4243,11 +4243,10 @@ function renderRecentUploadsPanel() {
|
|||||||
&& rows.length > _recentLastRenderedLen
|
&& rows.length > _recentLastRenderedLen
|
||||||
&& tbody.querySelectorAll('.recent-file-row').length === _recentLastRenderedLen;
|
&& tbody.querySelectorAll('.recent-file-row').length === _recentLastRenderedLen;
|
||||||
|
|
||||||
const wrap = tbody.closest('.recent-files-table-wrap');
|
|
||||||
const wasAtTop = !wrap || wrap.scrollTop <= 48;
|
|
||||||
|
|
||||||
let wasAppendOnly = false;
|
let wasAppendOnly = false;
|
||||||
if (dateDescAppendOnly) {
|
if (dateDescAppendOnly) {
|
||||||
|
// Fast path: only new rows (date desc puts newest on top) — insert them
|
||||||
|
// at the top without rebuilding the 5000-row tbody below.
|
||||||
const added = rows.length - _recentLastRenderedLen;
|
const added = rows.length - _recentLastRenderedLen;
|
||||||
let html = '';
|
let html = '';
|
||||||
for (let i = 0; i < added; i++) html += _buildRecentRowHtml(rows[i]);
|
for (let i = 0; i < added; i++) html += _buildRecentRowHtml(rows[i]);
|
||||||
@ -4256,7 +4255,6 @@ function renderRecentUploadsPanel() {
|
|||||||
} else {
|
} else {
|
||||||
tbody.innerHTML = rows.map(_buildRecentRowHtml).join('');
|
tbody.innerHTML = rows.map(_buildRecentRowHtml).join('');
|
||||||
}
|
}
|
||||||
if (wrap && sig === 'date|desc' && wasAtTop) wrap.scrollTop = 0;
|
|
||||||
_recentLastRenderedSig = sig;
|
_recentLastRenderedSig = sig;
|
||||||
_recentLastRenderedLen = rows.length;
|
_recentLastRenderedLen = rows.length;
|
||||||
|
|
||||||
@ -4422,13 +4420,14 @@ function setupListeners() {
|
|||||||
const th = e.target.closest('th[data-recent-sort]');
|
const th = e.target.closest('th[data-recent-sort]');
|
||||||
if (!th) return;
|
if (!th) return;
|
||||||
const key = th.dataset.recentSort;
|
const key = th.dataset.recentSort;
|
||||||
if (recentSortState.key === key) {
|
const defaultDir = key === 'date' ? 'desc' : 'asc';
|
||||||
recentSortState.direction = recentSortState.direction === 'desc' ? 'asc' : 'desc';
|
if (!_recentSortClicked || recentSortState.key !== key) {
|
||||||
} else {
|
_recentSortClicked = true;
|
||||||
recentSortState.key = key;
|
recentSortState.key = key;
|
||||||
recentSortState.direction = key === 'date' ? 'desc' : 'asc';
|
recentSortState.direction = defaultDir;
|
||||||
|
} else {
|
||||||
|
recentSortState.direction = recentSortState.direction === 'desc' ? 'asc' : 'desc';
|
||||||
}
|
}
|
||||||
_recentLastRenderedSig = '';
|
|
||||||
renderRecentUploadsPanel();
|
renderRecentUploadsPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
const { test, before, after } = require('node:test');
|
|
||||||
const assert = require('node:assert');
|
|
||||||
const fs = require('fs');
|
|
||||||
const os = require('os');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
let requestRouter = async () => ({ statusCode: 200, headers: {}, body: { text: async () => '{}' } });
|
|
||||||
const undici = require('undici');
|
|
||||||
const _origUndiciRequest = undici.request;
|
|
||||||
undici.request = (...a) => requestRouter(...a);
|
|
||||||
delete require.cache[require.resolve('../lib/hosters')];
|
|
||||||
const hostersMod = require('../lib/hosters');
|
|
||||||
const { uploadFile } = hostersMod;
|
|
||||||
|
|
||||||
let tmpFile;
|
|
||||||
let origFetch;
|
|
||||||
before(() => {
|
|
||||||
tmpFile = path.join(os.tmpdir(), `byse-itest-${process.pid}.mkv`);
|
|
||||||
fs.writeFileSync(tmpFile, Buffer.alloc(2048, 7));
|
|
||||||
origFetch = global.fetch;
|
|
||||||
});
|
|
||||||
after(() => {
|
|
||||||
global.fetch = origFetch;
|
|
||||||
undici.request = _origUndiciRequest;
|
|
||||||
delete require.cache[require.resolve('../lib/hosters')];
|
|
||||||
try { fs.unlinkSync(tmpFile); } catch {}
|
|
||||||
});
|
|
||||||
|
|
||||||
function stubByseUploadServer() {
|
|
||||||
global.fetch = async (url) => {
|
|
||||||
if (/upload\/server/.test(String(url))) {
|
|
||||||
return { status: 200, text: async () => JSON.stringify({ status: 200, result: 'https://node1.byse.sx/upload/01' }) };
|
|
||||||
}
|
|
||||||
return { status: 200, text: async () => '{"status":200}' };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test('byse explicit "Not video file format" throws fast WITHOUT recovery polling', async () => {
|
|
||||||
stubByseUploadServer();
|
|
||||||
let listCalls = 0;
|
|
||||||
requestRouter = async (url, opts) => {
|
|
||||||
const u = String(url);
|
|
||||||
if (/\/api\/file\/list/.test(u)) {
|
|
||||||
listCalls++;
|
|
||||||
return { statusCode: 200, headers: {}, body: { text: async () => '{"status":200,"result":{"files":[]}}' } };
|
|
||||||
}
|
|
||||||
if (opts && opts.body && typeof opts.body[Symbol.asyncIterator] === 'function') {
|
|
||||||
for await (const chunk of opts.body) { if (chunk && chunk.length === -1) break; }
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
body: { text: async () => JSON.stringify({ status: 200, msg: 'OK', files: [{ filecode: '', filename: 'x.mkv', status: 'Not video file format' }] }) }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
await assert.rejects(
|
|
||||||
() => uploadFile('byse.sx', tmpFile, 'VALIDKEY', null, null, null),
|
|
||||||
(err) => err.fileRejected === true && /Not video file format/i.test(err.message)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.strictEqual(listCalls, 1, 'file/list should be hit ONCE (baseline only) — no 15-attempt recovery poll on explicit rejection');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('byse empty filecode WITHOUT explicit rejection still polls recovery', async () => {
|
|
||||||
stubByseUploadServer();
|
|
||||||
let listCalls = 0;
|
|
||||||
requestRouter = async (url, opts) => {
|
|
||||||
const u = String(url);
|
|
||||||
if (/\/api\/file\/list/.test(u)) {
|
|
||||||
listCalls++;
|
|
||||||
const body = listCalls === 1
|
|
||||||
? '{"status":200,"result":{"files":[]}}'
|
|
||||||
: JSON.stringify({ status: 200, result: { files: [{ file_code: 'RECOVERED99', title: path.basename(tmpFile) }] } });
|
|
||||||
return { statusCode: 200, headers: {}, body: { text: async () => body } };
|
|
||||||
}
|
|
||||||
if (opts && opts.body && typeof opts.body[Symbol.asyncIterator] === 'function') {
|
|
||||||
for await (const chunk of opts.body) { if (chunk && chunk.length === -1) break; }
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
headers: { 'content-type': 'application/json' },
|
|
||||||
body: { text: async () => JSON.stringify({ status: 200, msg: 'OK' }) }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await uploadFile('byse.sx', tmpFile, 'VALIDKEY', null, null, null);
|
|
||||||
assert.strictEqual(res.file_code, 'RECOVERED99');
|
|
||||||
assert.ok(listCalls >= 2, 'recovery polling must run when there is no explicit rejection');
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue
Block a user