Ejemplo n.º 1
0
def search(bot, update, session, user):
    """Handle inline queries for sticker search."""
    query = update.inline_query.query.strip()

    # Also search for closed polls if the `closed_polls` keyword is found
    closed = False
    if 'closed_polls' in query:
        closed = True
        query = query.replace('closed_polls', '').strip()

    if query == '':
        # Just display all polls
        polls = session.query(Poll) \
            .filter(Poll.user == user) \
            .filter(Poll.closed.is_(closed)) \
            .filter(Poll.created.is_(True)) \
            .order_by(Poll.created_at.desc()) \
            .all()

    else:
        # Find polls with search parameter in name or description
        polls = session.query(Poll) \
            .filter(Poll.user == user) \
            .filter(Poll.closed.is_(closed)) \
            .filter(Poll.created.is_(True)) \
            .filter(or_(
                Poll.name.ilike(f'%{query}%'),
                Poll.description.ilike(f'%{query}%'),
            )) \
            .order_by(Poll.created_at.desc()) \
            .all()

    if len(polls) == 0:
        update.inline_query.answer(
            [],
            cache_time=0,
            is_personal=True,
            switch_pm_text=i18n.t('inline_query.create_first',
                                  locale=user.locale),
            switch_pm_parameter='inline',
        )
    else:
        results = []
        for poll in polls:
            text, keyboard = get_poll_text_and_vote_keyboard(
                session, poll, show_warning=False)
            if poll.closed:
                keyboard = InlineKeyboardMarkup([[
                    InlineKeyboardButton('Please ignore this',
                                         callback_data='100')
                ]])

            content = InputTextMessageContent(
                text,
                parse_mode='markdown',
                disable_web_page_preview=True,
            )
            results.append(
                InlineQueryResultArticle(
                    poll.id,
                    poll.name,
                    description=poll.description,
                    input_message_content=content,
                    reply_markup=keyboard,
                ))

        update.inline_query.answer(results,
                                   cache_time=0,
                                   is_personal=True,
                                   switch_pm_text=i18n.t(
                                       'inline_query.create_poll',
                                       locale=user.locale),
                                   switch_pm_parameter='inline')
Ejemplo n.º 2
0
def set_previous_month(session, context, poll):
    """Show to vote type keyboard."""
    poll.current_date -= relativedelta(months=1)
    update_datepicker(context, poll)
    context.query.answer(i18n.t('callback.date_changed', locale=poll.locale,
                                date=poll.current_date.isoformat()))
Ejemplo n.º 3
0
    def wrapper(bot, update, session, user):
        if user.username.lower() != config["telegram"]["admin"].lower():
            return i18n.t("admin.not_allowed", locale=user.locale)

        return function(bot, update, session, user)
Ejemplo n.º 4
0
def show_deletion_confirmation(session, context, poll):
    """Show the delete confirmation message."""
    context.query.message.edit_text(
        i18n.t('management.delete', locale=poll.user.locale),
        reply_markup=get_deletion_confirmation(poll),
    )
Ejemplo n.º 5
0
def send_error_quiz_unsupported(update: Update, _context):
    update.effective_chat.send_message(
        i18n.t("creation.native_poll.quiz_unsupported"))
Ejemplo n.º 6
0
def get_back_to_init_button(poll):
    """Get the button to go back to the init creation message."""
    back_text = i18n.t("keyboard.back", locale=poll.locale)
    anonymity_payload = f"{CallbackType.back_to_init.value}:{poll.id}:0"
    return InlineKeyboardButton(back_text, callback_data=anonymity_payload)
Ejemplo n.º 7
0
def get_back_to_settings_button(user):
    """Get the back to options settings button for settings sub menus."""
    payload = f"{CallbackType.user_settings.value}:0:0"
    return InlineKeyboardButton(text=i18n.t("keyboard.back",
                                            locale=user.locale),
                                callback_data=payload)
Ejemplo n.º 8
0
    def wrapper(session, context):
        if context.poll is None:
            return i18n.t('callback.poll_no_longer_exists',
                          locale=context.user.locale)

        return function(session, context, context.poll)
Ejemplo n.º 9
0
def change_user_language(session, context):
    """Open the language picker."""
    context.user.locale = context.action
    session.commit()
    open_user_settings(session, context)
    return i18n.t("user.language_changed", locale=context.user.locale)
Ejemplo n.º 10
0
def get_back_to_management_button(poll):
    """Get the back to management menu button for management sub menus."""
    locale = poll.user.locale
    payload = f'{CallbackType.menu_back.value}:{poll.id}:{CallbackResult.main_menu.value}'
    return InlineKeyboardButton(i18n.t('keyboard.back', locale=locale),
                                callback_data=payload)
