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')
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()))
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)
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), )
def send_error_quiz_unsupported(update: Update, _context): update.effective_chat.send_message( i18n.t("creation.native_poll.quiz_unsupported"))
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)
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)
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)
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)
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)
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)
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')
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)
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)
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), )
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))
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)
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))
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)
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
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), )
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
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), )
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)
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()))
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()
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), )
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()
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)
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)