Skip to content

Commit f3f8c30

Browse files
feat: telegram request throttling per chat id
1 parent 9bb33e7 commit f3f8c30

File tree

3 files changed

+50
-30
lines changed

3 files changed

+50
-30
lines changed

lib/notification/adapter/telegram.js

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,27 @@
11
import { markdown2Html } from '../../services/markdown.js';
22
import { getJob } from '../../services/storage/jobStorage.js';
33
import fetch from 'node-fetch';
4+
import pThrottle from 'p-throttle';
5+
46
const MAX_ENTITIES_PER_CHUNK = 8;
5-
const RATE_LIMIT_INTERVAL = 1010;
7+
const RATE_LIMIT_INTERVAL = 1000;
8+
const chatThrottleMap = new Map();
9+
10+
/**
11+
* Returns a throttled async function for sending messages to a specific chat.
12+
* Telegram enforces a rate limit of 1 message per second per chat (chatId).
13+
*
14+
* @param {number} chatId - The chat ID to throttle messages for.
15+
* @param {Function} fn - The async function to throttle (should send the message).
16+
* @returns {Function} Throttled async function for sending messages.
17+
*/
18+
function getThrottled(chatId, fn) {
19+
if (!chatThrottleMap.has(chatId)) {
20+
chatThrottleMap.set(chatId, pThrottle({ limit: 1, interval: RATE_LIMIT_INTERVAL })(fn));
21+
}
22+
return chatThrottleMap.get(chatId);
23+
}
24+
625
/**
726
* splitting an array into chunks because Telegram only allows for messages up to
827
* 4096 chars, thus we have to split messages into chunks
@@ -22,41 +41,36 @@ export const send = ({ serviceName, newListings, notificationConfig, jobKey }) =
2241
const { token, chatId } = notificationConfig.find((adapter) => adapter.id === config.id).fields;
2342
const job = getJob(jobKey);
2443
const jobName = job == null ? jobKey : job.name;
25-
// we have to split messages into chunks, because otherwise messages are going to become too big and will fail
2644
const chunks = arrayChunks(newListings, MAX_ENTITIES_PER_CHUNK);
45+
46+
const getThrottledSend = getThrottled(chatId, async function (body) {
47+
await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
48+
method: 'post',
49+
body: JSON.stringify(body),
50+
headers: { 'Content-Type': 'application/json' },
51+
});
52+
});
53+
2754
const promises = chunks.map((chunk) => {
2855
const messageParagraphs = [];
2956

3057
messageParagraphs.push(`<i>${jobName}</i> (${serviceName}) found <b>${newListings.length}</b> new listings:`);
31-
messageParagraphs.push(...chunk.map(
32-
(o) =>
33-
`<a href='${o.link}'><b>${shorten(o.title.replace(/\*/g, ''), 45).trim()}</b></a>\n` +
34-
[o.address, o.price, o.size].join(' | ')
35-
));
58+
messageParagraphs.push(
59+
...chunk.map(
60+
(o) =>
61+
`<a href='${o.link}'><b>${shorten(o.title.replace(/\*/g, ''), 45).trim()}</b></a>\n` +
62+
[o.address, o.price, o.size].join(' | '),
63+
),
64+
);
3665

37-
/**
38-
* This is to not break the rate limit. It is to only send 1 message per second
39-
*/
40-
return new Promise((resolve, reject) => {
41-
setTimeout(() => {
42-
fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
43-
method: 'post',
44-
body: JSON.stringify({
45-
chat_id: chatId,
46-
text: messageParagraphs.join('\n\n'),
47-
parse_mode: 'HTML',
48-
disable_web_page_preview: true,
49-
}),
50-
headers: { 'Content-Type': 'application/json' },
51-
})
52-
.then(() => {
53-
resolve();
54-
})
55-
.catch(() => {
56-
reject();
57-
});
58-
}, RATE_LIMIT_INTERVAL);
59-
});
66+
const body = {
67+
chat_id: chatId,
68+
text: messageParagraphs.join('\n\n'),
69+
parse_mode: 'HTML',
70+
disable_web_page_preview: true,
71+
};
72+
73+
return getThrottledSend(body);
6074
});
6175
return Promise.all(promises);
6276
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"nanoid": "5.1.5",
7373
"node-fetch": "3.3.2",
7474
"node-mailjet": "6.0.8",
75+
"p-throttle": "^7.0.0",
7576
"package-up": "^5.0.0",
7677
"puppeteer": "^24.14.0",
7778
"puppeteer-extra": "^3.3.6",

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5396,6 +5396,11 @@ p-locate@^5.0.0:
53965396
dependencies:
53975397
p-limit "^3.0.2"
53985398

5399+
p-throttle@^7.0.0:
5400+
version "7.0.0"
5401+
resolved "https://registry.yarnpkg.com/p-throttle/-/p-throttle-7.0.0.tgz#d2650e884dad46fd626a9a5cfc3fb239cb799dee"
5402+
integrity sha512-aio0v+S0QVkH1O+9x4dHtD4dgCExACcL+3EtNaGqC01GBudS9ijMuUsmN8OVScyV4OOp0jqdLShZFuSlbL/AsA==
5403+
53995404
pac-proxy-agent@^7.1.0:
54005405
version "7.2.0"
54015406
resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz#9cfaf33ff25da36f6147a20844230ec92c06e5df"

0 commit comments

Comments
 (0)