Ejemplo n.º 11
0
def get_management_keyboard(poll):
    """Get the management keyboard for this poll."""
    locale = poll.user.locale
    delete_payload = f'{CallbackType.menu_delete.value}:{poll.id}:0'

    if poll.closed and not poll.results_visible:
        return InlineKeyboardMarkup([[
            InlineKeyboardButton(i18n.t('keyboard.delete', locale=locale),
                                 callback_data=delete_payload)
        ]])
    elif poll.closed:
        reopen_payload = f'{CallbackType.reopen.value}:{poll.id}:0'
        reset_payload = f'{CallbackType.reset.value}:{poll.id}:0'
        clone_payload = f'{CallbackType.clone.value}:{poll.id}:0'
        buttons = [[
            InlineKeyboardButton(i18n.t('keyboard.reset', locale=locale),
                                 callback_data=reset_payload),
            InlineKeyboardButton(i18n.t('keyboard.clone', locale=locale),
                                 callback_data=clone_payload),
        ],
                   [
                       InlineKeyboardButton(i18n.t('keyboard.delete',
                                                   locale=locale),
                                            callback_data=delete_payload)
                   ],
                   [
                       InlineKeyboardButton(i18n.t('keyboard.reopen',
                                                   locale=locale),
                                            callback_data=reopen_payload)
                   ]]
        return InlineKeyboardMarkup(buttons)

    vote_payload = f'{CallbackType.menu_vote.value}:{poll.id}:0'
    option_payload = f'{CallbackType.menu_option.value}:{poll.id}:0'
    delete_payload = f'{CallbackType.menu_delete.value}:{poll.id}:0'

    if poll.results_visible:
        close_payload = f'{CallbackType.close.value}:{poll.id}:0'
    else:
        close_payload = f'{CallbackType.menu_close.value}:{poll.id}:0'

    buttons = [
        [
            InlineKeyboardButton(i18n.t('keyboard.share', locale=locale),
                                 switch_inline_query=poll.name),
        ],
        [
            InlineKeyboardButton(i18n.t('keyboard.vote', locale=locale),
                                 callback_data=vote_payload),
            InlineKeyboardButton(i18n.t('keyboard.settings', locale=locale),
                                 callback_data=option_payload),
        ],
        [
            InlineKeyboardButton(i18n.t('keyboard.delete', locale=locale),
                                 callback_data=delete_payload),
            InlineKeyboardButton(i18n.t('keyboard.close_poll', locale=locale),
                                 callback_data=close_payload),
        ],
    ]

    return InlineKeyboardMarkup(buttons)
Ejemplo n.º 12
0
def start(bot, update, session, user):
    """Send a start text."""
    # Truncate the /start command
    text = update.message.text[6:].strip()
    user.started = True

    try:
        poll_uuid = UUID(text.split('-')[0])
        action = StartAction(int(text.split('-')[1]))

        poll = session.query(Poll).filter(Poll.uuid == poll_uuid).one()
    except:
        text = ''

    main_keyboard = get_main_keyboard()
    # We got an empty text, just send the start message
    if text == '':
        update.message.chat.send_message(
            i18n.t('misc.start', locale=user.locale),
            parse_mode='markdown',
            reply_markup=main_keyboard,
            disable_web_page_preview=True,
        )

        return

    if poll is None:
        return 'This poll no longer exists.'

    if action == StartAction.new_option:
        # Update the expected input and set the current poll
        user.expected_input = ExpectedInput.new_user_option.name
        user.current_poll = poll
        session.commit()

        update.message.chat.send_message(
            i18n.t('creation.option.first', locale=poll.locale),
            parse_mode='markdown',
            reply_markup=get_external_add_option_keyboard(poll)
        )
    elif action == StartAction.show_results:
        # Get all lines of the poll
        lines = compile_poll_text(session, poll)
        # Now split the text into chunks of max 4000 characters
        chunks = split_text(lines)

        for chunk in chunks:
            message = '\n'.join(chunk)
            try:
                update.message.chat.send_message(
                    message,
                    parse_mode='markdown',
                    disable_web_page_preview=True,
                )
            # Retry for Timeout error (happens quite often when sending large messages)
            except TimeoutError:
                time.sleep(2)
                update.message.chat.send_message(
                    message,
                    parse_mode='markdown',
                    disable_web_page_preview=True,
                )
            time.sleep(1)

        update.message.chat.send_message(
            i18n.t('misc.start_after_results', locale=poll.locale),
            parse_mode='markdown',
            reply_markup=main_keyboard,
        )
        increase_stat(session, 'show_results')

    elif action == StartAction.share_poll:
        update.message.chat.send_message(
            i18n.t('external.share_poll', locale=poll.locale),
            reply_markup=get_external_share_keyboard(poll)
        )
        increase_stat(session, 'externally_shared')
