feat: implement auto-retry for Telegram API requests on error 429 and update documentation

This commit is contained in:
MythEclipse
2026-05-18 21:02:38 +07:00
parent 91cfee0b37
commit 721fa3db7d
5 changed files with 70 additions and 122 deletions
+2 -43
View File
@@ -1,48 +1,7 @@
import logger from './logger';
interface RateLimitRecord {
count: number;
reset: number;
}
const rateLimitMap = new Map<string, RateLimitRecord>();
export const checkRateLimit = (key: string): boolean => {
const now = Date.now();
const windowMs = parseInt(process.env.RATE_LIMIT_WINDOW_MS!, 10) || 60000;
const maxRequests = parseInt(process.env.RATE_LIMIT_MAX_REQUESTS!, 10) || 30;
if (!rateLimitMap.has(key)) {
rateLimitMap.set(key, { count: 0, reset: now + windowMs });
}
const record = rateLimitMap.get(key)!;
if (now > record.reset) {
record.count = 0;
record.reset = now + windowMs;
}
if (record.count >= maxRequests) {
logger.warn('Rate limit exceeded', { key, count: record.count, reset: record.reset });
return false;
}
record.count++;
export const checkRateLimit = (_key: string): boolean => {
return true;
};
export const cleanupRateLimitCache = (): void => {
const now = Date.now();
const keysToDelete: string[] = [];
for (const [key, record] of rateLimitMap.entries()) {
if (now > record.reset) {
keysToDelete.push(key);
}
}
for (const key of keysToDelete) {
rateLimitMap.delete(key);
}
// No-op karena rate limit dinonaktifkan
};
+31 -13
View File
@@ -5,6 +5,24 @@ import logger from './logger';
const bot = new Telegraf(config.botToken);
const TELEGRAM_API_URL = `https://api.telegram.org/bot${config.botToken}/`;
const withRetry = async <T>(fn: () => Promise<T>, retries = 5): Promise<T> => {
try {
return await fn();
} catch (error: any) {
const errorStr = error.message || String(error);
const match = errorStr.match(/retry after (\d+)/i);
if (match && retries > 0) {
const seconds = parseInt(match[1], 10);
logger.warn(`Telegram 429 Too Many Requests detected. Retrying after ${seconds} seconds...`, {
error: errorStr,
});
await new Promise((resolve) => setTimeout(resolve, seconds * 1000));
return withRetry(fn, retries - 1);
}
throw error;
}
};
interface ForwardResult {
telegramFileId: string;
telegramFileUniqueId: string;
@@ -27,31 +45,31 @@ export const forwardToStorage = async (
let result: any;
if (fileType === 'photo') {
result = await bot.telegram.sendPhoto(config.storageChatId, filePayload, {
result = await withRetry(() => bot.telegram.sendPhoto(config.storageChatId, filePayload, {
caption: fileName,
});
}));
} else if (fileType === 'audio') {
result = await bot.telegram.sendAudio(config.storageChatId, filePayload, {
result = await withRetry(() => bot.telegram.sendAudio(config.storageChatId, filePayload, {
caption: fileName,
});
}));
} else if (fileType === 'video') {
result = await bot.telegram.sendVideo(config.storageChatId, filePayload, {
result = await withRetry(() => bot.telegram.sendVideo(config.storageChatId, filePayload, {
caption: fileName,
});
}));
} else if (fileType === 'voice') {
result = await bot.telegram.sendVoice(config.storageChatId, filePayload, {
result = await withRetry(() => bot.telegram.sendVoice(config.storageChatId, filePayload, {
caption: fileName,
});
}));
} else if (fileType === 'animation') {
result = await bot.telegram.sendAnimation(config.storageChatId, filePayload, {
result = await withRetry(() => bot.telegram.sendAnimation(config.storageChatId, filePayload, {
caption: fileName,
});
}));
} else if (fileType === 'sticker') {
result = await bot.telegram.sendSticker(config.storageChatId, filePayload);
result = await withRetry(() => bot.telegram.sendSticker(config.storageChatId, filePayload));
} else {
result = await bot.telegram.sendDocument(config.storageChatId, filePayload, {
result = await withRetry(() => bot.telegram.sendDocument(config.storageChatId, filePayload, {
caption: `📁 ${fileName}`,
});
}));
}
let uploadedFile: any;