Quite often, articles about writing a bot for Telegram appear on Habré, which, if one throws away the uniqueness of an idea, is the most common tutorial on "how to get a message from Telegram, process it and send a response to the user." However, in none of the articles I read (of course, I don’t presume to say that I read them all, but nonetheless) I did not find any mention of the limits for sending messages to users and how to work with them. Who are interested, please under the cat.
Some time ago I sat down to develop a text-based multiplayer strategy based on the Telegram bot API, and a month later I launched the first release with poor but playable functionality. In the dating chain, the game quickly gained a small, actively playing audience and continued to recruit in the following days thanks to the in-game referral program. And everything seems to be fine, until the day online has passed a mark of two hundred users. That's where the problems started. Increasingly, users turned to me asking why the bot is not responding for a few minutes. The strongest discomfort was delivered to the players, especially during wars, when the user tried to quickly restore the army for a counterattack, and the game hung foully and did not respond to any actions. Moreover, Telegram could ban both the sending of all messages, and only messages of specific content with its frequent use, for example, the purchase of resources and the recruitment of soldiers, which used custom keyboard buttons with standard quantitative values.
Experience with the bot API already existed, but for a smaller audience and with less sending intensity. About the limits, too, was known, but really came across them only when working with groups. Everything is much tougher there than when working with personal chat rooms. To learn more about the limits, just refer to the FAQ on the official website of Telegram.
My bot is hitting limits, how do I avoid this?More info: You may receive short bursts that go over this limit, but eventually you'll begin receiving 429 errors.
')
More than 30 messages per second or so. Consider spreading out notifications over large intervals of 8–12 hours for best results.
From the above, we have that it is impossible to send messages to a specific user more than once per second and no more than 30 messages per second during mass distribution to different users. But some errors are allowed. Therefore, we need to send a message to the user every 1/30 seconds, checking whether we have already sent a message to him within the current second, otherwise send a message to the next user if he has passed the same test.
Since the development was initially conducted in the Go language, where there are channels and coroutines (they are also Gorutins), the idea of sending pending messages immediately came to mind. First, we add the processed response to the channel, and in a separate thread, we dig out this channel every 1/30 seconds allowed to us. But the idea of one channel for all messages did not work. Having got a message from the channel and making sure that we cannot send messages to this user yet, we need to put this message somewhere. Sending again to the same channel is not good, because we will break the chronological order of the user's messages, and also strongly postpone the delivery of this message with a large number of active players. Check the message without getting it from the channel and move on to the next one, as far as I know, we also cannot.
Then there is an idea to start the channel on the user. From this point on in more detail.
Now, on the move, I want to use the select case construction to process the resulting set of channels, but the problem is that it describes a fixed set of channels for each case, and in our case, the set of channels is dynamic, as users are added during the game, creating new channels for their posts. Otherwise, do not do without locks. Then, turning to Google, as usual, in the vast StackOverflow there was an excellent solution. And it consists in using the Select function from the package
reflect .
In short, this function allows us to extract from a pre-formed array of SelectCases, each of which contains a channel, a message ready for sending. The principle is the same as in select case, but with an indefinite number of channels. What we need.
func (c *Client) sendDeferredMessages() {
Now in order.
- To begin, we create a timer that will “tick” every 1/30 seconds we need, and run the for loop on it.
- After that, we begin to form the array of SelectCases we need, sorting through our map channels, and adding only non-empty channels to the array, whose users can already receive messages, that is, one second has passed since the last send.
- Create a reflect.SelectCase structure for each channel in which we need to fill two fields: Dir - direction (sending to the channel or extract from the channel), in our case set the flag reflect.SelectRecv (extraction) and Chan - the channel itself.
- Having finished forming the SelectCase array, we give it to reflect.Select () and at the output of the channel id in the SelectCase array, we get the value extracted from the channel and the flag of the successful execution of the operation. If all is well, make an API request and get the answer. Having received an error, call the callback and pass the error there. Do not forget to write to the user the date of the last message sent.
So, everything seems to be simple. Now Telegram will not stick to our bot because of the frequent sending of messages to the user, and it will be comfortable for players to play. Of course, it is clear that with a huge number of users, messages will be sent to the player slower and slower, but this will be done evenly, creating less inconvenience than with single blocking for a few minutes if you do not follow the limits.
By the way, recall the errors specified in the FAQ. In my implementation, I send users two messages per second instead of one and not once in 1/30 second, but once in 1/40, which is much more often than recommended. But while problems did not arise.
The source code of the client can be viewed on
gitlabWell, if someone was interested in what it was about, then in Telegram @
BastionSiegeBot