Ejemplo n.º 13
0
def get_poll_text(session, poll, show_warning):
    """Create the text of the poll."""
    total_user_count = session.query(User.id) \
        .join(Vote) \
        .join(PollOption) \
        .filter(PollOption.poll == poll) \
        .group_by(User.id) \
        .count()

    # Name and description
    lines = []
    lines.append(f'✉️ *{poll.name}*')
    if poll.description is not None:
        lines.append(f'_{poll.description}_')

    # Anonymity information
    if not poll.results_visible and not poll.should_show_result() or \
            poll.anonymous and not poll.closed:
        lines.append('')
    if poll.anonymous and not poll.closed:
        lines.append(f"_{i18n.t('poll.anonymous', locale=poll.locale)}_")
    if not poll.results_visible and not poll.should_show_result():
        lines.append(
            f"_{i18n.t('poll.results_not_visible', locale=poll.locale)}_")

    # Sort the options accordingly to the polls settings
    options = get_sorted_options(poll, total_user_count)

    # All options with their respective people percentage
    for index, option in enumerate(options):
        lines.append('')
        lines.append(get_option_line(session, option, index))
        if option.description is not None:
            lines.append(f'┆ _{option.description}_')

        if poll.should_show_result() and poll.show_percentage:
            lines.append(get_percentage_line(option, total_user_count))

        # Add the names of the voters to the respective options
        if poll.should_show_result() and not poll.anonymous and len(
                option.votes) > 0:
            # Sort the votes accordingly to the poll's settings
            votes = get_sorted_votes(poll, option.votes)
            for index, vote in enumerate(votes):
                vote_line = get_vote_line(poll, option, vote, index)
                lines.append(vote_line)
    lines.append('')

    if poll_has_limited_votes(poll):
        lines.append(
            i18n.t('poll.vote_times',
                   locale=poll.locale,
                   amount=poll.number_of_votes))

    # Total user count information
    information_line = get_vote_information_line(poll, total_user_count)
    if information_line is not None:
        lines.append(information_line)

    if not poll.anonymous:
        remaining_votes = get_remaining_votes(session, poll)
        lines += remaining_votes

    if poll.due_date is not None:
        lines.append(
            i18n.t('poll.due',
                   locale=poll.locale,
                   date=poll.get_formatted_due_date()))

    # Notify users that poll is closed
    if poll.closed:
        lines.append(i18n.t('poll.closed', locale=poll.locale))

    if show_warning:
        lines.append(i18n.t('poll.too_many_votes', locale=poll.locale))

    return '\n'.join(lines)
Ejemplo n.º 14
0
    def wrapper(bot, update, session, user):
        if user.username.lower() != config['telegram']['admin'].lower():
            return(i18n.t('admin.not_allowed', locale=user.locale))

        return function(bot, update, session, user)
Ejemplo n.º 15
0
def search(bot, update, session, user):
    """Handle inline queries for sticker search."""
    query = update.inline_query.query.strip()

    # Also search for closed polls if the `closed_polls` keyword is found
    closed = False
    if "closed_polls" in query:
        closed = True
        query = query.replace("closed_polls", "").strip()

    offset = update.inline_query.offset

    if offset == "":
        offset = 0
    elif offset == "Done":
        update.inline_query.answer(
            [],
            cache_time=0,
            is_personal=True,
        )
        return
    else:
        offset = int(offset)

    if query == "":
        # Just display all polls
        polls = (session.query(Poll).filter(Poll.user == user).filter(
            Poll.closed.is_(closed)).filter(Poll.created.is_(True)).order_by(
                Poll.created_at.desc()).limit(10).offset(offset).all())

    else:
        # Find polls with search parameter in name or description
        polls = (session.query(Poll).filter(Poll.user == user).filter(
            Poll.closed.is_(closed)).filter(Poll.created.is_(True)).filter(
                or_(
                    Poll.name.ilike(f"%{query}%"),
                    Poll.description.ilike(f"%{query}%"),
                )).order_by(
                    Poll.created_at.desc()).limit(10).offset(offset).all())

    # Try to find polls that are shared by external people via uuid
    if len(polls) == 0 and len(query) == 36:
        try:
            poll_uuid = uuid.UUID(query)
            poll = (session.query(Poll).filter(
                Poll.uuid == poll_uuid).offset(offset).one_or_none())

            if poll is not None:
                # Check if sharin is enabled
                # If not, check if the owner issued the query
                if not poll.allow_sharing and user != poll.user:
                    polls = []
                else:
                    polls = [poll]

        except ValueError:
            pass

    if len(polls) == 0:
        update.inline_query.answer(
            [],
            cache_time=0,
            is_personal=True,
        )
    else:
        results = []
        for poll in polls:
            inline_reference_count = 0
            for reference in poll.references:
                if reference.type == ReferenceType.inline.name:
                    inline_reference_count += 1

            max_share_amount = config["telegram"]["max_inline_shares"]
            if inline_reference_count > max_share_amount:
                text = i18n.t("poll.shared_too_often",
                              locale=poll.locale,
                              amount=max_share_amount)
                results.append(
                    InlineQueryResultArticle(
                        uuid.uuid4(),
                        poll.name,
                        description=text,
                        input_message_content=InputTextMessageContent(text),
                    ))
                continue

            text = i18n.t("poll.please_wait", locale=poll.locale)
            keyboard = InlineKeyboardMarkup([[
                InlineKeyboardButton("Please ignore this",
                                     callback_data="100:0:0")
            ]])

            content = InputTextMessageContent(
                text,
                parse_mode="markdown",
                disable_web_page_preview=True,
            )
            description = (poll.description[:100]
                           if poll.description is not None else None)
            results.append(
                InlineQueryResultArticle(
                    poll.id,
                    poll.name,
                    description=description,
                    input_message_content=content,
                    reply_markup=keyboard,
                ))

        if len(polls) < 10:
            offset = "Done"
        else:
            offset + 10

        update.inline_query.answer(
            results,
            cache_time=0,
            is_personal=True,
            next_offset=str(offset),
        )
