def lookup_entity(query, exact=True): """ Searches for a Bot or User contained in the query """ if exact: try: return Bot.by_username(query, include_disabled=True) except Bot.DoesNotExist: pass try: return Bot.get(chat_id=int(query)) except ValueError: pass except Bot.DoesNotExist: pass try: return User.by_username(query) except User.DoesNotExist: pass try: return User.get(chat_id=query) except User.DoesNotExist: pass return None
def recommend_moderator(bot, update, bot_in_question, page): uid = update.effective_user.id mid = util.mid_from_update(update) moderators = User.select().where( (User.chat_id << settings.MODERATORS) & (User.chat_id != uid) ) buttons = [ InlineKeyboardButton( u.first_name, callback_data=util.callback_for_action( CallbackActions.SELECT_MODERATOR, {"bot_id": bot_in_question.id, "uid": u.id, "page": page}, ), ) for u in moderators ] buttons.insert( 0, InlineKeyboardButton( captions.BACK, callback_data=util.callback_for_action( CallbackActions.SWITCH_APPROVALS_PAGE, {"page": page} ), ), ) reply_markup = InlineKeyboardMarkup(util.build_menu(buttons, 1)) text = mdformat.action_hint( "Select a moderator you think is better suited to evaluate the submission of {}.".format( str(bot_in_question) ) ) bot.formatter.send_or_edit(uid, text, to_edit=mid, reply_markup=reply_markup)
def add_favorite(bot, update, item: Bot, callback_alert=None): user = User.from_update(update) uid = util.uid_from_update(update) mid = util.mid_from_update(update) from botlistbot.components.basic import main_menu_buttons main_menu_markup = ReplyKeyboardMarkup( main_menu_buttons(uid in settings.MODERATORS)) fav, created = Favorite.add(user=user, item=item) if created: Statistic.of(user, 'add-favorite', item.username) text = mdformat.love("{} added to your {}favorites.".format( fav.bot, '' if callback_alert else '/')) if callback_alert: update.callback_query.answer(text=text, show_alert=False) else: msg = util.send_md_message(bot, uid, text, to_edit=mid, reply_markup=main_menu_markup) mid = msg.message_id util.wait(bot, update) send_favorites_list(bot, update, to_edit=mid) else: text = mdformat.none_action( "{} is already a favorite of yours.{}".format( fav.bot, '' if callback_alert else ' /favorites')) if callback_alert: update.callback_query.answer(text=text, show_alert=False) else: util.send_md_message(bot, uid, text, reply_markup=main_menu_markup) return ConversationHandler.END
def add_custom(bot, update, username): uid = util.uid_from_update(update) user = User.from_update(update) mid = util.mid_from_update(update) from botlistbot.components.basic import main_menu_buttons main_menu_markup = ReplyKeyboardMarkup( main_menu_buttons(uid in settings.MODERATORS)) try: fav = Favorite.get(custom_bot=username) util.send_or_edit_md_message( bot, uid, mdformat.none_action( "{} is already a favorite of yours. /favorites".format( fav.custom_bot)), to_edit=mid, reply_markup=main_menu_markup) except Favorite.DoesNotExist: fav = Favorite(user=user, custom_bot=username, date_added=datetime.date.today()) fav.save() msg = bot.formatter.send_or_edit( uid, mdformat.love("{} added to your /favorites.".format( fav.custom_bot)), to_edit=mid) mid = msg.message_id util.wait(bot, update) send_favorites_list(bot, update, to_edit=mid) return ConversationHandler.END
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 start(bot, update, chat_data, args): tg_user = update.message.from_user chat_id = tg_user.id # Get or create the user from/in database User.from_telegram_object(tg_user) if isinstance(args, list) and len(args) > 0: # CATEGORY BY ID try: cat = Category.get(Category.id == args[0]) from botlistbot.components.explore import send_category return send_category(bot, update, chat_data, cat) except (ValueError, Category.DoesNotExist): pass query = " ".join(args).lower() # SPECIFIC DEEP-LINKED QUERIES if query == const.DeepLinkingActions.CONTRIBUTING: return help.contributing(bot, update, quote=False) elif query == const.DeepLinkingActions.EXAMPLES: return help.examples(bot, update, quote=False) elif query == const.DeepLinkingActions.RULES: return help.rules(bot, update, quote=False) elif query == const.DeepLinkingActions.SEARCH: return search_handler(bot, update, chat_data) # SEARCH QUERY search_query(bot, update, chat_data, query) else: bot.sendSticker( chat_id, open( os.path.join(appglobals.ROOT_DIR, "assets", "sticker", "greetings-humanoids.webp"), "rb", ), ) help.help(bot, update) util.wait(bot, update) if util.is_private_message(update): main_menu(bot, update) return ConversationHandler.END
def ban_user(_bot, update, user: User, ban_state: bool): if user.banned and ban_state is True: update.message.reply_text( mdformat.none_action("User {} is already banned.".format(user)), parse_mode="markdown", ) raise DispatcherHandlerStop if not user.banned and ban_state is False: update.message.reply_text( mdformat.none_action("User {} is not banned.".format(user)), parse_mode="markdown", ) raise DispatcherHandlerStop user.banned = ban_state if ban_state is True: with db.atomic(): user_submissions = Bot.select().where( (Bot.approved == False) & (Bot.submitted_by == user) # TODO: does this need to include `Bot.deleted == True`? ) for b in user_submissions: b.delete_instance() users_suggestions = Suggestion.select().where( (Suggestion.executed == False) & (Suggestion.user == user) ) for s in users_suggestions: s.delete_instance() update.message.reply_text( mdformat.success( "User {} banned, all bot submissions and suggestions removed.".format( user ) ), parse_mode="markdown", ) Statistic.of(update, "ban", user.markdown_short) else: update.message.reply_text( mdformat.success("User {} unbanned.".format(user)), parse_mode="markdown" ) Statistic.of(update, "unban", user.markdown_short) user.save()
def apply_all_changes(bot, update, chat_data, to_edit): user = User.from_update(update) user_suggestions = Suggestion.select_all_of_user(user) for suggestion in user_suggestions: suggestion.apply() refreshed_bot = Bot.get(id=to_edit.id) edit_bot(bot, update, chat_data, refreshed_bot) Statistic.of(update, "apply", refreshed_bot.username)
def accept_bot_submission(bot, update, of_bot: Bot, category): uid = util.uid_from_update(update) message_id = util.mid_from_update(update) user = User.from_update(update) try: of_bot.category = category of_bot.date_added = datetime.date.today() of_bot.approved = True of_bot.approved_by = user of_bot.save() buttons = [ [ InlineKeyboardButton( "Edit {} details".format(of_bot.username), callback_data=util.callback_for_action( CallbackActions.EDIT_BOT, {"id": of_bot.id} ), ) ] ] reply_markup = InlineKeyboardMarkup(buttons) bot.formatter.send_or_edit( uid, "{} has been accepted to the Botlist. ".format( of_bot ), to_edit=message_id, reply_markup=reply_markup, ) log_msg = "{} accepted by {}.".format(of_bot.username, uid) # notify submittant if of_bot.submitted_by != user: try: bot.sendMessage( of_bot.submitted_by.chat_id, util.success( messages.ACCEPTANCE_PRIVATE_MESSAGE.format( of_bot.username, of_bot.category ) ), ) log_msg += "\nUser {} was notified.".format(str(of_bot.submitted_by)) except TelegramError: log_msg += "\nUser {} could NOT be contacted/notified in private.".format( str(of_bot.submitted_by) ) log.info(log_msg) except: bot.formatter.send_failure(uid, "An error has occured. Bot not added.")
def access_token(bot, update): user = User.from_telegram_object(update.effective_user) rd_token = binascii.hexlify(os.urandom(32)).decode('utf-8') try: db_token = APIAccess.get(user=user).token except: db_token = "You have no token." text = "Random token: {}\n" \ "Database token (use this for api calls):\n{}".format(rd_token, db_token) update.message.reply_text(text) return ConversationHandler.END
def t3chnostats(bot, update): days = 30 txt = 'Bots approved by other people *in the last {} days*:\n\n'.format( days) bots = Bot.select().where( (Bot.approved_by != User.get(User.chat_id == 918962)) & (Bot.date_added.between( datetime.date.today() - datetime.timedelta(days=days), datetime.date.today()))) txt += '\n'.join( ['{} by @{}'.format(str(b), b.approved_by.username) for b in bots]) update.message.reply_text(txt, parse_mode=ParseMode.MARKDOWN)
def send_favorites_list(bot, update, to_edit=None): uid = util.uid_from_update(update) user = User.from_update(update) t = threading.Thread(target=_too_many_favorites_handler, args=(bot, update, user)) t.start() favorites = Favorite.select_all(user) buttons = [[ InlineKeyboardButton(captions.ADD_FAVORITE, callback_data=util.callback_for_action( CallbackActions.ADD_FAVORITE)), InlineKeyboardButton(captions.REMOVE_FAVORITE, callback_data=util.callback_for_action( CallbackActions.REMOVE_FAVORITE_MENU)) ], [ InlineKeyboardButton( 'Layout: ' + Layouts.get_caption(user.favorites_layout), callback_data=util.callback_for_action( CallbackActions.TOGGLE_FAVORITES_LAYOUT, {'v': Layouts.get_next(user.favorites_layout)})), ], [ InlineKeyboardButton( captions.SHARE, switch_inline_query=DeepLinkingActions.FAVORITES), ]] reply_markup = InlineKeyboardMarkup(buttons) if to_edit is None: to_edit = util.mid_from_update(update) if len(favorites) == 0: text = "You have no favorites yet." else: text = _favorites_categories_md(favorites, user.favorites_layout) bot.formatter.send_or_edit(uid, text, to_edit=to_edit, reply_markup=reply_markup)
def of(cls, issuer, action: str, entity: str = None, level=logging.INFO): # if action not in Statistic.ACTIONS.keys(): # raise ValueError('"{}" is not a valid action. Refer to Statistic.ACTIONS for available keys.') if isinstance(issuer, User): user = issuer elif isinstance(issuer, Update): user = User.from_update(issuer) else: raise AttributeError( "The issuer argument needs to be an object of type User or Update." ) obj = cls(user=user, date=datetime.datetime.now(), action=action, entity=entity, level=level) obj.save() return obj
def broadcast(bot, update, user_data): cid = update.effective_chat.id uid = update.effective_user.id mid = util.mid_from_update(update) user = User.from_update(update) text = '' if cid == settings.BOTLISTCHAT_ID: replied_to = update.message.reply_to_message if replied_to: user_data['broadcast'] = dict( user_data.get('broadcast', dict()), reply_to_message_id=replied_to.message_id) if replied_to.from_user.username.lower() == settings.SELF_BOT_NAME: # editing text += '*You are editing one of my messages*\n\n' user_data['broadcast']['mode'] = 'editing' else: # replying text += '*You are replying to a message of {}.*\n\n'.format( update.message.reply_to_message.from_user.first_name) user_data['broadcast']['mode'] = 'replying' # answer and clean msg = bot.send_message(cid, "k") _delete_multiple_delayed(bot, cid, delayed=[msg.message_id], immediately=[update.message.message_id]) to_text = " _to_ " text += "Send me the text to broadcast to @BotListChat.\n" text += "_You can use the following words and they will replaced:_\n\n" text += '\n'.join([ '"{}"{}{}'.format(k, to_text, v) for k, v in BROADCAST_REPLACEMENTS.items() ]) # TODO Build text mentioning replacements # text += '\n@' + str(update.effective_user.username) + to_text + user.markdown_short bot.formatter.send_or_edit(uid, mdformat.action_hint(text), mid) return BotStates.BROADCASTING
def share_with_moderator(bot, update, bot_in_question, moderator): user = User.from_update(update) buttons = [ [ InlineKeyboardButton( "Yea, let me take this one!", callback_data=util.callback_for_action( CallbackActions.APPROVE_REJECT_BOTS, {"id": bot_in_question.id} ), ) ] ] reply_markup = InlineKeyboardMarkup(buttons) text = "{} thinks that you have the means to inspect this bot submission:\n▶️ {}".format( user.markdown_short, bot_in_question ) try: util.send_md_message( bot, moderator.chat_id, text, reply_markup=reply_markup, disable_web_page_preview=True, ) answer_text = mdformat.success( "I will ask {} to have a look at this submission.".format( moderator.plaintext ) ) except Exception as e: answer_text = mdformat.failure(f"Could not contact {moderator.plaintext}: {e}") if update.callback_query: update.callback_query.answer(text=answer_text) Statistic.of( update, "share", "submission {} with {}".format(bot_in_question.username, moderator.plaintext), )
def hint_handler(bot, update, job_queue: JobQueue): chat_id = update.message.chat_id if not util.is_group_message(update): return text = update.message.text reply_to = update.message.reply_to_message user = User.from_update(update) msg, reply_markup, hashtag = get_hint_data(text) def _send_hint(hint_text): if hint_text is None: return if reply_to: hint_text = f"{user.markdown_short} hints: {hint_text}" bot.formatter.send_message( chat_id, hint_text, reply_markup=reply_markup, reply_to_message_id=reply_to.message_id if reply_to else None, ) update.effective_message.delete() should_reply: bool = HINTS.get(hashtag)["should_reply"] if should_reply and not reply_to: del_markup = append_free_delete_button(update, InlineKeyboardMarkup([[]])) _send_hint(msg) ntfc_msg = update.effective_message.reply_text( f"Hey {user.markdown_short}, next time reply to someone 🙃", parse_mode=ParseMode.MARKDOWN, reply_markup=del_markup, quote=False, disable_web_page_preview=True, ) job_queue.run_once(lambda *_: ntfc_msg.delete(), 7, name="delete notification") else: _send_hint(msg)
def remove_favorite_menu(bot, update): uid = util.uid_from_update(update) user = User.from_update(update) favorites = Favorite.select_all(user) fav_remove_buttons = [ InlineKeyboardButton('✖️ {}'.format(str(f.bot.username)), callback_data=util.callback_for_action( CallbackActions.REMOVE_FAVORITE, {'id': f.id})) for f in favorites ] buttons = util.build_menu(fav_remove_buttons, 2, header_buttons=[ InlineKeyboardButton( captions.DONE, callback_data=util.callback_for_action( CallbackActions.SEND_FAVORITES_LIST)) ]) reply_markup = InlineKeyboardMarkup(buttons) bot.formatter.send_or_edit(uid, util.action_hint("Select favorites to remove"), to_edit=util.mid_from_update(update), reply_markup=reply_markup)
def credits(bot, update): users_contrib = User.select().join(Bot) pass Bot.select(Bot.submitted_by) 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 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
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 check_submission(bot, bot_checker: "BotChecker", to_check: Bot): # TODO: make this method async if bot_checker is None: return botlistbot_user = User.botlist_user_instance() log.debug("Checking bot {}...".format(to_check.username)) def reject(reason): to_check.delete_instance() msg = notify_submittant_rejected( bot, botlistbot_user, notify_submittant=True, reason=reason, to_reject=to_check, ) bot.formatter.send_message(settings.BOTLIST_NOTIFICATIONS_ID, msg) try: peer = bot_checker.resolve_bot(to_check) except UsernameNotOccupied: to_check.delete_instance() reject( "The entity you submitted either does not exist or is not a Telegram bot." ) return bot_checker.update_bot_details(to_check, peer) if to_check.userbot: reject( "You submitted the name of a Telegram user, not one of a bot. If you're trying to " "submit a userbot, please contact the BLSF directly (" "@BotListChat)." ) return # Check online state response = loop.run_until_complete( bot_checker.get_ping_response( to_check, timeout=18, try_inline=to_check.inlinequeries ) ) is_offline = not bool(response) if is_offline: reject( "The bot you sent seems to be offline, unfortunately. Feel free to submit it again " "when it's back up 😙" ) return now = datetime.datetime.now() to_check.last_ping = now to_check.last_response = now loop.run_until_complete(add_keywords(bot, response, to_check)) # Download profile picture if settings.DOWNLOAD_PROFILE_PICTURES: # TODO: does this work asynchronously? loop.run_until_complete(download_profile_picture(bot, bot_checker, to_check)) to_check.save() log.info(f"{to_check} was evaluated and looks good for approval.")
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 inlinequery_handler(bot, update, chat_data): query = update.inline_query.query.lower() # TODO: remove or enhance eventually, this is potentially very spammy # Statistic.of(update, 'inlinequery', '"{}"'.format(query), Statistic.DETAILED) user = User.from_update(update) results_list = list() input_given = len(query.strip()) > 0 query_too_short = 0 < len(query.strip()) < SEARCH_QUERY_MIN_LENGTH too_many_results = False cat_results = [] bot_results = [] if input_given: # query category results cat_results = search.search_categories(query) if not query_too_short: # query bot results bot_results = list(search.search_bots(query)) if len(bot_results) > MAX_BOTS: bot_results = bot_results[:MAX_BOTS] too_many_results = True # query for new bots if query == messages.NEW_BOTS_INLINEQUERY.lower() or query == 'new': results_list.append(new_bots_article()) bot.answer_inline_query(update.inline_query.id, results=results_list) return if query in CONTRIBUTING_QUERIES: results_list.append( InlineQueryResultArticle( id=uuid4(), title='Contributing', input_message_content=InputTextMessageContent( message_text=messages.CONTRIBUTING, parse_mode="Markdown"), )) bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=600) return if query in EXAMPLES_QUERIES: results_list.append( InlineQueryResultArticle( id=uuid4(), title='Examples', input_message_content=InputTextMessageContent( message_text=messages.EXAMPLES, parse_mode="Markdown"), )) bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=600) return if query in (const.DeepLinkingActions.RULES, '#rules'): results_list.append( InlineQueryResultArticle( id=uuid4(), title='@BotListChat Rules', input_message_content=InputTextMessageContent( message_text=messages.BOTLISTCHAT_RULES, parse_mode="Markdown", disable_web_page_preview=True), )) bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=600) return if query == const.DeepLinkingActions.FAVORITES and user.has_favorites: results_list.append(favorites_article(user)) bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=0, is_personal=True) return msg, reply_markup, key = botlistchat.get_hint_data(query) if msg is not None: results_list.append(hint_article(msg, reply_markup, key)) bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=600) return invalid_search_term = query_too_short and not cat_results if invalid_search_term: results_list.append(query_too_short_article()) results_available = cat_results or bot_results if results_available: if len(bot_results) > 1: results_list.append( all_bot_results_article(bot_results, too_many_results)) for c in cat_results: results_list.append(category_article(c)) for b in bot_results: results_list.append(bot_article(b)) if len(bot_results) > 0: bot.answer_inline_query( update.inline_query.id, results=results_list, switch_pm_text="See all results" if too_many_results else "Search in private chat", switch_pm_parameter=util.encode_base64(query), cache_time=0, is_personal=True) else: bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=0, is_personal=True) else: if user.has_favorites: results_list.append(favorites_article(user)) results_list.append(new_bots_article()) categories = Category.select_all() for c in categories: results_list.append(category_article(c)) if invalid_search_term or not input_given: bot.answer_inline_query(update.inline_query.id, results=results_list, cache_time=0, is_personal=True) else: bot.answer_inline_query( update.inline_query.id, results=results_list, switch_pm_text="No results. Contribute a bot?", switch_pm_parameter='contributing', cache_time=0, is_personal=True)
def callback_router(bot, update, chat_data, user_data, job_queue): obj = json.loads(str(update.callback_query.data)) user = User.from_update(update) try: if "a" in obj: action = obj["a"] # BOTLISTCHAT if action == CallbackActions.DELETE_CONVERSATION: botlistchat.delete_conversation(bot, update, chat_data) # HELP elif action == CallbackActions.HELP: help.help(bot, update) elif action == CallbackActions.CONTRIBUTING: help.contributing(bot, update) elif action == CallbackActions.EXAMPLES: help.examples(bot, update) # BASIC QUERYING elif action == CallbackActions.SELECT_CATEGORY: select_category(bot, update, chat_data) elif action == CallbackActions.SELECT_BOT_FROM_CATEGORY: category = Category.get(id=obj["id"]) send_category(bot, update, chat_data, category) elif action == CallbackActions.SEND_BOT_DETAILS: item = Bot.get(id=obj["id"]) send_bot_details(bot, update, chat_data, item) # FAVORITES elif action == CallbackActions.TOGGLE_FAVORITES_LAYOUT: value = obj["v"] favorites.toggle_favorites_layout(bot, update, value) elif action == CallbackActions.ADD_FAVORITE: favorites.add_favorite_handler(bot, update) elif action == CallbackActions.REMOVE_FAVORITE_MENU: favorites.remove_favorite_menu(bot, update) elif action == CallbackActions.REMOVE_FAVORITE: to_remove = Favorite.get(id=obj["id"]) bot_details = to_remove.bot to_remove.delete_instance() if obj.get("details"): send_bot_details(bot, update, chat_data, bot_details) else: favorites.remove_favorite_menu(bot, update) elif action == CallbackActions.SEND_FAVORITES_LIST: favorites.send_favorites_list(bot, update) elif action == CallbackActions.ADD_ANYWAY: favorites.add_custom(bot, update, obj["u"]) elif action == CallbackActions.ADD_TO_FAVORITES: details = obj.get("details") discreet = obj.get("discreet", False) or details item = Bot.get(id=obj["id"]) favorites.add_favorite(bot, update, item, callback_alert=discreet) if details: send_bot_details(bot, update, chat_data, item) # ACCEPT/REJECT BOT SUBMISSIONS elif action == CallbackActions.APPROVE_REJECT_BOTS: custom_approve_list = [Bot.get(id=obj["id"])] admin.approve_bots(bot, update, override_list=custom_approve_list) elif action == CallbackActions.ACCEPT_BOT: to_accept = Bot.get(id=obj["id"]) admin.edit_bot_category(bot, update, to_accept, CallbackActions.BOT_ACCEPTED) # Run in x minutes, giving the moderator enough time to edit bot details job_queue.run_once( lambda b, job: botlistchat. notify_group_submission_accepted(b, job, to_accept), settings.BOT_ACCEPTED_IDLE_TIME * 60, ) elif action == CallbackActions.RECOMMEND_MODERATOR: bot_in_question = Bot.get(id=obj["id"]) admin.recommend_moderator(bot, update, bot_in_question, obj["page"]) elif action == CallbackActions.SELECT_MODERATOR: bot_in_question = Bot.get(id=obj["bot_id"]) moderator = User.get(id=obj["uid"]) admin.share_with_moderator(bot, update, bot_in_question, moderator) admin.approve_bots(bot, update, obj["page"]) elif action == CallbackActions.REJECT_BOT: to_reject = Bot.get(id=obj["id"]) notification = obj.get("ntfc", True) admin.reject_bot_submission( bot, update, None, to_reject, verbose=False, notify_submittant=notification, ) admin.approve_bots(bot, update, obj["page"]) elif action == CallbackActions.BOT_ACCEPTED: to_accept = Bot.get(id=obj["bid"]) category = Category.get(id=obj["cid"]) admin.accept_bot_submission(bot, update, to_accept, category) elif action == CallbackActions.COUNT_THANK_YOU: new_count = obj.get("count", 1) basic.count_thank_you(bot, update, new_count) # ADD BOT # elif action == CallbackActions.ADD_BOT_SELECT_CAT: # category = Category.get(id=obj['id']) # admin.add_bot(bot, update, chat_data, category) # EDIT BOT elif action == CallbackActions.EDIT_BOT: to_edit = Bot.get(id=obj["id"]) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_SELECT_CAT: to_edit = Bot.get(id=obj["id"]) admin.edit_bot_category(bot, update, to_edit) elif action == CallbackActions.EDIT_BOT_CAT_SELECTED: to_edit = Bot.get(id=obj["bid"]) cat = Category.get(id=obj["cid"]) botproperties.change_category(bot, update, to_edit, cat) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_COUNTRY: to_edit = Bot.get(id=obj["id"]) botproperties.set_country_menu(bot, update, to_edit) elif action == CallbackActions.SET_COUNTRY: to_edit = Bot.get(id=obj["bid"]) if obj["cid"] == "None": country = None else: country = Country.get(id=obj["cid"]) botproperties.set_country(bot, update, to_edit, country) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_DESCRIPTION: to_edit = Bot.get(id=obj["id"]) botproperties.set_text_property(bot, update, chat_data, "description", to_edit) elif action == CallbackActions.EDIT_BOT_EXTRA: to_edit = Bot.get(id=obj["id"]) # SAME IS DONE HERE, but manually botproperties.set_text_property(bot, update, chat_data, "extra", to_edit) elif action == CallbackActions.EDIT_BOT_NAME: to_edit = Bot.get(id=obj["id"]) botproperties.set_text_property(bot, update, chat_data, "name", to_edit) elif action == CallbackActions.EDIT_BOT_USERNAME: to_edit = Bot.get(id=obj["id"]) botproperties.set_text_property(bot, update, chat_data, "username", to_edit) # elif action == CallbackActions.EDIT_BOT_KEYWORDS: # to_edit = Bot.get(id=obj['id']) # botproperties.set_keywords_init(bot, update, chat_data, to_edit) elif action == CallbackActions.APPLY_ALL_CHANGES: to_edit = Bot.get(id=obj["id"]) admin.apply_all_changes(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_INLINEQUERIES: to_edit = Bot.get(id=obj["id"]) value = bool(obj["value"]) botproperties.toggle_value(bot, update, "inlinequeries", to_edit, value) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_OFFICIAL: to_edit = Bot.get(id=obj["id"]) value = bool(obj["value"]) botproperties.toggle_value(bot, update, "official", to_edit, value) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_OFFLINE: to_edit = Bot.get(id=obj["id"]) value = bool(obj["value"]) botproperties.toggle_value(bot, update, "offline", to_edit, value) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.EDIT_BOT_SPAM: to_edit = Bot.get(id=obj["id"]) value = bool(obj["value"]) botproperties.toggle_value(bot, update, "spam", to_edit, value) admin.edit_bot(bot, update, chat_data, to_edit) elif action == CallbackActions.CONFIRM_DELETE_BOT: to_delete = Bot.get(id=obj["id"]) botproperties.delete_bot_confirm(bot, update, to_delete) elif action == CallbackActions.DELETE_BOT: to_edit = Bot.get(id=obj["id"]) botproperties.delete_bot(bot, update, to_edit) # send_category(bot, update, chat_data, to_edit.category) elif action == CallbackActions.ACCEPT_SUGGESTION: suggestion = Suggestion.get(id=obj["id"]) components.botproperties.accept_suggestion( bot, update, suggestion) admin.approve_suggestions(bot, update, page=obj["page"]) elif action == CallbackActions.REJECT_SUGGESTION: suggestion = Suggestion.get(id=obj["id"]) suggestion.delete_instance() admin.approve_suggestions(bot, update, page=obj["page"]) elif action == CallbackActions.CHANGE_SUGGESTION: suggestion = Suggestion.get(id=obj["id"]) botproperties.change_suggestion(bot, update, suggestion, page_handover=obj["page"]) elif action == CallbackActions.SWITCH_SUGGESTIONS_PAGE: page = obj["page"] admin.approve_suggestions(bot, update, page) elif action == CallbackActions.SWITCH_APPROVALS_PAGE: admin.approve_bots(bot, update, page=obj["page"]) elif action == CallbackActions.SET_NOTIFICATIONS: set_notifications(bot, update, obj["value"]) elif action == CallbackActions.NEW_BOTS_SELECTED: show_new_bots(bot, update, chat_data, back_button=True) elif action == CallbackActions.ABORT_SETTING_KEYWORDS: to_edit = Bot.get(id=obj["id"]) admin.edit_bot(bot, update, chat_data, to_edit) # SENDING BOTLIST elif action == CallbackActions.SEND_BOTLIST: silent = obj.get("silent", False) re_send = obj.get("re", False) botlist.send_botlist(bot, update, resend=re_send, silent=silent) elif action == CallbackActions.RESEND_BOTLIST: botlist.send_botlist(bot, update, resend=True, silent=True) # BROADCASTING elif action == "send_broadcast": broadcasts.send_broadcast(bot, update, user_data) elif action == "pin_message": broadcasts.pin_message(bot, update, obj["mid"]) elif action == "add_thank_you": basic.add_thank_you_button(bot, update, obj["cid"], obj["mid"]) # EXPLORING elif action == CallbackActions.EXPLORE_NEXT: explore.explore(bot, update, chat_data) except Exception as e: traceback.print_exc() # get the callback action in plaintext actions = dict(CallbackActions.__dict__) a = next(k for k, v in actions.items() if v == obj.get("a")) util.send_md_message( bot, settings.DEVELOPER_ID, "Exception in callback query for {}:\n{}\n\nWith CallbackAction {}\n\nWith data:\n{}" .format( user.markdown_short, util.escape_markdown(e), util.escape_markdown(a), util.escape_markdown(str(obj)), ), ) finally: bot.answerCallbackQuery(update.callback_query.id) return ConversationHandler.END
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 toggle_favorites_layout(bot, update, value): uid = util.uid_from_update(update) user = User.from_update(update) user.favorites_layout = value user.save() send_favorites_list(bot, update)
from botlistbot.models import Bot from botlistbot.models import User if __name__ == '__main__': all_bots = Bot.select() for bot in all_bots: if bot.spam is None: bot.spam = False if bot.official is None: bot.official = False if bot.inlinequeries is None: bot.inlinequeries = False if bot.offline is None: bot.offline = False bot.save() all_users = User.select() for user in all_users: if user.favorites_layout is None: user.favorites_layout = 'categories' user.save()