tdlib / telegram-bot-api

Telegram Bot API server
https://core.telegram.org/bots
Boost Software License 1.0
3.08k stars 588 forks source link

Is it possible for Telegram to accept an edit but still return `MESSAGE_NOT_MODIFIED`? #624

Open nmlorg opened 1 month ago

nmlorg commented 1 month ago

I'm a little stumped, please bear with me.

I have a bot that sent a message to a group yesterday at 7 a.m. US/Pacific, then edited it successfully at 12:10 this morning, but failed at 12:20 with a MESSAGE_NOT_MODIFIED:

2024-08-08 07:00:13,533 INFO reminders.py:161] Sending reminder to -1001xxx.
⋮
2024-08-09 00:10:06,885 INFO reminders.py:185] Editing reminder -1001xxx/1991.
⋮
2024-08-09 00:20:12,006 INFO reminders.py:185] Editing reminder -1001xxx/1991.
2024-08-09 00:20:14,872 ERROR reminders.py:199] While editing 1991 in -1001xxx:
There are a bunch of events coming up:

⋮
Traceback (most recent call last):
  File "lib/python3.10/site-packages/metabot/modules/reminders.py", line 188, in reminder_edit
    return bot.edit_message_caption(chat_id=groupid,
  File "lib/python3.10/site-packages/ntelebot/bot.py", line 71, in __call__
    raise ntelebot.errors.Error(data)
ntelebot.errors.Error: {'ok': False, 'error_code': 400, 'description': 'Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message'}

  I don't have a record of what it [thinks it] sent at 12:10, but it includes a countdown to an event that took place at 4 p.m. today, and that message 1991 currently includes the string "¹⁵ʰ⁴⁰ᵐ", which is what it tried to send at 12:20. The only way I can think of for that to happen is if it sent the wrong countdown at 12:10 somehow but still recorded the correct one somehow:

00:10 (15h50m before the event)   send "15h40m"   record that it sent "15h50m" 00:20 (15h40m before the event)   send "15h40m"     error

  I've tried to figure out where MESSAGE_NOT_MODIFIED comes from, but haven't gotten very far.

Is it possible the 12:20 edit did actually go through, but Telegram returned MESSAGE_NOT_MODIFIED anyway?

If so, is there any way to confirm that that happened in this case?

nmlorg commented 1 month ago

I was just preparing to push a fix for a bug in my code that's triggered by this situation, and it seems the situation just happened again; same bot, but different group:

2024-08-09 19:00:11,830 ERROR reminders.py:199] While editing 104113 in -1001xxx:
There are a bunch of events coming up:

… ¹⁵ ʰᵒᵘʳˢ Sat 10ᵗʰ, 12–2ᵖᵐ …
ntelebot.errors.Error: {'ok': False, 'error_code': 400, 'description': 'Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message'}

The message in question does currently say "¹⁵ ʰᵒᵘʳˢ Sat 10ᵗʰ, 12–2ᵖᵐ".

levlam commented 1 month ago

The error is returned by the Telegram servers. They compare the current message content and the new content provided by the bot. If they match, then the request is aborted and edit isn't performed. But you may handle the error as if the edit succeeded, because content of the message is already exactly as you have requested to be. The likely cause of the issue is the request sent twice in parallel by the bot, one of which succeeds, and one of which fails with the error.

nmlorg commented 1 month ago

When Google Calendar first came out, a tool I wrote used to create duplicate events fairly often. Eventually it was determined that I would send a single "create" request to the API servers, then the API servers would send an RPC to create the event, and then a separate RPC for each attendee (I believe something to do with creating reminders). If any of the per-attendee RPCs failed, even transiently, the API server just immediately returned a 500 — it didn't roll back the event creation, but also didn't return the new event's id, so it existed (at least partially) but was completely orphaned.

  1. my app: → create {"summary": "cool event", "attendees": ["alpha@example.com", "bravo@example.com"], "start": 1234}
    1. GC: → create {"summary": "cool event", "start": 1234}
      1. DS: ← created {"id": "aaaaa"}
    2. GC: → add {"id": "aaaaa"} to "alpha@example.com"
      1. DS: ← done!
    3. GC: → add {"id": "aaaaa"} to "bravo@example.com"
      1. DS: ← no
    4. GC: ← 500
  2. my app: → create {"summary": "cool event", "attendees": ["alpha@example.com", "bravo@example.com"], "start": 1234}
    1. GC: → create {"summary": "cool event", "start": 1234}
      1. DS: ← created {"id": "bbbbb"}
    2. GC: → add {"id": "bbbbb"} to "alpha@example.com"
      1. DS: ← done!
    3. GC: → add {"id": "bbbbb"} to "bravo@example.com"
      1. DS: ← done!
    4. GC: ← created {"id": "bbbbb"}

I had to run a separate job once a week that downloaded the entirety of every calendar I managed and just deleted any events that weren't in my cache. They switched data stores a few years later and the problem went away (I think the new DS allowed them to run the whole create operation inside a transaction).


Anyway, TBA has been so reliable for the past 7 years I haven't bothered to add any kind of retry logic, and I haven't been seeing any "conflict" errors (that would have suggested multiple instances of the bot were running somehow). Is it possible "api.telegram.org" is forwarding my request to something else, getting maybe a network error, and resending the request on my behalf?

I don't send edit requests unless the copy of the message in my cache is different from what the message is [now] supposed to be. So if I send an edit, and the API server tells me the content hasn't changed, that means the copy in my cache is incorrect. There's no way to retrieve a message by id, right? I suppose I could editMessageText(text='dummy') and then resend the editMessageText(text=newtext) (or maybe forwardMessage it to a dummy location) just to get a copy of the current message, or maybe even rework my bot so it doesn't need a copy of the current message in its cache, but I guess now I'm wondering if there may be situations where other API calls (including sendMessage) might mutate Telegram but return a failure to me.

levlam commented 3 weeks ago

Is it possible "api.telegram.org" is forwarding my request to something else, getting maybe a network error, and resending the request on my behalf?

The chance of this is extremely low, so I would say that it is impossible. It is much more likely that the request to api.telegram.org is retried because of a network error.

If you receive "message is not modified" error, then message content is already as you want it to be. You must not do anything additionally with the message.