Ejemplo n.º 16
0
def delete_poll(session, context, poll):
    """Permanently delete the poll."""
    remove_poll_messages(session, context.bot, poll)
    session.delete(poll)
    session.commit()
    context.query.answer(i18n.t('callback.deleted', locale=poll.user.locale))
Ejemplo n.º 17
0
def delete_all(bot, update, session, user):
    """Delete all polls of the user."""
    for poll in user.polls:
        session.delete(poll)

    return i18n.t('deleted.polls', locale=user.locale)
Ejemplo n.º 18
0
def close_poll(session, context, poll):
    """Close this poll."""
    poll.closed = True
    session.commit()
    update_poll_messages(session, context.bot, poll)
    context.query.answer(i18n.t('callback.closed', locale=poll.user.locale))
Ejemplo n.º 19
0
def get_settings_keyboard(poll):
    """Get the options menu for this poll."""
    buttons = []
    locale = poll.user.locale
    # Anonymization
    if not poll.anonymous and not poll.is_priority():
        text = i18n.t("keyboard.anonymize", locale=locale)
        payload = (
            f"{CallbackType.settings_anonymization_confirmation.value}:{poll.id}:0"
        )
        buttons.append(
            [InlineKeyboardButton(text=text, callback_data=payload)])

    # Change language
    language_text = i18n.t("keyboard.change_language", locale=locale)
    language_payload = f"{CallbackType.settings_open_language_picker.value}:{poll.id}:0"
    buttons.append([
        InlineKeyboardButton(text=language_text,
                             callback_data=language_payload)
    ])

    # Open due date datepicker
    new_option_text = i18n.t("keyboard.due_date", locale=locale)
    new_option_payload = (
        f"{CallbackType.settings_open_due_date_datepicker.value}:{poll.id}:0")
    buttons.append([
        InlineKeyboardButton(text=new_option_text,
                             callback_data=new_option_payload)
    ])

    # Styling sub menu
    styling_text = i18n.t("keyboard.styling.open", locale=locale)
    styling_payload = f"{CallbackType.settings_show_styling.value}:{poll.id}:0"
    buttons.append([
        InlineKeyboardButton(text=styling_text, callback_data=styling_payload)
    ])

    # New option button
    new_option_text = i18n.t("keyboard.new_option", locale=locale)
    new_option_payload = f"{CallbackType.settings_new_option.value}:{poll.id}:0"
    buttons.append([
        InlineKeyboardButton(text=new_option_text,
                             callback_data=new_option_payload)
    ])

    # Remove options button
    new_option_text = i18n.t("keyboard.remove_option", locale=locale)
    new_option_payload = (
        f"{CallbackType.settings_show_remove_option_menu.value}:{poll.id}:0")
    buttons.append([
        InlineKeyboardButton(text=new_option_text,
                             callback_data=new_option_payload)
    ])

    # Allow user options button
    allow_new_option_text = i18n.t("keyboard.allow_user_options",
                                   locale=locale)
    if poll.allow_new_options:
        allow_new_option_text = i18n.t("keyboard.forbid_user_options",
                                       locale=locale)
    allow_new_option_payload = (
        f"{CallbackType.settings_toggle_allow_new_options.value}:{poll.id}:0")
    buttons.append([
        InlineKeyboardButton(text=allow_new_option_text,
                             callback_data=allow_new_option_payload)
    ])

    # Allow others to share the poll
    allow_sharing_text = i18n.t("keyboard.allow_sharing", locale=locale)
    if poll.allow_sharing:
        allow_sharing_text = i18n.t("keyboard.forbid_sharing", locale=locale)
    allow_sharing_payload = (
        f"{CallbackType.settings_toggle_allow_sharing.value}:{poll.id}:0")
    buttons.append([
        InlineKeyboardButton(text=allow_sharing_text,
                             callback_data=allow_sharing_payload)
    ])

    # Back button
    buttons.append([get_back_to_management_button(poll)])

    return InlineKeyboardMarkup(buttons)
