-
Notifications
You must be signed in to change notification settings - Fork 5.7k
[FEATURE] Automatic dismiss repeated callbackquery updates #2539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hi. I didn't follow the discussion in the user group about this too closely, but you did a nice summary and I see the use case 🙂 I'd like to add two things to this discussion:
Workable approach:Basic idea: have the handler make the decision. That a rather clean design IMO and this should also be pretty safe for Click to expand MWE
from collections import defaultdict
from typing import Optional, Union, Tuple, DefaultDict
from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
from telegram.ext import Updater, CommandHandler, CallbackQueryHandler, CallbackContext
import logging
# Enable logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
)
logger = logging.getLogger(__name__)
class CustomCQH(CallbackQueryHandler):
handled_queries: DefaultDict[
Tuple[Optional[int], Optional[int], Optional[int], str], bool
] = defaultdict(bool)
def check_update(self, update: object) -> Optional[Union[bool, object]]:
if super().check_update(update):
assert isinstance(update, Update)
chat_id = update.effective_chat.id if update.effective_chat else None
message_id = update.effective_message.message_id if update.effective_message else None
inline_message_id = update.callback_query.inline_message_id
data = update.callback_query.data
key = (chat_id, message_id, inline_message_id, data)
if self.handled_queries[key]:
# Button was already handled
logger.info('Ignoring CallbackQuery because the button was already handled.')
return False
else:
# Mark query as handled
self.handled_queries[key] = True
return True
return False
def start(update: Update, _: CallbackContext) -> None:
update.message.reply_text(
update.message.text,
reply_markup=InlineKeyboardMarkup.from_button(
InlineKeyboardButton('Click me', callback_data='data')
)
)
def button(update: Update, _: CallbackContext) -> None:
update.callback_query.answer()
update.effective_message.edit_text('This button was already handled.')
def main() -> None:
updater = Updater("TOKEN")
dispatcher = updater.dispatcher
dispatcher.add_handler(CommandHandler("start", start))
dispatcher.add_handler(CustomCQH(button, run_async=True))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
Arguments agains adding this to PTBWhile I do see that this would be useful for working with inline buttons in general, I fell uncomfortable with adding this to PTB - at least as a first impression. My reasons:
I would therefore suggest to move this to ptbcontrib, where we don't have to be as careful about what the user understands and what they don't. Maybe I'll be thinking differently about this in a week, but that's my thoughts for today :) |
I dont think this will work. Your MWE doesn't include the corresponding API call to update the key. If we introduce this, which is a very low level approach, we can completely mitigate your second point. But if we do this, we can't fully say if an update needs to be handled or not
Or we introduce code in the ext.bot to actually include this in our expected key list. This might just require the user to link the method to the handler. then we should probably really put it in contrib. If we can do without this, I would vote to add it to main though. |
You want to only dismiss doublicate callback queries, if the callback contains a
Sorry, I can't follow your here. Iclude what in which key? Link which method to which handler, and what does "link" mean here? |
After a bit in depth internal discussion with @Bibo-Joshi, the idea on moving forward with this issue is the following:
|
Almost forgot to comment this: With arbitrary callback data, you can now already cover the non-async case: python-telegram-bot/examples/arbitrarycallbackdatabot.py Lines 56 to 73 in e982a5a
Line 73 makes sure that the second incoming callback query will have |
Update: We have plans to allow for updates to be processed asynchronously in v14, so moving this back to Stage 3 … |
It's been a while since we last discussed this issue and quite a bit has changed. In particular, we now have new mechanism of corcurrent update handling in v20, also now with custom UpdateProcessors (#3654). A few thoughts that I now have on this topic: IMO two very crucial parts of making a feature like this workable are
With aribtray callback data, we actually already have a solution for both of these things in place and it would even be possible to re-use that for this purpose quite easily. We could add a parameter
|
Yet more additional thoughts:
|
As there is no profound interest in this feature in the dev team at the moment and the technical realization is still rather unclear, I'm closing this issue as won't-do for now. If interest spikes in the future, we can revisit. |
Is your feature request related to a problem? Please describe.
Clients allow users to spam button presses. This leads to race conditions where the code edited the reply_markup and then tries to edit again even though it was already edited because several updates resulted from the same button.
Describe the solution you'd like
I would like to have the library handle these race conditions with some simple code checks and not propagate these repeated updates to the handlers.
Doing this for non async handlers is very easy. All this library needs to store is the callback_data from the first inline button send/edit_message request, and then the callback_data from the next one or a deleted flag if the reply_markup is removed; all this valued to a unique key from chat+message_id or inline_message_id. If then an update happens which has the first callback_data even though the next one is expected, or another callbackquery update happens when the delete flag is set, the query should be answered (for this, maybe the answer value should be stored as well) and then the update dismissed.
I am not sure how to handle this for async handlers, but I think we could try and apply the same logic, and warn in the docs that it can just happen that two Updates slip through. I hope someone here can think of a better way to handle this. One alternative would be to not handle a multiple update with the same callback_data from the unique key. But that could lead to an issue where something else in the code breaks temporarily, and a user isn't able to try hitting the button again after some time when that issue is resolved. Maybe we can work with try expect for that?
Describe alternatives you've considered
This can be easily implemented client side, and with minimal coding effort with a decorator. I vote against this for two reasons: I would need to do this for every callbackqueryhandler manually, and I mostly think beginners will overlook this issue. Then they are facing errors they cant understand because no one thinks about spamming a button that fast.
The text was updated successfully, but these errors were encountered: