def edit_bot(bot, update, chat_data, to_edit=None): uid = util.uid_from_update(update) message_id = util.mid_from_update(update) user = User.from_update(update) if not to_edit: if update.message: command = update.message.text if "edit" in command: b_id = re.match(r"^/edit(\d+)$", command).groups()[0] elif "approve" in command: b_id = re.match(r"^/approve(\d+)$", command).groups()[0] else: raise ValueError("No 'edit' or 'approve' in command.") try: to_edit = Bot.get(id=b_id) except Bot.DoesNotExist: update.message.reply_text(util.failure("No bot exists with this id.")) return else: bot.formatter.send_failure(uid, "An unexpected error occured.") return # if not to_edit.approved: # return approve_bots(bot, update, override_list=[to_edit]) pending_suggestions = Suggestion.pending_for_bot(to_edit, user) reply_markup = InlineKeyboardMarkup( _edit_bot_buttons(to_edit, pending_suggestions, uid in settings.MODERATORS) ) pending_text = ( "\n\n{} Some changes are pending approval{}.".format( captions.SUGGESTION_PENDING_EMOJI, "" if user.chat_id in settings.MODERATORS else " by a moderator", ) if pending_suggestions else "" ) meta_text = ( "\n\nDate added: {}\nMember since revision {}\n" "Submitted by {}\nApproved by {}".format( to_edit.date_added, to_edit.revision, to_edit.submitted_by, to_edit.approved_by, ) ) bot.formatter.send_or_edit( uid, "🛃 Edit {}{}{}".format( to_edit.detail_text, meta_text if user.id in settings.MODERATORS else "", pending_text, ), to_edit=message_id, reply_markup=reply_markup, )
def notify_submittant_rejected(bot, admin_user, notify_submittant, reason, to_reject): notification_successful = False msg = "{} rejected by {}.".format(to_reject.username, admin_user) if notify_submittant or reason: try: if reason: bot.send_message( to_reject.submitted_by.chat_id, util.failure( messages.REJECTION_WITH_REASON.format( to_reject.username, reason=reason ) ), ) else: bot.sendMessage( to_reject.submitted_by.chat_id, util.failure( messages.REJECTION_PRIVATE_MESSAGE.format(to_reject.username) ), ) msg += "\nUser {} was notified.".format(str(to_reject.submitted_by)) notification_successful = True except TelegramError: msg += "\nUser {} could NOT be contacted/notified in private.".format( str(to_reject.submitted_by) ) notification_successful = False text = util.success("{} rejected.".format(to_reject.username)) if notification_successful is True: text += " User {} was notified.".format(to_reject.submitted_by.plaintext) elif notification_successful is False: try: text += " " + mdformat.failure( "Could not contact {}.".format(to_reject.submitted_by.plaintext) ) except: pass else: text += " No notification sent." return msg
def add_favorite_handler(bot, update, args=None): uid = util.uid_from_update(update) from botlistbot.components.basic import main_menu_buttons main_menu_markup = ReplyKeyboardMarkup( main_menu_buttons(uid in settings.MODERATORS)) if args: query = ' '.join(args) if isinstance(args, list) else args try: # TODO: add multiple username = re.match(settings.REGEX_BOT_IN_TEXT, query).groups()[0] try: # TODO: get exact database matches for input without `@` item = Bot.by_username(username, include_disabled=True) return add_favorite(bot, update, item) except Bot.DoesNotExist: buttons = [ InlineKeyboardButton( "Yai!", callback_data=util.callback_for_action( CallbackActions.ADD_ANYWAY, {'u': username})), InlineKeyboardButton( "Nay...", callback_data=util.callback_for_action( CallbackActions.ADD_FAVORITE)) ] reply_markup = InlineKeyboardMarkup([buttons]) util.send_md_message( bot, uid, "{} is not in the @BotList. Do you want to add it to your {} anyway?" .format(username, captions.FAVORITES), reply_markup=reply_markup) except AttributeError: # invalid bot username # TODO when does this happen? update.message.reply_text( util.failure( "Sorry, but that is not a valid username. Please try again. /addfav" )) else: buttons = [ InlineKeyboardButton("Search inline", switch_inline_query_current_chat='') ] reply_markup = InlineKeyboardMarkup([buttons]) bot.sendMessage(uid, messages.ADD_FAVORITE, reply_markup=ForceReply(selective=True)) return ConversationHandler.END
def reject_bot_submission( bot, update, args=None, to_reject=None, verbose=True, notify_submittant=True, reason=None, ): uid = util.uid_from_update(update) user = User.from_update(update) if to_reject is None: if not update.message.reply_to_message: bot.send_message( update.effective_user.id, util.failure("You must reply to a message of mine."), ) return text = update.message.reply_to_message.text reason = reason if reason else (" ".join(args) if args else None) try: update.message.delete() except: pass username = helpers.find_bots_in_text(text, first=True) if not username: bot.send_message( update.effective_user.id, util.failure("No username in the message that you replied to."), ) return try: to_reject = Bot.by_username(username) except Bot.DoesNotExist: bot.send_message( update.effective_user.id, util.failure( "Rejection failed: {} is not present in the " "database.".format(username) ), ) return if to_reject.approved is True: msg = "{} has already been accepted, so it cannot be rejected anymore.".format( username ) bot.sendMessage(uid, util.failure(msg)) return Statistic.of(update, "reject", to_reject.username) text = notify_submittant_rejected(bot, user, notify_submittant, reason, to_reject) to_reject.delete_instance() if verbose: bot.sendMessage(uid, text) if update.callback_query: update.callback_query.answer(text=text)
def send_bot_details( bot, update, chat_data, item=None, header_msg: Optional[str] = None ): is_group = util.is_group_message(update) cid = update.effective_chat.id user = User.from_update(update) if item is None: if is_group: return try: text = update.message.text bot_in_text = re.findall(settings.REGEX_BOT_IN_TEXT, text)[0] item = Bot.by_username(bot_in_text, include_disabled=True) except Bot.DoesNotExist: update.message.reply_text( util.failure( "This bot is not in the @BotList. If you think this is a " "mistake, see the /examples for /contributing." ) ) return header_buttons = [] buttons = [] if item.disabled: txt = "{} {} and thus removed from the @BotList.".format( item, Bot.DisabledReason.to_str(item.disabled_reason) ) elif item.approved: # bot is already in the botlist => show information txt = f"{header_msg}\n\n{item.detail_text}" if header_msg else item.detail_text if ( item.description is None and not Keyword.select().where(Keyword.entity == item).exists() ): txt += " is in the @BotList, but has no description or keywords yet." btn = InlineCallbackButton( captions.BACK_TO_CATEGORY, CallbackActions.SELECT_BOT_FROM_CATEGORY, {"id": item.category.id}, ) header_buttons.insert(0, btn) header_buttons.append( InlineKeyboardButton(captions.SHARE, switch_inline_query=item.username) ) # if cid in settings.MODERATORS: header_buttons.append( InlineKeyboardButton( "📝 Edit", callback_data=util.callback_for_action( CallbackActions.EDIT_BOT, {"id": item.id} ), ) ) # Add favorite button favorite_found = Favorite.search_by_bot(user, item) if favorite_found: buttons.append( InlineKeyboardButton( captions.REMOVE_FAVORITE_VERBOSE, callback_data=util.callback_for_action( CallbackActions.REMOVE_FAVORITE, {"id": favorite_found.id, "details": True}, ), ) ) else: buttons.append( InlineKeyboardButton( captions.ADD_TO_FAVORITES, callback_data=util.callback_for_action( CallbackActions.ADD_TO_FAVORITES, {"id": item.id, "details": True}, ), ) ) else: txt = "{} is currently pending to be accepted for the @BotList.".format(item) if cid in settings.MODERATORS: header_buttons.append( InlineKeyboardButton( "🛃 Accept / Reject", callback_data=util.callback_for_action( CallbackActions.APPROVE_REJECT_BOTS, {"id": item.id} ), ) ) if buttons or header_buttons: reply_markup = InlineKeyboardMarkup( util.build_menu(buttons, n_cols=3, header_buttons=header_buttons) ) else: reply_markup = None reply_markup, callback = botlistchat.append_restricted_delete_button( update, chat_data, reply_markup ) # Should we ever decide to show thumbnails *shrug* # if os.path.exists(item.thumbnail_file): # preview = True # photo = '[\xad]({})'.format('{}/thumbnail/{}.jpeg'.format( # settings.API_URL, # item.username[1:] # )) # log.info(photo) # txt = photo + txt # else: # preview = False msg = bot.formatter.send_or_edit( cid, txt, to_edit=util.mid_from_update(update), reply_markup=reply_markup ) callback(msg) Statistic.of(update, "view-details", item.username, Statistic.ANALYSIS) return CallbackStates.SHOWING_BOT_DETAILS
def notify_admin_err(self, txt): self.bot.formatter.send_or_edit(self.chat_id, util.failure(txt), to_edit=self.message_id, disable_web_page_preview=True, disable_notification=False)
def notify_bot_offline(bot, update, args=None): tg_user = update.message.from_user user = User.from_telegram_object(tg_user) reply_to = util.original_reply_id(update) if args: text = " ".join(args) else: text = update.message.text command_no_args = ( len(re.findall(r"^/new\s*$", text)) > 0 or text.lower().strip() == "/offline@botlistbot" ) if command_no_args: update.message.reply_text( util.action_hint( "Please use this command with an argument. For example:\n/offline @mybot" ), reply_to_message_id=reply_to, ) return # `#offline` is already checked by handler try: username = re.match(settings.REGEX_BOT_IN_TEXT, text).groups()[0] if username == "@" + settings.SELF_BOT_NAME: log.info("Ignoring {}".format(text)) return except AttributeError: if args: update.message.reply_text( util.failure("Sorry, but you didn't send me a bot `@username`."), quote=True, parse_mode=ParseMode.MARKDOWN, reply_to_message_id=reply_to, ) else: log.info("Ignoring {}".format(text)) # no bot username, ignore update pass return try: offline_bot = Bot.get( fn.lower(Bot.username) ** username.lower(), Bot.approved == True ) try: Suggestion.get(action="offline", subject=offline_bot) except Suggestion.DoesNotExist: suggestion = Suggestion( user=user, action="offline", date=datetime.date.today(), subject=offline_bot, ) suggestion.save() update.message.reply_text( util.success( "Thank you! We will review your suggestion and set the bot offline." ), reply_to_message_id=reply_to, ) except Bot.DoesNotExist: update.message.reply_text( util.action_hint("The bot you sent me is not in the @BotList."), reply_to_message_id=reply_to, ) return ConversationHandler.END
def new_bot_submission(bot, update, chat_data, args=None, bot_checker=None): tg_user = update.message.from_user user = User.from_telegram_object(tg_user) if util.stop_banned(update, user): return reply_to = util.original_reply_id(update) if args: text = " ".join(args) else: text = update.message.text command_no_args = ( len(re.findall(r"^/new\s*$", text)) > 0 or text.lower().strip() == "/new@botlistbot" ) if command_no_args: update.message.reply_text( util.action_hint( "Please use this command with an argument. For example:\n/new @mybot 🔎" ), reply_to_message_id=reply_to, ) return # `#new` is already checked by handler try: username = re.match(settings.REGEX_BOT_IN_TEXT, text).groups()[0] if username.lower() == "@" + settings.SELF_BOT_NAME.lower(): log.info("Ignoring {}".format(text)) return except AttributeError: if args: update.message.reply_text( util.failure("Sorry, but you didn't send me a bot `@username`."), quote=True, parse_mode=ParseMode.MARKDOWN, reply_to_message_id=reply_to, ) log.info("Ignoring {}".format(text)) # no bot username, ignore update return try: new_bot = Bot.by_username(username, include_disabled=True) if new_bot.disabled: update.message.reply_text( util.failure( "{} is banned from the @BotList.".format(new_bot.username) ), reply_to_message_id=reply_to, ) elif new_bot.approved: update.message.reply_text( util.action_hint( "Sorry fool, but {} is already in the @BotList 😉".format( new_bot.username ) ), reply_to_message_id=reply_to, ) else: update.message.reply_text( util.action_hint( "{} has already been submitted. Please have patience...".format( new_bot.username ) ), reply_to_message_id=reply_to, ) return except Bot.DoesNotExist: new_bot = Bot( revision=Revision.get_instance().next, approved=False, username=username, submitted_by=user, ) new_bot.inlinequeries = "🔎" in text new_bot.official = "🔹" in text # find language languages = Country.select().execute() for lang in languages: if lang.emoji in text: new_bot.country = lang new_bot.date_added = datetime.date.today() description_reg = re.match(settings.REGEX_BOT_IN_TEXT + " -\s?(.*)", text) description_notify = "" if description_reg: description = description_reg.group(2) new_bot.description = description description_notify = " Your description was included." new_bot.save() if ( util.is_private_message(update) and util.uid_from_update(update) in settings.MODERATORS ): from botlistbot.components.explore import send_bot_details send_bot_details(bot, update, chat_data, new_bot) else: update.message.reply_text( util.success( "You submitted {} for approval.{}".format(new_bot, description_notify) ), parse_mode=ParseMode.MARKDOWN, reply_to_message_id=reply_to, ) # Ask the user to fill in the bot details util.send_md_message( bot, update.effective_user.id, "Congratulations, you just submitted a bot to the @BotList. Please help us fill in the details below:", ) edit_bot(bot, update, chat_data, to_edit=new_bot) try: check_submission(bot, bot_checker, new_bot) except Exception as e: log.exception(e) return ConversationHandler.END
def search_query(bot, update: Update, chat_data, query, send_errors=True): cid: int = update.effective_chat.id user: User = User.from_update(update) is_admin: bool = cid in settings.MODERATORS replied_to_message_id: Optional[int] = util.original_reply_id(update) is_suggestion_by_other: bool = (update.effective_chat and update.effective_chat.id == settings.BOTLISTCHAT_ID) results = search.search_bots(query) reply_markup = (ReplyKeyboardMarkup(basic.main_menu_buttons(is_admin), resize_keyboard=True) if util.is_private_message(update) else None) if results: if len(results) == 1: update.effective_message.delete() if is_suggestion_by_other: header = f"{user.markdown_short} found the following bot for you:" else: header = "I found the following bot for you:" return send_bot_details(bot, update, chat_data, results[0], header_msg=header) too_many_results = len(results) > settings.MAX_SEARCH_RESULTS bots_list = "" if cid in settings.MODERATORS: # private chat with moderator # append edit buttons bots_list += "\n".join([ "{} — /edit{} 🛃".format(b, b.id) for b in list(results)[:100] ]) else: bots_list += "\n".join( [str(b) for b in list(results)[:settings.MAX_SEARCH_RESULTS]]) bots_list += "\n…" if too_many_results else "" bots_list = messages.SEARCH_RESULTS.format( bots=bots_list, num_results=len(results), plural="s" if len(results) > 1 else "", query=query, ) if util.is_group_message( update) and not update.message.reply_to_message: try: bot.formatter.send_message( update.effective_user.id, bots_list, reply_markup=reply_markup, disable_web_page_preview=True, ) reply_markup, callback = botlistchat.append_restricted_delete_button( update, chat_data, InlineKeyboardMarkup([[]])) msg = bot.formatter.send_message( update.effective_chat.id, f"Hey {user.plaintext}, let's not annoy the others. I sent you the search results " f"[in private chat](https://t.me/{settings.SELF_BOT_NAME}).", disable_web_page_preview=True, reply_markup=reply_markup, ) callback(msg) update.effective_message.delete() except TelegramError: hint_msg, hint_reply_markup, _ = get_hint_data("#private") bot.formatter.send_message( update.effective_chat.id, hint_msg, reply_markup=hint_reply_markup, reply_to_message_id=update.effective_message.id, disable_web_page_preview=True, ) return ConversationHandler.END if is_suggestion_by_other and replied_to_message_id: bots_list = f"{user.markdown_short} suggests to search and {bots_list}" bot.formatter.send_message( update.effective_chat.id, bots_list, parse_mode=ParseMode.MARKDOWN, reply_markup=reply_markup, disable_web_page_preview=True, reply_to_message_id=replied_to_message_id, ) update.effective_message.delete() else: if send_errors: callback = None if util.is_group_message(update): reply_markup, callback = botlistchat.append_restricted_delete_button( update, chat_data, InlineKeyboardMarkup([[]])) msg = update.message.reply_text( util.failure("Sorry, I couldn't find anything related " "to *{}* in the @BotList. /search".format( util.escape_markdown(query))), parse_mode=ParseMode.MARKDOWN, reply_markup=reply_markup, ) if callback: callback(msg) return ConversationHandler.END