Ejemplo n.º 20
0
def get_priority_buttons(poll, user):
    """Create the keyboard for priority poll. Only show the deeplink, if not in a direct conversation."""
    if user is None:
        bot_name = config['telegram']['bot_name']
        payload = get_start_button_payload(poll, StartAction.vote)
        url = f'http://t.me/{bot_name}?start={payload}'
        buttons = [[
            InlineKeyboardButton(i18n.t('keyboard.vote', locale=poll.locale),
                                 url=url)
        ]]

        return buttons

    buttons = []
    options = get_sorted_options(poll)
    vote_button_type = CallbackType.vote.value
    vote_increase = CallbackResult.increase_priority.value
    vote_decrease = CallbackResult.decrease_priority.value
    session = get_session()
    votes = session.query(Vote) \
        .filter(Vote.poll == poll) \
        .filter(Vote.user == user) \
        .order_by(Vote.priority.asc()) \
        .options(joinedload(Vote.poll_option)) \
        .all()

    indices = get_option_indices(options)
    for index, vote in enumerate(votes):
        option = vote.poll_option
        if not poll.compact_buttons:
            name_row = [
                InlineKeyboardButton(f"{option.name}",
                                     callback_data=IGNORE_PAYLOAD)
            ]
            buttons.append(name_row)
        name_hint_payload = f'{CallbackType.show_option_name.value}:{poll.id}:{option.id}'
        increase_payload = f'{vote_button_type}:{option.id}:{vote_increase}'
        decrease_payload = f'{vote_button_type}:{option.id}:{vote_decrease}'
        ignore_payload = f'{CallbackType.ignore.value}:0:0'

        vote_row = []
        if poll.compact_buttons:
            vote_row.append(
                InlineKeyboardButton(f"{indices[index]})",
                                     callback_data=name_hint_payload))

        if index != len(votes) - 1:
            vote_row.append(
                InlineKeyboardButton('▼', callback_data=decrease_payload))
        else:
            vote_row.append(
                InlineKeyboardButton(' ', callback_data=ignore_payload))

        if index != 0:
            vote_row.append(
                InlineKeyboardButton('▲', callback_data=increase_payload))
        else:
            vote_row.append(
                InlineKeyboardButton(' ', callback_data=ignore_payload))

        buttons.append(vote_row)
    return buttons
Ejemplo n.º 21
0
def search(bot, update, session, user):
    """Handle inline queries for sticker search."""
    query = update.inline_query.query.strip()

    # Also search for closed polls if the `closed_polls` keyword is found
    closed = False
    if 'closed_polls' in query:
        closed = True
        query = query.replace('closed_polls', '').strip()

    offset = update.inline_query.offset

    if offset == '':
        offset = 0
    else:
        offset = int(offset)

    if query == '':
        # Just display all polls
        polls = session.query(Poll) \
            .filter(Poll.user == user) \
            .filter(Poll.closed.is_(closed)) \
            .filter(Poll.created.is_(True)) \
            .order_by(Poll.created_at.desc()) \
            .limit(10) \
            .offset(offset) \
            .all()

    else:
        # Find polls with search parameter in name or description
        polls = session.query(Poll) \
            .filter(Poll.user == user) \
            .filter(Poll.closed.is_(closed)) \
            .filter(Poll.created.is_(True)) \
            .filter(or_(
                Poll.name.ilike(f'%{query}%'),
                Poll.description.ilike(f'%{query}%'),
            )) \
            .order_by(Poll.created_at.desc()) \
            .limit(10) \
            .offset(offset) \
            .all()

    # Try to find polls that are shared by external people via uuid
    if len(polls) == 0 and len(query) == 36:
        try:
            poll_uuid = uuid.UUID(query)
            polls = session.query(Poll) \
                .filter(Poll.uuid == poll_uuid) \
                .all()
        except ValueError:
            pass

    if len(polls) == 0:
        update.inline_query.answer(
            [],
            cache_time=0,
            is_personal=True,
            switch_pm_text=i18n.t('inline_query.create_first',
                                  locale=user.locale),
            switch_pm_parameter='inline',
        )
    else:
        results = []
        for poll in polls:
            text, keyboard = get_poll_text_and_vote_keyboard(session,
                                                             poll,
                                                             user=user,
                                                             inline_query=True)
            keyboard = InlineKeyboardMarkup([[
                InlineKeyboardButton('Please ignore this',
                                     callback_data='100:0:0')
            ]])

            content = InputTextMessageContent(
                text,
                parse_mode='markdown',
                disable_web_page_preview=True,
            )
            description = poll.description[:
                                           100] if poll.description is not None else None
            results.append(
                InlineQueryResultArticle(
                    poll.id,
                    poll.name,
                    description=description,
                    input_message_content=content,
                    reply_markup=keyboard,
                ))

        update.inline_query.answer(
            results,
            cache_time=0,
            is_personal=True,
            switch_pm_text=i18n.t('inline_query.create_poll',
                                  locale=user.locale),
            switch_pm_parameter='inline',
            next_offset=str(offset + 10),
        )
