mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
feat: telegram request throttling per chat id
This commit is contained in:
@@ -1,8 +1,27 @@
|
|||||||
import { markdown2Html } from '../../services/markdown.js';
|
import { markdown2Html } from '../../services/markdown.js';
|
||||||
import { getJob } from '../../services/storage/jobStorage.js';
|
import { getJob } from '../../services/storage/jobStorage.js';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
import pThrottle from 'p-throttle';
|
||||||
|
|
||||||
const MAX_ENTITIES_PER_CHUNK = 8;
|
const MAX_ENTITIES_PER_CHUNK = 8;
|
||||||
const RATE_LIMIT_INTERVAL = 1010;
|
const RATE_LIMIT_INTERVAL = 1000;
|
||||||
|
const chatThrottleMap = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a throttled async function for sending messages to a specific chat.
|
||||||
|
* Telegram enforces a rate limit of 1 message per second per chat (chatId).
|
||||||
|
*
|
||||||
|
* @param {number} chatId - The chat ID to throttle messages for.
|
||||||
|
* @param {Function} fn - The async function to throttle (should send the message).
|
||||||
|
* @returns {Function} Throttled async function for sending messages.
|
||||||
|
*/
|
||||||
|
function getThrottled(chatId, fn) {
|
||||||
|
if (!chatThrottleMap.has(chatId)) {
|
||||||
|
chatThrottleMap.set(chatId, pThrottle({ limit: 1, interval: RATE_LIMIT_INTERVAL })(fn));
|
||||||
|
}
|
||||||
|
return chatThrottleMap.get(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* splitting an array into chunks because Telegram only allows for messages up to
|
* splitting an array into chunks because Telegram only allows for messages up to
|
||||||
* 4096 chars, thus we have to split messages into chunks
|
* 4096 chars, thus we have to split messages into chunks
|
||||||
@@ -22,41 +41,36 @@ export const send = ({ serviceName, newListings, notificationConfig, jobKey }) =
|
|||||||
const { token, chatId } = notificationConfig.find((adapter) => adapter.id === config.id).fields;
|
const { token, chatId } = notificationConfig.find((adapter) => adapter.id === config.id).fields;
|
||||||
const job = getJob(jobKey);
|
const job = getJob(jobKey);
|
||||||
const jobName = job == null ? jobKey : job.name;
|
const jobName = job == null ? jobKey : job.name;
|
||||||
// we have to split messages into chunks, because otherwise messages are going to become too big and will fail
|
|
||||||
const chunks = arrayChunks(newListings, MAX_ENTITIES_PER_CHUNK);
|
const chunks = arrayChunks(newListings, MAX_ENTITIES_PER_CHUNK);
|
||||||
|
|
||||||
|
const getThrottledSend = getThrottled(chatId, async function (body) {
|
||||||
|
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const promises = chunks.map((chunk) => {
|
const promises = chunks.map((chunk) => {
|
||||||
const messageParagraphs = [];
|
const messageParagraphs = [];
|
||||||
|
|
||||||
messageParagraphs.push(`<i>${jobName}</i> (${serviceName}) found <b>${newListings.length}</b> new listings:`);
|
messageParagraphs.push(`<i>${jobName}</i> (${serviceName}) found <b>${newListings.length}</b> new listings:`);
|
||||||
messageParagraphs.push(...chunk.map(
|
messageParagraphs.push(
|
||||||
(o) =>
|
...chunk.map(
|
||||||
`<a href='${o.link}'><b>${shorten(o.title.replace(/\*/g, ''), 45).trim()}</b></a>\n` +
|
(o) =>
|
||||||
[o.address, o.price, o.size].join(' | ')
|
`<a href='${o.link}'><b>${shorten(o.title.replace(/\*/g, ''), 45).trim()}</b></a>\n` +
|
||||||
));
|
[o.address, o.price, o.size].join(' | '),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
const body = {
|
||||||
* This is to not break the rate limit. It is to only send 1 message per second
|
chat_id: chatId,
|
||||||
*/
|
text: messageParagraphs.join('\n\n'),
|
||||||
return new Promise((resolve, reject) => {
|
parse_mode: 'HTML',
|
||||||
setTimeout(() => {
|
disable_web_page_preview: true,
|
||||||
fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
|
};
|
||||||
method: 'post',
|
|
||||||
body: JSON.stringify({
|
return getThrottledSend(body);
|
||||||
chat_id: chatId,
|
|
||||||
text: messageParagraphs.join('\n\n'),
|
|
||||||
parse_mode: 'HTML',
|
|
||||||
disable_web_page_preview: true,
|
|
||||||
}),
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
reject();
|
|
||||||
});
|
|
||||||
}, RATE_LIMIT_INTERVAL);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
"nanoid": "5.1.5",
|
"nanoid": "5.1.5",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"node-mailjet": "6.0.8",
|
"node-mailjet": "6.0.8",
|
||||||
|
"p-throttle": "^7.0.0",
|
||||||
"package-up": "^5.0.0",
|
"package-up": "^5.0.0",
|
||||||
"puppeteer": "^24.14.0",
|
"puppeteer": "^24.14.0",
|
||||||
"puppeteer-extra": "^3.3.6",
|
"puppeteer-extra": "^3.3.6",
|
||||||
|
|||||||
@@ -5432,6 +5432,11 @@ p-locate@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^3.0.2"
|
p-limit "^3.0.2"
|
||||||
|
|
||||||
|
p-throttle@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-throttle/-/p-throttle-7.0.0.tgz#d2650e884dad46fd626a9a5cfc3fb239cb799dee"
|
||||||
|
integrity sha512-aio0v+S0QVkH1O+9x4dHtD4dgCExACcL+3EtNaGqC01GBudS9ijMuUsmN8OVScyV4OOp0jqdLShZFuSlbL/AsA==
|
||||||
|
|
||||||
pac-proxy-agent@^7.1.0:
|
pac-proxy-agent@^7.1.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz#9cfaf33ff25da36f6147a20844230ec92c06e5df"
|
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz#9cfaf33ff25da36f6147a20844230ec92c06e5df"
|
||||||
|
|||||||
Reference in New Issue
Block a user