release: v2.0.6
Redesign the desktop workspace with task sidebars, live filters, clearer settings, and an accessible update dialog. Harden encrypted backup imports, configuration persistence, history retention, queue snapshots, shutdown recovery, and update installation ordering. Publish verified Windows artifacts and refreshed English documentation.
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
function isDiscordWebhook(url) {
|
||||
return /^https?:\/\/(ptb\.|canary\.)?(discord(app)?\.com)\/api\/webhooks\/\d+\/[\w-]+/i.test(String(url || ''));
|
||||
}
|
||||
|
||||
const DISCORD_CONTENT_LIMIT = 1900;
|
||||
|
||||
function clampDiscordContent(text) {
|
||||
const s = String(text || '');
|
||||
if (s.length <= DISCORD_CONTENT_LIMIT) return s;
|
||||
return s.slice(0, DISCORD_CONTENT_LIMIT - 1) + '…';
|
||||
}
|
||||
|
||||
function formatDurationShort(sec) {
|
||||
const s = Math.max(0, Math.round(Number(sec) || 0));
|
||||
const h = Math.floor(s / 3600);
|
||||
const m = Math.floor((s % 3600) / 60);
|
||||
const r = s % 60;
|
||||
if (h > 0) return `${h}h ${m}m`;
|
||||
if (m > 0) return `${m}m ${r}s`;
|
||||
return `${r}s`;
|
||||
}
|
||||
|
||||
function summarizePerHosterFromBatch(summary) {
|
||||
const out = {};
|
||||
if (!summary || !Array.isArray(summary.files)) return out;
|
||||
for (const f of summary.files) {
|
||||
if (!f || !Array.isArray(f.results)) continue;
|
||||
for (const r of f.results) {
|
||||
if (!r || !r.hoster) continue;
|
||||
const b = out[r.hoster] || (out[r.hoster] = { ok: 0, fail: 0 });
|
||||
if (r.status === 'done') b.ok++;
|
||||
else b.fail++;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function resolveDiscordMention(raw) {
|
||||
const s = String(raw || '').trim();
|
||||
if (!s) return null;
|
||||
const keyword = s.replace(/^@/, '').toLowerCase();
|
||||
if (keyword === 'here' || keyword === 'everyone') {
|
||||
return { token: `@${keyword}`, allowed: { parse: ['everyone'] } };
|
||||
}
|
||||
const roleMatch = s.match(/^(?:<@&(\d+)>|role:(\d+))$/i);
|
||||
if (roleMatch) {
|
||||
const id = roleMatch[1] || roleMatch[2];
|
||||
return { token: `<@&${id}>`, allowed: { roles: [id] } };
|
||||
}
|
||||
const userMatch = s.match(/^(?:<@!?(\d+)>|user:(\d+)|(\d{5,30}))$/i);
|
||||
if (userMatch) {
|
||||
const id = userMatch[1] || userMatch[2] || userMatch[3];
|
||||
return { token: `<@${id}>`, allowed: { users: [id] } };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildWebhookRequest(url, summary, meta) {
|
||||
const m = meta || {};
|
||||
const total = Number(summary && summary.total) || 0;
|
||||
const succeeded = Number(summary && summary.succeeded) || 0;
|
||||
const failed = Number(summary && summary.failed) || 0;
|
||||
const perHoster = summarizePerHosterFromBatch(summary);
|
||||
const duration = formatDurationShort(m.durationSec);
|
||||
|
||||
let body;
|
||||
if (isDiscordWebhook(url)) {
|
||||
const headline = m.aborted ? 'Batch abgebrochen' : 'Batch fertig';
|
||||
const hosterEntries = Object.entries(perHoster);
|
||||
const MAX_HOSTER_LINES = 12;
|
||||
let hosterLines = hosterEntries.slice(0, MAX_HOSTER_LINES)
|
||||
.map(([h, b]) => `${h}: ${b.ok}/${b.ok + b.fail}`)
|
||||
.join(' · ');
|
||||
if (hosterEntries.length > MAX_HOSTER_LINES) hosterLines += ` · …+${hosterEntries.length - MAX_HOSTER_LINES}`;
|
||||
const lines = [
|
||||
`**Multi-Hoster-Upload — ${headline}**${m.machineName ? ` (${m.machineName})` : ''}`,
|
||||
`✅ ${succeeded} ok · ❌ ${failed} Fehler · 📦 ${total} gesamt · ⏱ ${duration}`
|
||||
];
|
||||
if (hosterLines) lines.push(hosterLines);
|
||||
const mention = resolveDiscordMention(m.mention);
|
||||
const content = clampDiscordContent((mention ? mention.token + ' ' : '') + lines.join('\n'));
|
||||
const payload = { content };
|
||||
payload.allowed_mentions = mention ? mention.allowed : { parse: [] };
|
||||
body = JSON.stringify(payload);
|
||||
} else {
|
||||
body = JSON.stringify({
|
||||
event: 'batch-done',
|
||||
app: 'multi-hoster-upload',
|
||||
version: m.appVersion || null,
|
||||
machine: m.machineName || null,
|
||||
total,
|
||||
succeeded,
|
||||
failed,
|
||||
durationSec: Math.round(Number(m.durationSec) || 0),
|
||||
aborted: !!m.aborted,
|
||||
perHoster,
|
||||
timestamp: m.timestamp || null
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
url: String(url),
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body
|
||||
};
|
||||
}
|
||||
|
||||
function isAllAborted(summary) {
|
||||
if (!summary || !Array.isArray(summary.files) || summary.files.length === 0) return false;
|
||||
let sawResult = false;
|
||||
for (const f of summary.files) {
|
||||
if (!f || !Array.isArray(f.results)) continue;
|
||||
for (const r of f.results) {
|
||||
if (!r) continue;
|
||||
sawResult = true;
|
||||
if (r.status !== 'aborted') return false;
|
||||
}
|
||||
}
|
||||
return sawResult;
|
||||
}
|
||||
|
||||
module.exports = { isDiscordWebhook, formatDurationShort, summarizePerHosterFromBatch, buildWebhookRequest, resolveDiscordMention, isAllAborted, clampDiscordContent, DISCORD_CONTENT_LIMIT };
|
||||
Reference in New Issue
Block a user