Ejemplo n.º 22
0
def handle_cumulative_vote(session, context, option, unlimited=False):
    """Handle a cumulative vote."""
    locale = option.poll.locale
    existing_vote = session.query(Vote) \
        .filter(Vote.poll_option == option) \
        .filter(Vote.user == context.user) \
        .one_or_none()

    vote_count = session.query(func.sum(Vote.vote_count)) \
        .filter(Vote.poll == option.poll) \
        .filter(Vote.user == context.user) \
        .one()
    vote_count = vote_count[0]
    if vote_count is None:
        vote_count = 0

    action = context.callback_result
    allowed_votes = 10000000
    if not unlimited:
        allowed_votes = option.poll.number_of_votes

    # Upvote, but no votes left
    if not unlimited and action == CallbackResult.yes and vote_count >= allowed_votes:
        no_left = i18n.t('callback.vote.no_left', locale=locale)
        respond_to_vote(session, no_left, context, option.poll)
        return False

    # Early return if downvote on non existing vote
    if existing_vote is None and action == CallbackResult.no:
        respond_to_vote(session, 'Cannot downvote this option.', context,
                        option.poll)
        return False

    if existing_vote:
        # Add to an existing vote
        if action == CallbackResult.yes:
            existing_vote.vote_count += 1
            session.commit()
            total_vote_count = allowed_votes - (vote_count + 1)
            vote_registered = i18n.t('callback.vote.registered', locale=locale)
            respond_to_vote(session, vote_registered, context, option.poll,
                            total_vote_count, True)

        # Remove from existing vote
        elif action == CallbackResult.no:
            existing_vote.vote_count -= 1
            session.commit()
            total_vote_count = allowed_votes - (vote_count - 1)
            vote_removed = i18n.t('callback.vote.removed', locale=locale)
            respond_to_vote(session, vote_removed, context, option.poll,
                            total_vote_count, True)

        # Delete vote if necessary
        if existing_vote.vote_count <= 0:
            session.delete(existing_vote)
            session.commit()

    # Add new vote
    elif existing_vote is None and action == CallbackResult.yes:
        vote = Vote(context.user, option)
        session.add(vote)
        session.commit()
        total_vote_count = allowed_votes - (vote_count + 1)
        vote_registered = i18n.t('callback.vote.registered', locale=locale)
        respond_to_vote(session, vote_registered, context, option.poll,
                        total_vote_count, True)

    return True
Ejemplo n.º 23
0
def show_close_confirmation(session, context, poll):
    """Show the permanent close confirmation message."""
    context.query.message.edit_text(
        i18n.t('management.permanently_close', locale=poll.user.locale),
        reply_markup=get_close_confirmation(poll),
    )
Ejemplo n.º 24
0
def get_settings_text(poll):
    """Compile the options text for this poll."""
    text = []
    locale = poll.user.locale
    text.append(i18n.t('settings.poll_type',
                       locale=locale,
                       poll_type=translate_poll_type(poll.poll_type, poll.locale)))

    text.append(i18n.t('settings.language', locale=locale, language=poll.locale))

    if poll.anonymous:
        text.append(i18n.t('settings.anonymous', locale=locale))
    else:
        text.append(i18n.t('settings.not_anonymous', locale=locale))

    if poll.due_date:
        text.append(i18n.t('settings.due_date', locale=locale,
                           date=poll.get_formatted_due_date()))
    else:
        text.append(i18n.t('settings.no_due_date', locale=locale))

    if poll.results_visible:
        text.append(i18n.t('settings.results_visible', locale=locale))
    else:
        text.append(i18n.t('settings.results_not_visible', locale=locale))

    text.append('')

    if poll.allow_new_options:
        text.append(i18n.t('settings.user_options', locale=locale))
    else:
        text.append(i18n.t('settings.no_user_options', locale=locale))

    if poll.results_visible:
        if poll.show_percentage:
            text.append(i18n.t('settings.percentage', locale=locale))
        else:
            text.append(i18n.t('settings.no_percentage', locale=locale))

    if poll.has_date_option():
        if poll.european_date_format:
            text.append(i18n.t('settings.euro_date_format', locale=locale))
        else:
            text.append(i18n.t('settings.us_date_format', locale=locale))

    text.append('')

    # Sorting of user names
    if not poll.anonymous:
        sorting_name = i18n.t(f'sorting.{poll.user_sorting}', locale=locale)
        text.append(i18n.t('settings.user_sorting', locale=locale, name=sorting_name))

    sorting_name = i18n.t(f'sorting.{poll.option_sorting}', locale=locale)
    text.append(i18n.t('settings.option_sorting', locale=locale, name=sorting_name))

    return '\n'.join(text)
