fix: preserve concurrent Telegram album files

This commit is contained in:
hicocos committed 2026-08-24 23:25:06 +08:00
1 parent 37fb436c36
commit 7d28bc8e66
4 files changed
+71 -9

No files matched your search

+20 -4
View File
@@ -4517,6 +4517,20 @@ function createTelegramMediaGroupDebouncer(options) {
function telegramMediaGroupQueueKey(chatId, mediaGroupId) {
return `${chatId === void 0 || chatId === null ? "unknown" : String(chatId)}:${mediaGroupId}`;
}
async function getOrCreateTelegramMediaGroupQueue(queues, creations, queueKey, create) {
const existing = queues.get(queueKey);
if (existing) return existing;
const inFlight = creations.get(queueKey);
if (inFlight) return inFlight;
const creation = create().then((queue) => {
queues.set(queueKey, queue);
return queue;
}).finally(() => {
creations.delete(queueKey);
});
creations.set(queueKey, creation);
return creation;
}
function firstCaptionLine2(message) {
return String(message.message || message.text || message.caption || "").split(/\r?\n/)[0].trim();
}
@@ -7261,6 +7275,7 @@ function retryFailedDownloadTasks(limit = 10, taskId, chatId, userId) {
return downloadQueue.retryFailed(limit, { chatId, userId }, taskId);
}
var mediaGroupQueues = /* @__PURE__ */ new Map();
var mediaGroupQueueCreations = /* @__PURE__ */ new Map();
var mediaGroupDebouncer = createTelegramMediaGroupDebouncer({
delayMs: MEDIA_GROUP_DEBOUNCE_MS,
onReady: (mediaGroupId) => processBatchUpload(void 0, mediaGroupId)
@@ -8230,12 +8245,12 @@ async function handleFileUpload(client2, event) {
await checkAndResetSession(client2, message.chatId);
}
const queueKey = telegramMediaGroupQueueKey(message.chatId, mediaGroupId);
let queue = mediaGroupQueues.get(queueKey);
if (!queue) {
const existingQueue = mediaGroupQueues.get(queueKey) || mediaGroupQueueCreations.has(queueKey);
const queue = await getOrCreateTelegramMediaGroupQueue(mediaGroupQueues, mediaGroupQueueCreations, queueKey, async () => {
const chatKey = message.chatId?.toString() || String(senderId);
const selectedTarget = await consumeOrGetTelegramTargetState(chatKey);
const storageTarget = selectedTarget ? storageManager.getTarget(selectedTarget.provider, selectedTarget.accountId) : storageManager.getActiveTarget();
queue = {
return {
mediaGroupId,
queueKey,
chatId: message.chatId,
@@ -8247,7 +8262,8 @@ async function handleFileUpload(client2, event) {
createdAt: Date.now(),
lastAddedAt: Date.now()
};
mediaGroupQueues.set(queueKey, queue);
});
if (!existingQueue) {
const queueInstance = queue;
setTimeout(() => {
if (mediaGroupQueues.get(queueKey) === queueInstance && !queueInstance.processingStarted) {
+7 -5
View File
@@ -37,7 +37,7 @@ import {
import { getUniqueStoredName } from '../utils/fileUtils.js';
import { buildStorageFolderWithRules, getStoragePathRules, getTelegramBatchFolderName, getTelegramChatName, isOpaqueTelegramIdentifier } from '../utils/storagePath.js';
import { resolveTelegramGeneratedFileName } from '../utils/telegramNaming.js';
import { annotateTelegramMediaGroup, createTelegramMediaGroupDebouncer, getForwardedSourceLookup, prefetchForwardedSourceMessages, takePendingMediaGroupSnapshot, telegramMediaGroupQueueKey, type ForwardedSourceMessageCache } from '../utils/telegramMediaGroup.js';
import { annotateTelegramMediaGroup, createTelegramMediaGroupDebouncer, getForwardedSourceLookup, getOrCreateTelegramMediaGroupQueue, prefetchForwardedSourceMessages, takePendingMediaGroupSnapshot, telegramMediaGroupQueueKey, type ForwardedSourceMessageCache } from '../utils/telegramMediaGroup.js';
import { resolveTelegramStorageFolderPersistent, resolveTelegramTaskStorageFolderPersistent, previewTelegramStorageFolderPersistent } from '../utils/telegramPathSettings.js';
import { consumeOrGetTelegramTargetState } from '../utils/telegramTargetStateStore.js';
import { findDuplicateFile, getDuplicateMode } from '../utils/duplicatePolicy.js';
@@ -1388,6 +1388,7 @@ interface MediaGroupQueue {
// 多文件上传队列 (key: mediaGroupId)
const mediaGroupQueues = new Map<string, MediaGroupQueue>();
const mediaGroupQueueCreations = new Map<string, Promise<MediaGroupQueue>>();
// 多文件上传动态 debounce:每收到同组新消息后重置短等待窗口,同时设置最大等待兜底,避免已收齐相册还固定等 1.5s。
const mediaGroupDebouncer = createTelegramMediaGroupDebouncer({
@@ -2586,14 +2587,14 @@ export async function handleFileUpload(client: TelegramClient, event: NewMessage
await checkAndResetSession(client, message.chatId);
}
const queueKey = telegramMediaGroupQueueKey(message.chatId, mediaGroupId);
let queue = mediaGroupQueues.get(queueKey);
if (!queue) {
const existingQueue = mediaGroupQueues.get(queueKey) || mediaGroupQueueCreations.has(queueKey);
const queue = await getOrCreateTelegramMediaGroupQueue(mediaGroupQueues, mediaGroupQueueCreations, queueKey, async () => {
const chatKey = message.chatId?.toString() || String(senderId);
const selectedTarget = await consumeOrGetTelegramTargetState(chatKey);
const storageTarget = selectedTarget
? storageManager.getTarget(selectedTarget.provider, selectedTarget.accountId)
: storageManager.getActiveTarget();
queue = {
return {
mediaGroupId,
queueKey,
chatId: message.chatId,
@@ -2605,7 +2606,8 @@ export async function handleFileUpload(client: TelegramClient, event: NewMessage
createdAt: Date.now(),
lastAddedAt: Date.now(),
};
mediaGroupQueues.set(queueKey, queue);
});
if (!existingQueue) {
const queueInstance = queue;
setTimeout(() => {
if (mediaGroupQueues.get(queueKey) === queueInstance && !queueInstance.processingStarted) {
@@ -4,6 +4,7 @@ import {
createTelegramMediaGroupDebouncer,
getForwardedSourceLookup,
getOrCreateTelegramMediaGroupQueue,
prefetchForwardedSourceMessages,
takePendingMediaGroupSnapshot,
telegramMediaGroupQueueKey,
@@ -83,6 +84,27 @@ function testPendingSnapshotSkipsProcessedAndDuplicateMessages() {
assert.deepEqual(takePendingMediaGroupSnapshot(items).map(item => item.message.id), [2, 3]);
}
async function testConcurrentAlbumMessagesShareOneQueueCreation() {
const queues = new Map<string, { processingStarted: boolean; files: number[] }>();
const creations = new Map<string, Promise<{ processingStarted: boolean; files: number[] }>>();
let createCount = 0;
let releaseCreation!: () => void;
const creationGate = new Promise<void>(resolve => { releaseCreation = resolve; });
const create = async () => {
createCount += 1;
await creationGate;
return { processingStarted: false, files: [] };
};
const pending = Array.from({ length: 6 }, () => getOrCreateTelegramMediaGroupQueue(queues, creations, 'chat:album', create));
releaseCreation();
const resolved = await Promise.all(pending);
assert.equal(createCount, 1);
assert.equal(queues.size, 1);
assert.ok(resolved.every(queue => queue === resolved[0]));
}
async function testForwardedSourceLookupUsesOneFetchPerPeer() {
const peerA = { className: 'PeerChannel', channelId: { toString: () => '100' } };
const peerB = { className: 'PeerChannel', channelId: { toString: () => '200' } };
@@ -117,6 +139,7 @@ async function main() {
await testAnnotatesMixedMediaInTelegramMessageOrder();
await testQueueKeySeparatesChatsWithSameGroupedId();
testPendingSnapshotSkipsProcessedAndDuplicateMessages();
await testConcurrentAlbumMessagesShareOneQueueCreation();
await testForwardedSourceLookupUsesOneFetchPerPeer();
}
+21
View File
@@ -51,6 +51,27 @@ export function telegramMediaGroupQueueKey(chatId: unknown, mediaGroupId: string
return `${chatId === undefined || chatId === null ? 'unknown' : String(chatId)}:${mediaGroupId}`;
}
export async function getOrCreateTelegramMediaGroupQueue<T>(
queues: Map<string, T>,
creations: Map<string, Promise<T>>,
queueKey: string,
create: () => Promise<T>,
): Promise<T> {
const existing = queues.get(queueKey);
if (existing) return existing;
const inFlight = creations.get(queueKey);
if (inFlight) return inFlight;
const creation = create().then(queue => {
queues.set(queueKey, queue);
return queue;
}).finally(() => {
creations.delete(queueKey);
});
creations.set(queueKey, creation);
return creation;
}
function firstCaptionLine(message: TelegramMediaGroupItemContext['message']): string {
return String(message.message || message.text || message.caption || '').split(/\r?\n/)[0].trim();
}