fix(telegram): clarify task queue controls

Show paused and cancelled states directly in the Telegram batch progress message. Remove fallback slash commands from the control card, clear inline buttons after successful completion or cancellation, and invalidate task ids so old buttons no longer report successful pause/resume after cancellation.
This commit is contained in:
hicocos committed 2026-07-01 20:07:15 +08:00
1 parent 07d3b677b0
commit fd2faa97e7
4 files changed
+174 -40

No files matched your search

+84 -21
View File
@@ -1958,18 +1958,21 @@ var PROVIDER_DISPLAY_MAP = {
function getProviderDisplayName(providerName) {
return PROVIDER_DISPLAY_MAP[providerName] || `\u{1F4E6} ${providerName}`;
}
function buildTaskControlLines(taskId) {
function buildTaskControlLines(taskId, queuePaused = false, pauseReason) {
if (!taskId) return [`\u{1F4A1} \u53D1\u9001 /tasks \u67E5\u770B\u5B9E\u65F6\u4EFB\u52A1\u72B6\u6001`];
if (queuePaused) {
return [
`\u23F8\uFE0F **\u5F53\u524D\u72B6\u6001\uFF1A\u5DF2\u6682\u505C**`,
pauseReason ? `\u{1F4CC} \u539F\u56E0\uFF1A${pauseReason}` : `\u{1F4CC} \u7B49\u5F85\u4E2D\u7684\u4E0B\u8F7D\u4EFB\u52A1\u4E0D\u4F1A\u7EE7\u7EED\u5F00\u59CB`,
`\u25B6\uFE0F \u70B9\u51FB\u4E0B\u65B9\u201C\u7EE7\u7EED\u201D\u53EF\u6062\u590D\u540E\u53F0\u4E0B\u8F7D\u961F\u5217`,
`\u{1F6D1} \u70B9\u51FB\u201C\u53D6\u6D88\u201D\u53EF\u505C\u6B62\u5F53\u524D\u540E\u53F0\u4EFB\u52A1\u5E76\u6E05\u7A7A\u7B49\u5F85\u9879`
];
}
return [
`\u{1F4A1} \u4E0B\u8F7D\u961F\u5217\u63A7\u5236\uFF1A\u8BF7\u4F18\u5148\u70B9\u4E0B\u65B9\u6309\u94AE`,
`\u23F8 \u6682\u505C\uFF1A\u6682\u65F6\u505C\u6B62\u7EE7\u7EED\u4E0B\u8F7D\uFF0C\u5DF2\u5728\u5904\u7406\u7684\u4EFB\u52A1\u4F1A\u5C3D\u5FEB\u505C\u4F4F`,
`\u25B6\uFE0F \u7EE7\u7EED\uFF1A\u4ECE\u6682\u505C\u5904\u6062\u590D\u540E\u53F0\u4E0B\u8F7D\u961F\u5217`,
`\u{1F6D1} \u53D6\u6D88\uFF1A\u505C\u6B62\u5F53\u524D\u540E\u53F0\u4EFB\u52A1\u5E76\u6E05\u7A7A\u7B49\u5F85\u9879`,
``,
`\u5907\u7528\u624B\u52A8\u547D\u4EE4\uFF1A`,
`/task_pause ${taskId}`,
`/task_resume ${taskId}`,
`/task_cancel ${taskId}`
`\u{1F6D1} \u53D6\u6D88\uFF1A\u505C\u6B62\u5F53\u524D\u540E\u53F0\u4EFB\u52A1\u5E76\u6E05\u7A7A\u7B49\u5F85\u9879`
];
}
function buildTaskControlButtons(taskId) {
@@ -2310,17 +2313,17 @@ function buildDeleteSuccess(fileName, fileId) {
`\u{1F5D1}\uFE0F ID: ${fileId}`
].join("\n");
}
function buildSilentModeNotice(fileCount, taskId) {
function buildSilentModeNotice(fileCount, taskId, queuePaused = false, pauseReason) {
return [
`\u{1F910} **\u5DF2\u5207\u6362\u5230\u9759\u9ED8\u6A21\u5F0F**`,
queuePaused ? `\u23F8\uFE0F **\u540E\u53F0\u4E0B\u8F7D\u5DF2\u6682\u505C**` : `\u{1F910} **\u5DF2\u5207\u6362\u5230\u9759\u9ED8\u6A21\u5F0F**`,
...taskId ? [`\u{1F194} \u4EFB\u52A1\uFF1A\`${taskId}\``] : [],
``,
`Bot \u5C06\u5728\u540E\u53F0\u7EE7\u7EED\u5904\u7406\u6240\u6709\u6587\u4EF6\uFF0C\u8BF7\u8010\u5FC3\u7B49\u5F85\u3002`,
queuePaused ? `\u7B49\u5F85\u4EFB\u52A1\u5DF2\u6682\u505C\uFF0C\u4E0D\u4F1A\u7EE7\u7EED\u5F00\u59CB\u65B0\u7684\u4E0B\u8F7D\u3002` : `Bot \u5C06\u5728\u540E\u53F0\u7EE7\u7EED\u5904\u7406\u6240\u6709\u6587\u4EF6\uFF0C\u8BF7\u8010\u5FC3\u7B49\u5F85\u3002`,
``,
...buildTaskControlLines(taskId)
...buildTaskControlLines(taskId, queuePaused, pauseReason)
].join("\n");
}
function buildSilentProgress(sessionTotal, batches, singleFiles = [], sessionCompleted = 0, sessionFailed = 0, taskId) {
function buildSilentProgress(sessionTotal, batches, singleFiles = [], sessionCompleted = 0, sessionFailed = 0, taskId, queuePaused = false, pauseReason) {
const totalBatchFiles = batches.reduce((sum, batch) => sum + batch.totalFiles, 0);
const completedBatchFiles = batches.reduce((sum, batch) => sum + batch.completed, 0);
const successfulBatchFiles = batches.reduce((sum, batch) => sum + batch.successful, 0);
@@ -2332,12 +2335,16 @@ function buildSilentProgress(sessionTotal, batches, singleFiles = [], sessionCom
const failedFiles = Math.max(sessionFailed, failedBatchFiles + failedSingleFiles);
const successfulFiles = Math.max(0, completedFiles - failedFiles);
const remainingFiles = Math.max(0, totalFiles - completedFiles);
const isComplete = totalFiles > 0 && remainingFiles === 0;
const activeBatch = batches.find((batch) => batch.completed < batch.totalFiles);
const activeSingle = singleFiles.find((file) => !["success", "failed"].includes(file.phase));
const currentFile = activeBatch?.currentFileName || activeSingle?.fileName;
const progress = generateProgressBar(completedFiles, Math.max(totalFiles, 1));
if (isComplete) {
return buildSilentAllTasksComplete(totalFiles, failedFiles, taskId, singleFiles, batches);
}
return [
`\u{1F910} **\u540E\u53F0\u6279\u91CF\u5904\u7406\u4E2D**`,
queuePaused ? `\u23F8\uFE0F **\u540E\u53F0\u4E0B\u8F7D\u5DF2\u6682\u505C**` : `\u{1F910} **\u540E\u53F0\u6279\u91CF\u5904\u7406\u4E2D**`,
`${progress} (${completedFiles}/${totalFiles})`,
``,
`\u2705 \u6210\u529F: ${successfulFiles}\u3000\u274C \u5931\u8D25: ${failedFiles}\u3000\u23F3 \u5269\u4F59: ${remainingFiles}`,
@@ -2345,7 +2352,7 @@ function buildSilentProgress(sessionTotal, batches, singleFiles = [], sessionCom
...activeBatch ? [`\u{1F4C1} \u6279\u6B21: ${activeBatch.folderName}`] : [],
...activeBatch?.queuePending ? [`\u{1F552} \u961F\u5217\u7B49\u5F85: ${activeBatch.queuePending}`] : [],
``,
...buildTaskControlLines(taskId),
...buildTaskControlLines(taskId, queuePaused, pauseReason),
...taskId && failedFiles > 0 && remainingFiles === 0 ? [`\u{1F504} \u68C0\u6D4B\u5230\u5931\u8D25\u4EFB\u52A1\uFF0C\u53EF\u53D1\u9001 /tg_retry ${taskId} \u91CD\u8BD5\u6700\u8FD1\u5931\u8D25\u9879`] : []
].join("\n");
}
@@ -3137,7 +3144,8 @@ async function ensureSilentNotice(client2, chatId, fileCount, replyToMsg) {
}
if (silentNoticeMessageIdMap.get(chatIdStr)) return;
}
const text = buildSilentModeNotice(fileCount, getSessionTaskId(chatIdStr));
const queueStats = getDownloadQueueStats();
const text = buildSilentModeNotice(fileCount, getSessionTaskId(chatIdStr), queueStats.paused, queueStats.pauseReason);
const sendPromise = (async () => {
let sMsg;
if (replyToMsg) {
@@ -3256,7 +3264,8 @@ var BetterDownloadQueue = class {
total: this.active.length + this.queue.length,
paused: this.paused,
diskPressurePaused: this.diskPressurePaused,
diskPressureReason: this.diskPressureReason
diskPressureReason: this.diskPressureReason,
pauseReason: this.diskPressureReason || (this.paused ? "\u7528\u6237\u5DF2\u6682\u505C\u4E0B\u8F7D\u961F\u5217" : void 0)
};
}
getDetailedStatus() {
@@ -3266,7 +3275,8 @@ var BetterDownloadQueue = class {
history: [...this.history],
paused: this.paused,
diskPressurePaused: this.diskPressurePaused,
diskPressureReason: this.diskPressureReason
diskPressureReason: this.diskPressureReason,
pauseReason: this.diskPressureReason || (this.paused ? "\u7528\u6237\u5DF2\u6682\u505C\u4E0B\u8F7D\u961F\u5217" : void 0)
};
}
// Update progress method
@@ -3431,7 +3441,8 @@ async function finalizeSilentSessionIfDone(client2, chatId) {
}))
]
);
await safeEditMessage(client2, chatId, { message: silentMsgId, text, buttons: new Api2.ReplyInlineMarkup({ rows: [] }) });
const controls = s?.failed ? buildTaskControlButtons(s?.taskId) : new Api2.ReplyInlineMarkup({ rows: [] });
await safeEditMessage(client2, chatId, { message: silentMsgId, text, buttons: controls });
}
silentSessionMap.delete(chatIdStr);
if (s?.taskId) taskIdToChatId.delete(s.taskId);
@@ -3671,8 +3682,25 @@ async function refreshSilentProgress(client2, chatId) {
const session = syncSilentSessionTotals(chatIdStr) || getSilentSession(chatIdStr);
const batches = getConsolidatedBatches(chatIdStr);
const files = getConsolidatedFiles(chatIdStr);
const text = buildSilentProgress(session.total, batches, files, session.completed, session.failed, session.taskId);
await safeEditMessage(client2, chatId, { message: silentMsgId, text, buttons: buildTaskControlButtons(session.taskId) });
const totalBatchFiles = batches.reduce((sum, batch) => sum + batch.totalFiles, 0);
const completedBatchFiles = batches.reduce((sum, batch) => sum + batch.completed, 0);
const completedSingleFiles = files.filter((file) => file.phase === "success" || file.phase === "failed").length;
const totalFiles = Math.max(session.total, totalBatchFiles + files.length, completedBatchFiles + completedSingleFiles, session.completed);
const completedFiles = Math.max(session.completed, completedBatchFiles + completedSingleFiles);
const isComplete = totalFiles > 0 && completedFiles >= totalFiles;
const queueStats = getDownloadQueueStats();
const text = buildSilentProgress(
session.total,
batches,
files,
session.completed,
session.failed,
session.taskId,
queueStats.paused,
queueStats.pauseReason
);
const buttons = isComplete && session.failed === 0 ? new Api2.ReplyInlineMarkup({ rows: [] }) : buildTaskControlButtons(session.taskId);
await safeEditMessage(client2, chatId, { message: silentMsgId, text, buttons });
}
async function checkAndResetSession(client2, chatId) {
const chatIdStr = chatId.toString();
@@ -3752,6 +3780,39 @@ function cancelDownloadTask(selector) {
}
return downloadQueue.cancel(normalized, "\u7528\u6237\u901A\u8FC7 /task_cancel \u53D6\u6D88\u4EFB\u52A1");
}
async function cancelSilentTask(client2, chatId, taskId, fallbackMessageId) {
const mappedChatId = resolveTaskChatId(taskId);
const chatIdStr = mappedChatId || chatId.toString();
const editChatId = mappedChatId || chatId;
const session = silentSessionMap.get(chatIdStr);
const silentMsgId = silentNoticeMessageIdMap.get(chatIdStr) || fallbackMessageId;
const result = cancelDownloadTask(taskId);
const total = Math.max(session?.total || 0, result.total);
const completed = session?.completed || 0;
const failed = session?.failed || 0;
const pendingOrActive = Math.max(0, total - completed);
if (silentMsgId) {
const text = [
`\u{1F6D1} **\u540E\u53F0\u4EFB\u52A1\u5DF2\u53D6\u6D88**`,
``,
`\u{1F194} \u4EFB\u52A1\uFF1A\`${taskId}\``,
`\u2705 \u5DF2\u5B8C\u6210: ${completed} \u4E2A\u6587\u4EF6`,
...failed > 0 ? [`\u274C \u5931\u8D25: ${failed} \u4E2A\u6587\u4EF6`] : [],
`\u{1F6AB} \u5DF2\u505C\u6B62/\u6E05\u7A7A: ${pendingOrActive} \u4E2A\u7B49\u5F85\u6216\u8FDB\u884C\u4E2D\u7684\u4EFB\u52A1`,
``,
`\u5DF2\u79FB\u9664\u6682\u505C / \u7EE7\u7EED / \u53D6\u6D88\u6309\u94AE\uFF0C\u6B64\u4EFB\u52A1\u4E0D\u4F1A\u518D\u54CD\u5E94\u65E7\u6309\u94AE\u64CD\u4F5C\u3002`
].join("\n");
await safeEditMessage(client2, editChatId, { message: silentMsgId, text, buttons: new Api2.ReplyInlineMarkup({ rows: [] }) });
}
silentSessionMap.delete(chatIdStr);
if (session?.taskId) taskIdToChatId.delete(session.taskId);
taskIdToChatId.delete(taskId);
silentNoticeMessageIdMap.delete(chatIdStr);
lastSilentNotificationTimeMap.delete(chatIdStr);
clearConsolidatedState(chatIdStr);
resetChatTransferSession(chatIdStr);
return result;
}
function retryFailedDownloadTasks(limit = 10, _taskId) {
return downloadQueue.retryFailed(limit);
}
@@ -6369,15 +6430,17 @@ async function handleTaskQueueCallback(update, data) {
try {
if (action === "pause") {
pauseDownloadTasks(taskId);
await refreshSilentProgress(client, update.peer);
await client.invoke(new Api4.messages.SetBotCallbackAnswer({ queryId: update.queryId, message: "\u5DF2\u6682\u505C\u4E0B\u8F7D\u961F\u5217" }));
return;
}
if (action === "resume") {
resumeDownloadTasks(taskId);
await refreshSilentProgress(client, update.peer);
await client.invoke(new Api4.messages.SetBotCallbackAnswer({ queryId: update.queryId, message: "\u5DF2\u7EE7\u7EED\u4E0B\u8F7D\u961F\u5217" }));
return;
}
cancelDownloadTask(taskId);
await cancelSilentTask(client, update.peer, taskId, update.msgId);
await client.invoke(new Api4.messages.SetBotCallbackAnswer({ queryId: update.queryId, message: "\u5DF2\u53D6\u6D88\u540E\u53F0\u4EFB\u52A1", alert: true }));
} catch (error) {
await client.invoke(new Api4.messages.SetBotCallbackAnswer({
+4 -2
View File
@@ -8,7 +8,7 @@ import { storageManager } from '../services/storage.js';
import { authenticatedUsers, passwordInputState, isAuthenticated, loadAuthenticatedUsers, persistAuthenticatedUser, userStates, TelegramUserState } from './telegramState.js';
import { is2FAEnabled, generateOTPAuthUrl, verifyTOTP, activate2FA } from '../utils/security.js';
import { handleStart, handleHelp, handleStorage, handleList, handleDelete, handleTasks, handleStopTasks, handlePauseTasks, handleResumeTasks, handleCancelTask, handleRetryFailedTasks, handleDownloadWorkers, handleDownloadWorkersCallback, handlePathRules, handlePathRulesCallback, handleDuplicateMode, handleDuplicateModeCallback, handleCleanupSettings, handleCleanupSettingsCallback } from './telegramCommands.js';
import { handleFileUpload, handleCleanupCallback, pauseDownloadTasks, resumeDownloadTasks, cancelDownloadTask, resolveTaskChatIdForControl } from './telegramUpload.js';
import { handleFileUpload, handleCleanupCallback, pauseDownloadTasks, resumeDownloadTasks, resolveTaskChatIdForControl, refreshSilentProgress, cancelSilentTask } from './telegramUpload.js';
import { handleYtDlpCommand } from './ytDlpDownload.js';
import {
enqueueTelegramDateDownload,
@@ -523,15 +523,17 @@ async function handleTaskQueueCallback(update: Api.UpdateBotCallbackQuery, data:
try {
if (action === 'pause') {
pauseDownloadTasks(taskId);
await refreshSilentProgress(client, update.peer);
await client.invoke(new Api.messages.SetBotCallbackAnswer({ queryId: update.queryId, message: '已暂停下载队列' }));
return;
}
if (action === 'resume') {
resumeDownloadTasks(taskId);
await refreshSilentProgress(client, update.peer);
await client.invoke(new Api.messages.SetBotCallbackAnswer({ queryId: update.queryId, message: '已继续下载队列' }));
return;
}
cancelDownloadTask(taskId);
await cancelSilentTask(client, update.peer, taskId, update.msgId);
await client.invoke(new Api.messages.SetBotCallbackAnswer({ queryId: update.queryId, message: '已取消后台任务', alert: true }));
} catch (error) {
await client.invoke(new Api.messages.SetBotCallbackAnswer({
+65 -5
View File
@@ -262,7 +262,8 @@ async function ensureSilentNotice(client: TelegramClient, chatId: Api.TypeEntity
if (silentNoticeMessageIdMap.get(chatIdStr)) return;
}
const text = buildSilentModeNotice(fileCount, getSessionTaskId(chatIdStr));
const queueStats = getDownloadQueueStats();
const text = buildSilentModeNotice(fileCount, getSessionTaskId(chatIdStr), queueStats.paused, queueStats.pauseReason);
const sendPromise = (async () => {
let sMsg: any;
@@ -416,6 +417,7 @@ class BetterDownloadQueue {
paused: this.paused,
diskPressurePaused: this.diskPressurePaused,
diskPressureReason: this.diskPressureReason,
pauseReason: this.diskPressureReason || (this.paused ? '用户已暂停下载队列' : undefined),
};
}
@@ -427,6 +429,7 @@ class BetterDownloadQueue {
paused: this.paused,
diskPressurePaused: this.diskPressurePaused,
diskPressureReason: this.diskPressureReason,
pauseReason: this.diskPressureReason || (this.paused ? '用户已暂停下载队列' : undefined),
};
}
@@ -635,7 +638,8 @@ async function finalizeSilentSessionIfDone(client: TelegramClient, chatId: Api.T
})),
],
);
await safeEditMessage(client, chatId, { message: silentMsgId, text, buttons: new Api.ReplyInlineMarkup({ rows: [] }) });
const controls = s?.failed ? buildTaskControlButtons(s?.taskId) : new Api.ReplyInlineMarkup({ rows: [] });
await safeEditMessage(client, chatId, { message: silentMsgId, text, buttons: controls });
}
// 清理静默状态
@@ -956,7 +960,7 @@ function syncSilentSessionTotals(chatIdStr: string): SilentSession | null {
return session;
}
async function refreshSilentProgress(client: TelegramClient, chatId: Api.TypeEntityLike) {
export async function refreshSilentProgress(client: TelegramClient, chatId: Api.TypeEntityLike) {
const chatIdStr = chatId.toString();
if (!silentSessionMap.has(chatIdStr)) return;
const silentMsgId = silentNoticeMessageIdMap.get(chatIdStr);
@@ -965,8 +969,27 @@ async function refreshSilentProgress(client: TelegramClient, chatId: Api.TypeEnt
const session = syncSilentSessionTotals(chatIdStr) || getSilentSession(chatIdStr);
const batches = getConsolidatedBatches(chatIdStr);
const files = getConsolidatedFiles(chatIdStr);
const text = buildSilentProgress(session.total, batches, files, session.completed, session.failed, session.taskId);
await safeEditMessage(client, chatId, { message: silentMsgId, text, buttons: buildTaskControlButtons(session.taskId) });
const totalBatchFiles = batches.reduce((sum, batch) => sum + batch.totalFiles, 0);
const completedBatchFiles = batches.reduce((sum, batch) => sum + batch.completed, 0);
const completedSingleFiles = files.filter(file => file.phase === 'success' || file.phase === 'failed').length;
const totalFiles = Math.max(session.total, totalBatchFiles + files.length, completedBatchFiles + completedSingleFiles, session.completed);
const completedFiles = Math.max(session.completed, completedBatchFiles + completedSingleFiles);
const isComplete = totalFiles > 0 && completedFiles >= totalFiles;
const queueStats = getDownloadQueueStats();
const text = buildSilentProgress(
session.total,
batches,
files,
session.completed,
session.failed,
session.taskId,
queueStats.paused,
queueStats.pauseReason,
);
const buttons = isComplete && session.failed === 0
? new Api.ReplyInlineMarkup({ rows: [] })
: buildTaskControlButtons(session.taskId);
await safeEditMessage(client, chatId, { message: silentMsgId, text, buttons });
}
@@ -1079,6 +1102,43 @@ export function cancelDownloadTask(selector?: string) {
return downloadQueue.cancel(normalized, '用户通过 /task_cancel 取消任务');
}
export async function cancelSilentTask(client: TelegramClient, chatId: Api.TypeEntityLike, taskId: string, fallbackMessageId?: number) {
const mappedChatId = resolveTaskChatId(taskId);
const chatIdStr = mappedChatId || chatId.toString();
const editChatId = mappedChatId || chatId;
const session = silentSessionMap.get(chatIdStr);
const silentMsgId = silentNoticeMessageIdMap.get(chatIdStr) || fallbackMessageId;
const result = cancelDownloadTask(taskId);
const total = Math.max(session?.total || 0, result.total);
const completed = session?.completed || 0;
const failed = session?.failed || 0;
const pendingOrActive = Math.max(0, total - completed);
if (silentMsgId) {
const text = [
`🛑 **后台任务已取消**`,
``,
`🆔 任务:\`${taskId}\``,
`✅ 已完成: ${completed} 个文件`,
...(failed > 0 ? [`❌ 失败: ${failed} 个文件`] : []),
`🚫 已停止/清空: ${pendingOrActive} 个等待或进行中的任务`,
``,
`已移除暂停 / 继续 / 取消按钮,此任务不会再响应旧按钮操作。`,
].join('\n');
await safeEditMessage(client, editChatId, { message: silentMsgId, text, buttons: new Api.ReplyInlineMarkup({ rows: [] }) });
}
silentSessionMap.delete(chatIdStr);
if (session?.taskId) taskIdToChatId.delete(session.taskId);
taskIdToChatId.delete(taskId);
silentNoticeMessageIdMap.delete(chatIdStr);
lastSilentNotificationTimeMap.delete(chatIdStr);
clearConsolidatedState(chatIdStr);
resetChatTransferSession(chatIdStr);
return result;
}
export function retryFailedDownloadTasks(limit = 10, _taskId?: string) {
// 当前失败历史来自全局队列;taskId 仅用于用户侧定位任务提示。
return downloadQueue.retryFailed(limit);
+21 -12
View File
@@ -22,18 +22,21 @@ export function getProviderDisplayName(providerName: string): string {
return PROVIDER_DISPLAY_MAP[providerName] || `📦 ${providerName}`;
}
function buildTaskControlLines(taskId?: string): string[] {
function buildTaskControlLines(taskId?: string, queuePaused = false, pauseReason?: string): string[] {
if (!taskId) return [`💡 发送 /tasks 查看实时任务状态`];
if (queuePaused) {
return [
`⏸️ **当前状态:已暂停**`,
pauseReason ? `📌 原因:${pauseReason}` : `📌 等待中的下载任务不会继续开始`,
`▶️ 点击下方“继续”可恢复后台下载队列`,
`🛑 点击“取消”可停止当前后台任务并清空等待项`,
];
}
return [
`💡 下载队列控制:请优先点下方按钮`,
`⏸ 暂停:暂时停止继续下载,已在处理的任务会尽快停住`,
`▶️ 继续:从暂停处恢复后台下载队列`,
`🛑 取消:停止当前后台任务并清空等待项`,
``,
`备用手动命令:`,
`/task_pause ${taskId}`,
`/task_resume ${taskId}`,
`/task_cancel ${taskId}`,
];
}
@@ -505,14 +508,14 @@ export function buildDeleteSuccess(fileName: string, fileId: string): string {
// ─── 多文件上传 ──────────────────────────────────────────────
/** 静默模式通知 */
export function buildSilentModeNotice(fileCount: number, taskId?: string): string {
export function buildSilentModeNotice(fileCount: number, taskId?: string, queuePaused = false, pauseReason?: string): string {
return [
`🤐 **已切换到静默模式**`,
queuePaused ? `⏸️ **后台下载已暂停**` : `🤐 **已切换到静默模式**`,
...(taskId ? [`🆔 任务:\`${taskId}\``] : []),
``,
`Bot 将在后台继续处理所有文件,请耐心等待。`,
queuePaused ? `等待任务已暂停,不会继续开始新的下载。` : `Bot 将在后台继续处理所有文件,请耐心等待。`,
``,
...buildTaskControlLines(taskId),
...buildTaskControlLines(taskId, queuePaused, pauseReason),
].join('\n');
}
@@ -544,6 +547,8 @@ export function buildSilentProgress(
sessionCompleted: number = 0,
sessionFailed: number = 0,
taskId?: string,
queuePaused = false,
pauseReason?: string,
): string {
const totalBatchFiles = batches.reduce((sum, batch) => sum + batch.totalFiles, 0);
const completedBatchFiles = batches.reduce((sum, batch) => sum + batch.completed, 0);
@@ -556,13 +561,17 @@ export function buildSilentProgress(
const failedFiles = Math.max(sessionFailed, failedBatchFiles + failedSingleFiles);
const successfulFiles = Math.max(0, completedFiles - failedFiles);
const remainingFiles = Math.max(0, totalFiles - completedFiles);
const isComplete = totalFiles > 0 && remainingFiles === 0;
const activeBatch = batches.find(batch => batch.completed < batch.totalFiles);
const activeSingle = singleFiles.find(file => !['success', 'failed'].includes(file.phase));
const currentFile = activeBatch?.currentFileName || activeSingle?.fileName;
const progress = generateProgressBar(completedFiles, Math.max(totalFiles, 1));
if (isComplete) {
return buildSilentAllTasksComplete(totalFiles, failedFiles, taskId, singleFiles, batches);
}
return [
`🤐 **后台批量处理中**`,
queuePaused ? `⏸️ **后台下载已暂停**` : `🤐 **后台批量处理中**`,
`${progress} (${completedFiles}/${totalFiles})`,
``,
`✅ 成功: ${successfulFiles} ❌ 失败: ${failedFiles} ⏳ 剩余: ${remainingFiles}`,
@@ -570,7 +579,7 @@ export function buildSilentProgress(
...(activeBatch ? [`📁 批次: ${activeBatch.folderName}`] : []),
...(activeBatch?.queuePending ? [`🕒 队列等待: ${activeBatch.queuePending}`] : []),
``,
...buildTaskControlLines(taskId),
...buildTaskControlLines(taskId, queuePaused, pauseReason),
...(taskId && failedFiles > 0 && remainingFiles === 0 ? [`🔄 检测到失败任务,可发送 /tg_retry ${taskId} 重试最近失败项`] : []),
].join('\n');
}