Ejemplo n.º 25
0
def set_date(session, context, poll):
    """Show to vote type keyboard."""
    poll.current_date = date.fromisoformat(context.action)
    update_datepicker(context, poll)
    context.query.answer(i18n.t('callback.date_changed', locale=poll.locale,
                                date=poll.current_date.isoformat()))
Ejemplo n.º 26
0
        def wrapper(update: Update, context: CallbackContext):
            # The user has been banned and already got a message regarding this issue
            if context.user_data.get("banned-message-sent"):
                return

            user = None
            session = get_session()
            try:
                if hasattr(update, "message") and update.message:
                    message = update.message
                elif hasattr(update,
                             "edited_message") and update.edited_message:
                    message = update.edited_message

                user, _ = get_user(session, message.from_user)

                # Send a message explaining the user, why they cannot use the bot.
                # Also set a flag, which prevents sending this messages multiple times and thereby prevents DOS attacks.
                if user.banned:
                    if not context.user_data.get("banned-message-sent"):
                        context.user_data["banned-message-sent"] = True

                    message.chat.send_message(
                        "You have been permanently banned from using this bot, either due to spamming or inappropriate behavior."
                        "Please refrain from asking questions in the support group or on Github. There's nothing we can do about this."
                    )
                    return

                # Show an error message, if the users uses the bot in a public chat,
                # when he shouldn't. Also check if we're even allowed to send a message.
                if private and message.chat.type != "private":
                    chat = context.bot.getChat(message.chat.id)
                    if chat.permissions.can_send_messages:
                        message.chat.send_message(
                            "Please do this in a direct conversation with me.")
                    return

                response = func(context.bot, update, session, user)

                session.commit()

                # Respond to user
                if response is not None:
                    message.chat.send_message(response)

            except RollbackException as e:
                session.rollback()

                message.chat.send_message(
                    e.message,
                    parse_mode="markdown",
                    disable_web_page_preview=True,
                )

            except Exception as e:
                if not ignore_exception(e):
                    if config["logging"]["debug"]:
                        traceback.print_exc()

                    sentry.capture_exception(
                        tags={
                            "handler": "message",
                        },
                        extra={
                            "update": update.to_dict(),
                            "function": func.__name__
                        },
                    )

                    locale = "English"
                    if user is not None:
                        locale = user.locale

                    try:
                        message.chat.send_message(
                            i18n.t("misc.error", locale=locale),
                            parse_mode="markdown",
                            disable_web_page_preview=True,
                        )
                    except Exception as e:
                        # It sometimes happens, that an error occurs during sending the
                        # error message. Only capture important exceptions
                        if not ignore_exception(e):
                            sentry.capture_exception(
                                tags={
                                    "handler": "message",
                                },
                                extra={
                                    "update": update.to_dict(),
                                    "function": func.__name__,
                                },
                            )
                            raise e

            finally:
                # The session might not be there yet
                # We're checking for bans inside this try/catch, which has to
                # happen before session initialization due to performance reasons
                if "session" in locals():
                    session.close()
Ejemplo n.º 27
0
def show_anonymization_confirmation(session, context, poll):
    """Show the delete confirmation message."""
    context.query.message.edit_text(
        i18n.t('settings.anonymize', locale=poll.user.locale),
        reply_markup=get_anonymization_confirmation_keyboard(poll),
    )
