11import { markdown2Html } from '../../services/markdown.js' ;
22import { getJob } from '../../services/storage/jobStorage.js' ;
33import fetch from 'node-fetch' ;
4+ import pThrottle from 'p-throttle' ;
5+
46const 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} ;
0 commit comments