grammyjs / grammY

The Telegram Bot Framework.
https://grammy.dev
MIT License
2.17k stars 110 forks source link

How to debug when bot stops? #524

Closed Roma-Kyrnis closed 7 months ago

Roma-Kyrnis commented 7 months ago

Hi, I'm using TDLib with Grammy bot. Flow:

  1. User sends /start command to the bot
  2. Bot send auth request to TDLib
  3. Bot asks some questions. TDLib handlers sendBotMessages to user when TDL receive update with auth state
  4. After user is loginned. Bot respond with Custom keyboard.
  5. User click button in the keyborad go to another keyboard "chats" than click on button "add"
  6. Button will trigger Hear "chatsAddHears".
  7. In this process I get Grammy logs but nothing specials. After a 3-5 times of repeating clicking "chatsAddHears" button - bot stops to respond
  8. It happend on the customer side. I don't have this issue. But after bot stops on customer side, bot stops to respond to every one.

Screenshot from 2024-01-24 15-01-39

As I understand my function has stopped and bot is waiting for this function. How to solve this?

I've used: export DEBUG = "grammy*".

Can you help to find a reason of the bot stop?

Roma-Kyrnis commented 7 months ago
chatAddHears export const chatsAddHears: HearsMiddleware = async (ctx: MyContext): Promise => { const telegramId = ctx.from?.id; if (!telegramId) { await ctx.reply('You does not have permission to enter the bot without ID.'); return; } const client = getTDLibInstanceDB(telegramId); if (!client) { await ctx.reply(`Будь-ласка перевірте чи ви увійшли в додаток Телеграму`); return; } console.log('client', client); const chats = await client.getChatsWithInfo(); console.log('client.getChatsWithInfo', chats); const sortedChats = chats.map(chat => [ chat.title, constantsConfig.CHATS.CALLBACK_QUERY.CHOOSE_CHAT + chat.id.toString(), ]); console.log('chats.map', sortedChats); const keyboard = chooseChatKeyboard(sortedChats); console.log('chooseChatKeyboard', keyboard); await ctx.reply(stringToMarkdownV2(ua.RESULT_TEXT.CHATS.GET_BUTTONS_KEYBOARD), { reply_markup: keyboard, parse_mode: 'MarkdownV2', }); };

Last crash with more logs: image

Overall code execution ``` typescript const bot = new Bot(env.TELEGRAM_BOT_TOKEN); bot.use( session({ initial() { // return empty object for now return {}; }, }), ); bot.hears(ua.HEARS.CHATS.ADD, chatsAddHears); /** import { getTDLibInstanceDB } from '../../db/memory/tdlib-instances.ts'; */ import { TDLibInstance } from '../../tdl/main.ts'; const instances = new Map(); export const addTDLibInstanceDB = (telegramId: number, instance: TDLibInstance): void => { console.log('addTDLibInstanceDB'); instances.set(telegramId, instance); }; export const getTDLibInstanceDB = (telegramId: number): TDLibInstance | undefined => { console.log('getTDLibInstanceDB'); return instances.get(telegramId); }; /** getChatsWithInfo */ async function getChatsWithInfo(client: tdlClient, limit?: number): Promise { const chats = await getChats(client, limit); console.log('getChatsWithInfo', chats); const chatsWithInfo = []; for (const chatId of chats.chat_ids) { const chat = await getChat(client, chatId); chatsWithInfo.push(chat); } return chatsWithInfo; } /** import { chatsKeyboard, chooseChatKeyboard } from '../keyboards/chats.ts'; */ import { InlineKeyboard, Keyboard } from 'grammy'; import constantsConfig from '../../config/constants.config.ts'; import ua from '../../config/locales/ua.ts'; export const chatsKeyboard = new Keyboard() .text(ua.HEARS.CHATS.ADD) .row() .text(ua.HEARS.CHATS.LIST) .row() .text(ua.HEARS.CHATS.REMOVE) .row() .text(ua.HEARS.BACK_TO_MAIN_MENU) .resized(); /** * const labelDataPairs = [ ['« 1', 'first'], ['‹ 3', 'prev'], ['· 4 ·', 'stay'], ['5 ›', 'next'], ['31 »', 'last'], ]; * * Limit is 100 buttons in telegram * * @param labelDataPairs array of buttons where label is what user see and data is what you will get on user's callback. For label better user Name of the Chat and for Data ChatId * @returns InlineKeyboard that you can reply to user */ export const chooseChatKeyboard = (labelDataPairs: string[][]): InlineKeyboard => { const buttonRow = labelDataPairs.map(([label, data]) => [InlineKeyboard.text(label, data)]); console.debug('buttons.length', buttonRow.length); if (buttonRow.length <= constantsConfig.TDLIB.REPLY_MARKUP_LIMIT_BUTTONS) { return InlineKeyboard.from(buttonRow); } return InlineKeyboard.from( buttonRow.slice(0, constantsConfig.TDLIB.REPLY_MARKUP_LIMIT_BUTTONS - 1), ); } ```
Roma-Kyrnis commented 7 months ago