Ejemplo n.º 28
0
    def wrapper(update, context):
        user = None
        if context.user_data.get("ban"):
            return

        try:
            # Check if the user is temporarily banned and send a message.
            # The check is done via the local telegram cache. This way we can prevent
            # opening a new DB connection for each spam request. (lots of performance)
            temp_ban_time = context.user_data.get("temporary-ban-time")
            if temp_ban_time is not None and temp_ban_time == date.today():
                update.callback_query.answer(i18n.t("callback.spam"))
                return

            session = get_session()

            user, statistic = get_user(session,
                                       update.callback_query.from_user)
            # Cache ban value, so we don't have to lookup the value in our database on each request
            if user.banned:
                context.user_data["ban"] = True
                return

            # Cache temporary-ban time, so we don't have to create a connection to our database
            if statistic.votes > config["telegram"]["max_user_votes_per_day"]:
                update.callback_query.answer(
                    i18n.t("callback.spam", locale=user.locale))
                context.user_data["temporary-ban-time"] = date.today()
                return

            func(context.bot, update, session, user)

            session.commit()

        except RollbackException as e:
            session.rollback()

            update.callback_query.answer(e.message)

        except Exception as e:
            if not ignore_exception(e):
                if config["logging"]["debug"]:
                    traceback.print_exc()

                sentry.capture_exception(
                    tags={
                        "handler": "callback_query",
                    },
                    extra={
                        "query": update.callback_query,
                    },
                )

                locale = "English"
                if user is not None:
                    locale = user.locale
                try:
                    update.callback_query.answer(
                        i18n.t("callback.error", locale=locale))
                except BadRequest as e:
                    # Check if this is a simple query timeout exception
                    if not ignore_exception(e):
                        raise e

        finally:
            # The session might not be there yet
            # We're checking for bans inside this try/catch, which has to
            # happen before session initialization due to performance reasons
            if "session" in locals():
                session.close()
Ejemplo n.º 29
0
    def wrapper(session, context):
        if context.poll is None or context.poll.delete is not None:
            return i18n.t("callback.poll_no_longer_exists", locale=context.user.locale)

        return function(session, context, context.poll)
Ejemplo n.º 30
0
def get_settings_text(poll):
    """Compile the options text for this poll."""
    text = []
    locale = poll.user.locale
    text.append(
        i18n.t(
            "settings.poll_type",
            locale=locale,
            poll_type=translate_poll_type(poll.poll_type, locale),
        ))

    text.append(
        i18n.t("settings.language", locale=locale, language=poll.locale))

    if poll.anonymous:
        text.append(i18n.t("settings.anonymous", locale=locale))
    elif not poll.is_priority():
        text.append(i18n.t("settings.not_anonymous", locale=locale))

    if poll.results_visible:
        text.append(i18n.t("settings.results_visible", locale=locale))
    else:
        text.append(i18n.t("settings.results_not_visible", locale=locale))

    if poll.due_date:
        text.append(
            i18n.t("settings.due_date",
                   locale=locale,
                   date=poll.get_formatted_due_date()))
    else:
        text.append(i18n.t("settings.no_due_date", locale=locale))

    text.append("")

    text.append("*------- Styling -------*")
    text.append("")
    if poll.allow_new_options:
        text.append(i18n.t("settings.user_options", locale=locale))
    else:
        text.append(i18n.t("settings.no_user_options", locale=locale))

    if poll.results_visible:
        if poll.show_percentage:
            text.append(i18n.t("settings.percentage", locale=locale))
        else:
            text.append(i18n.t("settings.no_percentage", locale=locale))

        if poll.permanently_summarized:
            text.append(
                i18n.t("settings.permanently_summarized", locale=locale))
        elif poll.summarize:
            text.append(i18n.t("settings.summarize", locale=locale))
        else:
            text.append(i18n.t("settings.dont_summarize", locale=locale))

    if poll.has_date_option():
        if poll.european_date_format:
            text.append(i18n.t("settings.euro_date_format", locale=locale))
        else:
            text.append(i18n.t("settings.us_date_format", locale=locale))

    text.append("")

    # Sorting of user names
    if poll.poll_type == PollType.doodle.name:
        sorting_name = i18n.t("sorting.doodle", locale=locale)
        text.append(
            i18n.t("settings.user_sorting", locale=locale, name=sorting_name))
    elif not poll.anonymous:
        sorting_name = i18n.t(f"sorting.{poll.user_sorting}", locale=locale)
        text.append(
            i18n.t("settings.user_sorting", locale=locale, name=sorting_name))

    sorting_name = i18n.t(f"sorting.{poll.option_sorting}", locale=locale)
    text.append(
        i18n.t("settings.option_sorting", locale=locale, name=sorting_name))

    bot_name = get_escaped_bot_name()
    if poll.allow_sharing:
        text.append("")
        text.append(
            i18n.t("settings.sharing_link", locale=locale, name=sorting_name))

        payload = get_start_button_payload(poll, StartAction.share_poll)
        text.append(f"https://t.me/{bot_name}?start={payload}")

    if config["telegram"]["allow_private_vote"]:
        text.append("")
        text.append(i18n.t("settings.private_vote_link", locale=locale))
        payload = get_start_button_payload(poll, StartAction.vote)
        text.append(f"https://t.me/{bot_name}?start={payload}")

    if config["telegram"]["allow_private_vote"] or poll.allow_sharing:
        text.append("")
        text.append(i18n.t("settings.link_warning", locale=locale))

    return "\n".join(text)