refactor: remove concurrency limit from Telegram upload queue and enhance media group handling
This commit is contained in:
-116
@@ -6,18 +6,6 @@ import { config } from './env';
|
||||
import logger from './utils/logger';
|
||||
import { forwardToStorage } from './utils/telegram';
|
||||
|
||||
interface MediaGroupBufferItem {
|
||||
ctx: any;
|
||||
fileId: string;
|
||||
fileSize: number;
|
||||
mimeType: string;
|
||||
fileName: string;
|
||||
fileType: string;
|
||||
fileUniqueId: string;
|
||||
}
|
||||
|
||||
const mediaGroupCache = new Map<string, { timer: any; items: MediaGroupBufferItem[] }>();
|
||||
|
||||
export const startBot = async (): Promise<Telegraf<Context>> => {
|
||||
try {
|
||||
const bot = new Telegraf(config.botToken);
|
||||
@@ -88,110 +76,6 @@ export const startBot = async (): Promise<Telegraf<Context>> => {
|
||||
return ctx.reply(`File size exceeds ${maxSize / (1024 * 1024)}MB limit`);
|
||||
}
|
||||
|
||||
const mediaGroupId = ctx.message.media_group_id;
|
||||
|
||||
if (mediaGroupId) {
|
||||
if (!mediaGroupCache.has(mediaGroupId)) {
|
||||
mediaGroupCache.set(mediaGroupId, { timer: null, items: [] });
|
||||
}
|
||||
|
||||
const group = mediaGroupCache.get(mediaGroupId)!;
|
||||
|
||||
if (group.timer) {
|
||||
clearTimeout(group.timer);
|
||||
}
|
||||
|
||||
group.items.push({
|
||||
ctx,
|
||||
fileId: file_id,
|
||||
fileSize: file_size,
|
||||
mimeType: mime_type,
|
||||
fileName: fileName,
|
||||
fileType: fileType,
|
||||
fileUniqueId: fileObj.file_unique_id,
|
||||
});
|
||||
|
||||
group.timer = setTimeout(async () => {
|
||||
mediaGroupCache.delete(mediaGroupId);
|
||||
|
||||
try {
|
||||
const itemsToUpload: MediaGroupBufferItem[] = [];
|
||||
const responses: string[] = [];
|
||||
|
||||
for (const item of group.items) {
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(fileSchema)
|
||||
.where(eq(fileSchema.telegramFileUniqueId, item.fileUniqueId))
|
||||
.limit(1);
|
||||
|
||||
if (existing && existing.length > 0) {
|
||||
const url = `${config.baseUrl}/f/${existing[0].publicId}`;
|
||||
responses.push(`File *${item.fileName}* sudah diupload! 📎\nDownload: ${url}`);
|
||||
} else {
|
||||
itemsToUpload.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemsToUpload.length > 0) {
|
||||
const uploadItems = itemsToUpload.map((item) => ({
|
||||
fileId: item.fileId,
|
||||
fileName: item.fileName,
|
||||
fileType: item.fileType,
|
||||
}));
|
||||
|
||||
const { forwardMediaGroupToStorage } = await import('./utils/telegram');
|
||||
const batchResult = await forwardMediaGroupToStorage(uploadItems);
|
||||
|
||||
const dbInserts = [];
|
||||
for (let i = 0; i < itemsToUpload.length; i++) {
|
||||
const item = itemsToUpload[i];
|
||||
const publicId = nanoid();
|
||||
const uploaded = {
|
||||
publicId,
|
||||
telegramFileId: batchResult.telegramFileIds[i],
|
||||
telegramFileUniqueId: batchResult.telegramFileUniqueIds[i],
|
||||
storageChatId: config.storageChatId,
|
||||
storageMessageId: batchResult.storageMessageId,
|
||||
fileName: item.fileName,
|
||||
mimeType: item.mimeType || 'application/octet-stream',
|
||||
sizeBytes: item.fileSize,
|
||||
fileType: item.fileType,
|
||||
uploaderId: ctx.from.id,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
dbInserts.push(uploaded);
|
||||
responses.push(
|
||||
`File *${item.fileName}* berhasil diupload! 📎\nDownload: ${config.baseUrl}/f/${publicId}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (dbInserts.length > 0) {
|
||||
await db.insert(fileSchema).values(dbInserts);
|
||||
}
|
||||
}
|
||||
|
||||
await ctx.reply(responses.join('\n\n'));
|
||||
logger.info('Media group uploaded as batch', {
|
||||
mediaGroupId,
|
||||
totalFiles: group.items.length,
|
||||
uploadedFiles: itemsToUpload.length,
|
||||
uploader: ctx.from.id,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('Failed to process media group batch', {
|
||||
error: error.message,
|
||||
mediaGroupId,
|
||||
});
|
||||
await ctx.reply('❌ Gagal mengupload beberapa file di media group.');
|
||||
}
|
||||
}, 600);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await db
|
||||
.select()
|
||||
.from(fileSchema)
|
||||
|
||||
@@ -1,47 +1,3 @@
|
||||
type QueueTask<T> = {
|
||||
task: () => Promise<T>;
|
||||
resolve: (value: T | PromiseLike<T>) => void;
|
||||
reject: (reason?: any) => void;
|
||||
};
|
||||
|
||||
class TelegramQueue {
|
||||
private activeCount = 0;
|
||||
private queue: QueueTask<any>[] = [];
|
||||
private concurrency: number;
|
||||
|
||||
constructor(concurrency = 2) {
|
||||
this.concurrency = concurrency;
|
||||
}
|
||||
|
||||
public enqueue<T>(task: () => Promise<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.queue.push({ task, resolve, reject });
|
||||
this.processNext();
|
||||
});
|
||||
}
|
||||
|
||||
private async processNext(): Promise<void> {
|
||||
if (this.activeCount >= this.concurrency || this.queue.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.queue.shift()!;
|
||||
this.activeCount++;
|
||||
|
||||
try {
|
||||
const result = await item.task();
|
||||
item.resolve(result);
|
||||
} catch (error) {
|
||||
item.reject(error);
|
||||
} finally {
|
||||
this.activeCount--;
|
||||
this.processNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const telegramQueue = new TelegramQueue(2);
|
||||
|
||||
export const enqueueUpload = <T>(task: () => Promise<T>): Promise<T> => {
|
||||
return telegramQueue.enqueue(task);
|
||||
return task();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user