I understand maybe it's a problem with TDL, but why grammy stops to respond?

KnorpelSenf commented 7 months ago

Do you use several instances of TDLib?

Roma-Kyrnis commented 7 months ago

Yes, There are many users that connects to this bot (connect to TDLib through this bot)

KnorpelSenf commented 7 months ago

That does not mean that you need several instances of TDLib. A bot isn't allowed to connect to different instances. You first need to explicitly log out of one and then start contacting another instance. Otherwise, updates may get lost.

TDLib can easily handle 500,000,000 messages per day with a single instance. If you don't have substantially more traffic than that, then your setup is overkill (=harmful). Most likely, you should only be using a single instance..

Roma-Kyrnis commented 7 months ago

If I'll use one TDLib instance I need to login different users.

Maybe you can recommend a topic what to read?

If I need to send message, receive chats for specific user than I need to use telegram ID on this instance, as I understand it.

image

I found this PR, it recommends to create a lot of instances.

I understand you try to help. Can you point me where to look?

KnorpelSenf commented 7 months ago

It has nothing to do with bots.

What are you even trying to do?

Roma-Kyrnis commented 7 months ago

I make an application to send regular messages from different accounts to the chats, like ads or notifications.

I've tested that TDLib is still receiving updates when bot stops to work. But not is not responding on other calls, bot is not calling getUpdates function to pull messages from Telegram. That's why I'm here

I want to test why it happens and solve the issue

Could you help?

KnorpelSenf commented 7 months ago

So your bot doesn't actually use the TDLib instances? In other words, your bot still connects to api.telegram.org?

Usually, if the bot stops, that's because a handler never completes. Do you have any handlers that could take a very long time, potentially forever?

Roma-Kyrnis commented 7 months ago

Yes, I send a request to get users chats.

I'm using "tdl" npm lib. User enter app_ia and app_hash etc. And then I can send messages from a users account to chats

Thank you! I think that the issue. But I don't get the logic here: bot waits until long request to get Chats from TDLib for example, but never receive. Does not telegram bot should work asynchronous and handle other requests while this one is processing?

Oh, maybe it's because you run a loop and wait for getUpdate function to resolve. And while you waiting you cannot send a new getUpdate?

I've seen this behaviour on the tdl lib: Here https://github.com/Bannerets/tdl In packages/tdl/index There is a loop for processing long pull requests to Telegram

I didn't quite catch you when you said: 'connect to telegram.com'. Does TDLib and Telegram bot both connecting to telegram servers?

KnorpelSenf commented 7 months ago

By default, grammY handles all updates in sequence. This is enough for 90 % of bots, and it leads to very simple and predictable behaviour. For example, in your case, you were able to find out that a handler takes way too much time. In a concurrent setup, this would have been much harder to detect, and your bot would probably just randomly OOM after a few weeks. Now you know, so you can fix it.

If you want to run your bot concurrently at a later point, you can use grammY runner.

TDLib is used internally by the Bot API server. You can read more in the docs. https://grammy.dev/guide/api

Roma-Kyrnis commented 7 months ago

Thank you! I've got the point what I need to change ;)

I'll read the docs.

Roma-Kyrnis commented 7 months ago

Have you thought about implementing the user's TDLib methods in Grammy?

KnorpelSenf commented 7 months ago

That is not possible. grammY is not an MTProto client. It's a completely different API and you'll need to use a completely different library for that, such as https://github.com/MTKruto/MTKruto

Roma-Kyrnis commented 7 months ago

Okay, thank you for the quick response and for the library creation)

May I join in implementing new features in the future, maybe in 1-2 months? I'm not sure if I will have time... Just interesting in this topic: https://github.com/grammyjs/grammY/issues/110

KnorpelSenf commented 7 months ago

Absolutely! Feel free to drop a message to https://t.me/grammyjs if you have questions or need advice. Looking forward to your contributions!

awohsen commented 7 months ago

Have you thought about implementing the user's TDLib methods in Grammy?

its possible via https://github.com/tdlight-team/tdlight-telegram-bot-api , they offer user MTProto methods over bot api - means you can use grammy to interact w/ user account. However you need to change api endpoints from /bot[token] to /user[token]

Read their docs for more information. I've been using it for few months and as long as its updated its all good.