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")
Exemple #2
0
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")
Exemple #3
0
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")
Exemple #4
0
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
Exemple #5
0
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