def handle_chosen_inline_result(bot, update, session, user): """Save the chosen inline result.""" result = update.chosen_inline_result poll_id = result.result_id try: poll = session.query(Poll).get(poll_id) except DataError: # Possile if the poll has been shared too often and # the inline result is picked despite saying otherwise. return if result.inline_message_id is None: return try: reference = Reference( poll, ReferenceType.inline.name, inline_message_id=result.inline_message_id, ) session.add(reference) session.commit() except (UniqueViolation, IntegrityError): # I don't know how this can happen, but it happens. # It seems that user can spam click inline query, which then leads to # multiple chosen_inline_result queries being sent to the bot. session.rollback() return update_reference(session, bot, poll, reference) increase_user_stat(session, user, "inline_shares")
def handle_chosen_inline_result(bot, update, session, user): """Save the chosen inline result.""" result = update.chosen_inline_result poll_id = result.result_id try: poll = session.query(Poll).get(poll_id) except DataError: # Possile if the poll has been shared too often and # the inline result is picked despite saying otherwise. return try: reference = Reference( poll, ReferenceType.inline.name, inline_message_id=result.inline_message_id, ) session.add(reference) session.commit() except UniqueViolation: # I don't know how this can happen, but it happens. return update_reference(session, bot, poll, reference) increase_user_stat(session, user, "inline_shares")
def create_poll(session, poll, user, chat, message=None): """Finish the poll creation.""" poll.created = True user.expected_input = None user.current_poll = None text = get_poll_text(session, poll) if len(text) > 4000: error_message = i18n.t("misc.over_4000", locale=user.locale) message = chat.send_message(error_message, parse_mode="markdown") session.delete(poll) return if message: message = message.edit_text( text, parse_mode="markdown", reply_markup=get_management_keyboard(poll), disable_web_page_preview=True, ) else: message = chat.send_message( text, parse_mode="markdown", reply_markup=get_management_keyboard(poll), disable_web_page_preview=True, ) if len(text) > 3000: error_message = i18n.t("misc.over_3000", locale=user.locale) message = chat.send_message(error_message, parse_mode="markdown") reference = Reference(poll, ReferenceType.admin.name, user=user, message_id=message.message_id) session.add(reference) session.flush() increase_stat(session, "created_polls") increase_user_stat(session, user, "created_polls")
def handle_callback_query(bot, update, session, user): """Handle synchronous callback queries. Some critical callbacks shouldn't be allowed to be asynchronous, since they tend to cause race conditions and integrity errors in the database schema. That's why some calls are restricted to be synchronous. """ context = get_context(bot, update, session, user) increase_user_stat(session, context.user, "callback_calls") session.commit() response = callback_mapping[context.callback_type](session, context) # Callback handler functions always return the callback answer # The only exception is the vote function, which is way too complicated and # implements its own callback query answer logic. if response is not None and context.callback_type != CallbackType.vote: context.query.answer(response) else: context.query.answer("") increase_stat(session, "callback_calls") return
def handle_async_callback_query(bot, update, session, user): """Handle asynchronous callback queries. Most callback queries are unproblematic in terms of causing race-conditions. Thereby they can be handled asynchronously. However, we do handle votes asynchronously as an edge-case, since we want those calls to be handled as fast as possible. The race condition handling for votes is handled in the respective `handle_vote` function. """ context = get_context(bot, update, session, user) # Vote logic needs some special handling if context.callback_type == CallbackType.vote: option = session.query(Option).get(context.payload) if option is None: return poll = option.poll # Ensure user statistics exist for this poll owner # We need to track at least some user activity, since there seem to be some users which # abuse the bot by creating polls and spamming up to 1 million votes per day. # # I really hate doing this, but I don't see another way to prevent DOS attacks # without tracking at least some numbers. user_statistic = session.query(UserStatistic).get( (date.today(), poll.user.id)) if user_statistic is None: user_statistic = UserStatistic(poll.user) session.add(user_statistic) try: session.commit() # Handle race condition for parallel user statistic creation # Return the statistic that has already been created in another session except IntegrityError as e: session.rollback() user_statistic = session.query(UserStatistic).get( (date.today(), poll.user.id)) if user_statistic is None: raise e # Increase stats before we do the voting logic # Otherwise the user might dos the bot by triggering flood exceptions # before actually being able to increase the stats increase_user_stat(session, context.user, "votes") increase_user_stat(session, poll.user, "poll_callback_calls") session.commit() response = handle_vote(session, context, option) else: increase_user_stat(session, context.user, "callback_calls") session.commit() response = async_callback_mapping[context.callback_type](session, context) # Callback handler functions always return the callback answer # The only exception is the vote function, which is way too complicated and # implements its own callback query answer logic. if response is not None and context.callback_type != CallbackType.vote: context.query.answer(response) else: context.query.answer("") increase_stat(session, "callback_calls") return