def cancel_tagging(self, bot): """Cancel the tagging process.""" if self.tag_mode == TagMode.STICKER_SET and self.current_sticker is not None: keyboard = get_continue_tagging_keyboard( self.current_sticker.file_id) try: call_tg_func( bot, "edit_message_reply_markup", [self.id, self.last_sticker_message_id], {"reply_markup": keyboard}, ) except BadRequest as e: # An update for a reply keyboard has failed (Probably due to button spam) logger = logging.getLogger() if "Message to edit not found" in str( e) or "Message is not modified" in str(e): logger.info("Message to edit has been deleted.") pass else: raise e self.tag_mode = None self.current_sticker = None self.last_sticker_message_id = None
def extract_text(tg_sticker): """Extract the text from a telegram sticker.""" text = None logger = logging.getLogger() try: # Get Image and preprocess it tg_file = call_tg_func(tg_sticker, 'get_file') image_bytes = call_tg_func(tg_file, 'download_as_bytearray') image = Image.open(io.BytesIO(image_bytes)).convert('RGB') image = preprocess_image(image) # Extract text text = image_to_string(image).strip().lower() # Only allow chars and remove multiple spaces to single spaces text = re.sub('[^a-zA-Z\ ]+', '', text) text = re.sub(' +', ' ', text) text = text.strip() if text == '': text = None except TimedOut: logger.info(f'Finally failed on file {tg_sticker.file_id}') pass except BadRequest: logger.info(f'Failed to get image of f{tg_sticker.file_id}') pass except BaseException: sentry.captureException() pass return text
def refresh_stickers(session, sticker_set, bot, refresh_ocr=False, chat=None): """Refresh stickers and set data from telegram.""" # Get sticker set from telegram and create new a Sticker for each sticker stickers = [] try: tg_sticker_set = call_tg_func(bot, 'get_sticker_set', args=[sticker_set.name]) except BadRequest as e: if e.message == 'Stickerset_invalid' or \ e.message == 'Requested data is inaccessible': sticker_set.deleted = True return raise e # Sometimes file ids in telegram seem to randomly change # If this has already happened, merge the two stickers (backup replay) # otherwise, change the file id to the new one for sticker in sticker_set.stickers: try: tg_sticker = call_tg_func(bot, 'get_file', args=[sticker.file_id]) except BadRequest as e: if e.message == 'Wrong file id': session.delete(sticker) continue if tg_sticker.file_id != sticker.file_id: new_sticker = session.query(Sticker).get(tg_sticker.file_id) if new_sticker is not None: merge_sticker(session, sticker, new_sticker) sticker.file_id = tg_sticker.file_id session.commit() for tg_sticker in tg_sticker_set.stickers: # Ignore already existing stickers if we don't need to rescan images sticker = session.query(Sticker).get(tg_sticker.file_id) text = None if sticker is None or refresh_ocr: text = extract_text(tg_sticker) # Create new Sticker. if sticker is None: sticker = Sticker(tg_sticker.file_id) # Only set text, if we got some text from the ocr recognition if text is not None: sticker.text = text add_original_emojis(session, sticker, tg_sticker.emoji) stickers.append(sticker) session.commit() sticker_set.name = tg_sticker_set.name.lower() sticker_set.title = tg_sticker_set.title.lower() sticker_set.stickers = stickers sticker_set.complete = True session.commit()
def cleanup(bot, update, session, chat, user): """Triggering a one time conversion from text changes to tags.""" threshold = datetime.strptime('Jan 1 2000', '%b %d %Y') full_cleanup(session, threshold, update=update) call_tg_func(update.message.chat, 'send_message', ['Cleanup finished.'], {'reply_markup': get_main_keyboard(admin=True)})
def test_broadcast(bot, update, session, chat, user): """Broadcast a message to all users.""" message = update.message.text.split(' ', 1)[1].strip() call_tg_func(bot, 'send_message', [chat.id, message], {'parse_mode': 'Markdown'})
def user_cleanup(session, update): """Do some cleanup tasks for users.""" all_users = session.query(User).all() if update is not None: call_tg_func(update.message.chat, 'send_message', [f'Found {len(all_users)} users']) deleted = 0 for user in all_users: if len(user.changes) == 0 \ and len(user.tasks) == 0 \ and len(user.reports) == 0 \ and len(user.inline_queries) == 0 \ and user.banned is False \ and user.reverted is False \ and user.admin is False \ and user.authorized is False: deleted += 1 session.delete(user) if update is not None: call_tg_func(update.message.chat, 'send_message', [f'User cleanup finished. {deleted} user deleted.'], {'reply_markup': admin_keyboard})
def tag_cleanup(session, update): """Do some cleanup tasks for tags.""" from stickerfinder.helper import blacklist all_tags = session.query(Tag).all() call_tg_func(update.message.chat, 'send_message', [f'Found {len(all_tags)} tags']) for tag in all_tags: # Remove all tags in the blacklist if tag.name in blacklist: session.delete(tag) continue # Remove ignored characters from tag new_name = tag.name for char in ignored_characters: if char in new_name: new_name = new_name.replace(char, '') # Remove hash tags if new_name.startswith('#'): new_name = new_name[1:] # If the new tag with removed chars already exists in the db, remove the old tag. # Otherwise just update the tag name if new_name != tag.name: new_exists = session.query(Tag).get(new_name) if new_exists is not None or new_name == '': session.delete(tag) else: tag.name = new_name call_tg_func(update.message.chat, 'send_message', ['Tag cleanup finished.'], {'reply_markup': admin_keyboard})
def show_sticker(bot, update, session, chat, user): """Show the sticker for the given file id.""" file_id = update.message.text.split(' ', 1)[1].strip() try: call_tg_func(update.message.chat, 'send_sticker', args=[file_id]) except: return "Wrong file id"
def stats(bot, update, session, chat, user): """Send a help text.""" # Users user_count = session.query(User).join(User.changes).group_by(User).count() banned_user_count = session.query(User).filter( User.banned.is_(True)).count() # Tags and emojis tag_count = session.query(Tag).filter(Tag.emoji.is_(False)).count() emoji_count = session.query(Tag).filter(Tag.emoji.is_(False)).count() # Stickers and sticker/text sticker/tag ratio sticker_count = session.query(Sticker).count() tagged_sticker_count = session.query(distinct(sticker_tag.c.sticker_file_id)) \ .join(Tag, sticker_tag.c.tag_name == Tag.name) \ .filter(Tag.emoji.is_(False)) \ .count() text_sticker_count = session.query(Sticker) \ .filter(Sticker.text.isnot(None)) \ .count() # Sticker set stuff sticker_set_count = session.query(StickerSet).count() nsfw_set_count = session.query(StickerSet).filter( StickerSet.nsfw.is_(True)).count() furry_set_count = session.query(StickerSet).filter( StickerSet.furry.is_(True)).count() banned_set_count = session.query(StickerSet).filter( StickerSet.banned.is_(True)).count() not_english_set_count = session.query(StickerSet).filter( StickerSet.is_default_language.is_(False)).count() # Inline queries total_queries_count = session.query(InlineQuery).count() last_day_queries_count = session.query(InlineQuery)\ .filter(InlineQuery.created_at > datetime.now() - timedelta(days=1)) \ .count() stats = f"""Users: {user_count} => banned: {banned_user_count} Sticker sets: {sticker_set_count} => nsfw: {nsfw_set_count} => furry: {furry_set_count} => banned: {banned_set_count} => international: {not_english_set_count} Tags: {tag_count} => emojis: {emoji_count} Stickers: {sticker_count} => with tags: {tagged_sticker_count} => with text: {text_sticker_count} Total queries : {total_queries_count} => last day: {last_day_queries_count} """ call_tg_func(update.message.chat, 'send_message', [stats], {'reply_markup': admin_keyboard})
def cleanup(bot, update, session, chat, user): """Triggering a one time conversion from text changes to tags.""" tag_cleanup(session, update) user_cleanup(session, update) call_tg_func(update.message.chat, 'send_message', ['Cleanup finished.'], {'reply_markup': admin_keyboard})
def set_not_is_default_language(bot, update, session, chat, user): """Change the language of the user to the non default langage.""" user.is_default_language = False keyboard = get_main_keyboard(user) text = "Your tags will now be marked as not english and you will see sticker sets with non-english content." call_tg_func(update.message.chat, 'send_message', [text], {'reply_markup': keyboard})
def send_tag_messages(chat, tg_chat, user, send_set_info=False): """Send next sticker and the tags of this sticker.""" # If we don't have a message, we need to add the inline keyboard to the sticker # Otherwise attach it to the following message. message = current_sticker_tags_message(chat.current_sticker, user, send_set_info=send_set_info) keyboard = get_tagging_keyboard(chat) if not message: response = call_tg_func(tg_chat, 'send_sticker', args=[chat.current_sticker.file_id], kwargs={'reply_markup': keyboard}) chat.last_sticker_message_id = response.message_id else: call_tg_func(tg_chat, 'send_sticker', args=[chat.current_sticker.file_id]) if message: response = call_tg_func(tg_chat, 'send_message', [message], {'reply_markup': keyboard}) chat.last_sticker_message_id = response.message_id
def handle_group_sticker(bot, update, session, chat, user): """Read all stickers. - Handle initial sticker addition. - Detect whether a sticker set is used in a chat or not. """ set_name = update.message.sticker.set_name # The sticker is no longer associated to a sticker set if set_name is None: return # Handle replies to #request messages and tag those stickers with the request tags handle_request_reply(update.message.sticker.file_id, update, session, chat, user) # Check if we know this sticker set. Early return if we don't sticker_set = session.query(StickerSet).get(set_name) if sticker_set is None: return if sticker_set not in chat.sticker_sets: chat.sticker_sets.append(sticker_set) # Set the send sticker to the current sticker for tagging or report. sticker = session.query(Sticker).get(update.message.sticker.file_id) chat.current_sticker = sticker if chat.is_maintenance or chat.is_newsfeed: message = f'StickerSet "{sticker_set.title}" ({sticker_set.name})' keyboard = get_nsfw_ban_keyboard(sticker_set) call_tg_func(update.message.chat, 'send_message', [message], {'reply_markup': keyboard}) return
def set_is_default_language(bot, update, session, chat, user): """Change the language of the user to the default langage.""" user.is_default_language = True keyboard = admin_keyboard if chat.is_maintenance else main_keyboard text = "Your tags will now be marked as english and you won't see any sticker sets with non-english content." call_tg_func(update.message.chat, 'send_message', [text], {'reply_markup': keyboard})
def extract_text(tg_sticker): """Extract the text from a telegram sticker.""" text = None logger = logging.getLogger() try: # Get Image and preprocess it tg_file = call_tg_func(tg_sticker, "get_file") image_bytes = call_tg_func(tg_file, "download_as_bytearray") with Image.open(io.BytesIO(image_bytes)).convert("RGB") as image: image = preprocess_image(image) # Extract text text = image_to_string(image).strip().lower() # Only allow chars and remove multiple spaces to single spaces text = re.sub("[^a-zA-Z\ ]+", "", text) text = re.sub(" +", " ", text) text = text.strip() if text == "": text = None except TimedOut: logger.info(f"Finally failed on file {tg_sticker.file_id}") pass except BadRequest: logger.info(f"Failed to get image of {tg_sticker.file_id}") pass except OSError: logger.info(f"Failed to open image {tg_sticker.file_id}") pass except: sentry.captureException() pass return text
def wrapper(update, context): session = get_session() chat = None 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, update) if config["mode"]["authorized_only"] and not user.authorized: text = "StickerFinder is officially offline. Access will still be granted for [Patreons](https://www.patreon.com/nukesor).\n" text += "Check the repository for the latest database dump in case you want to host your own bot." message.chat.send_message(text, parse_mode="Markdown") session.commit() return if not is_allowed( user, update, admin_only=admin_only, check_ban=check_ban): return chat_id = message.chat_id chat_type = message.chat.type chat = Chat.get_or_create(session, chat_id, chat_type) if not is_allowed(user, update, chat=chat, private=private): return response = func(context.bot, update, session, chat, user) session.commit() # Respond to user if hasattr(update, "message") and response is not None: message.chat.send_message(response) # A user banned the bot except Unauthorized: if chat is not None: session.delete(chat) # A group chat has been converted to a super group. except ChatMigrated: if chat is not None: session.delete(chat) # Handle all not telegram relatated exceptions except Exception as e: if not ignore_exception(e): traceback.print_exc() sentry.captureException() if send_message and message: session.close() call_tg_func(message.chat, "send_message", args=[error_text]) raise finally: session.close()
def start(bot, update, session, chat, user): """Send a help text.""" if chat.is_maintenance or chat.is_newsfeed: call_tg_func(update.message.chat, 'send_message', ['Hello there'], {'reply_markup': admin_keyboard}) else: call_tg_func(update.message.chat, 'send_message', [start_text], {'reply_markup': main_keyboard, 'parse_mode': 'Markdown'})
def send_help_text(bot, update, session, chat, user): """Send a help text.""" if user.admin: call_tg_func(update.message.chat, 'send_message', [admin_help_text], {'reply_markup': main_keyboard, 'parse_mode': 'Markdown'}) elif not user.admin: call_tg_func(update.message.chat, 'send_message', [help_text], {'reply_markup': main_keyboard, 'parse_mode': 'Markdown'})
def handle_deluxe_set(session, context): """Handle the deluxe button in newsfeed chats.""" sticker_set = session.query(StickerSet).get(context.payload) sticker_set.deluxe = not sticker_set.deluxe keyboard = get_nsfw_ban_keyboard(sticker_set) call_tg_func(context.query.message, "edit_reply_markup", [], {"reply_markup": keyboard})
def handle_tag_next(session, bot, user, query, chat, tg_chat): """Send the next sticker for tagging.""" current_sticker = chat.current_sticker handle_next(session, bot, chat, tg_chat, user) if chat.current_sticker is not None: keyboard = get_fix_sticker_tags_keyboard(current_sticker.file_id) call_tg_func(query.message, 'edit_reply_markup', [], {'reply_markup': keyboard})
def handle_change_set_language(session, context): """Handle the change language button in newsfeed chats.""" sticker_set = session.query(StickerSet).get(context.payload.lower()) sticker_set.international = not sticker_set.international keyboard = get_nsfw_ban_keyboard(sticker_set) call_tg_func(context.query.message, "edit_reply_markup", [], {"reply_markup": keyboard})
def search_sticker_sets(session, update, context, inline_query_request): """Query sticker sets.""" # Get all matching stickers matching_sets, duration = get_matching_sticker_sets(session, context) # Calculate the next offset. 'done' means there are no more results. next_offset = get_next_set_offset(context, matching_sets) inline_query_request.duration = duration inline_query_request.next_offset = (next_offset.split(":", 1)[1] if next_offset != "done" else next_offset) # Stuff for debugging, since I need that all the time if False: import pprint pprint.pprint("\n\nNext: ") pprint.pprint(context.offset) pprint.pprint(matching_sets) # Create a result list of max 50 cached sticker objects results = [] for sticker_set in matching_sets: sticker_set = sticker_set[0] url = f"https://telegram.me/addstickers/{sticker_set.name}" input_message_content = InputTextMessageContent(url) # Hash the sticker set name, since they tend to be super long sticker_set_hash = hashlib.md5(sticker_set.name.encode()).hexdigest() results.append( InlineQueryResultArticle( f"{context.inline_query_id}:{sticker_set_hash}", title=sticker_set.title, description=sticker_set.name, url=url, input_message_content=input_message_content, )) for index in range(0, 5): if index < len(sticker_set.stickers): sticker_id = sticker_set.stickers[index].id file_id = sticker_set.stickers[index].file_id results.append( InlineQueryResultCachedSticker( f"{context.inline_query_id}:{sticker_id}", sticker_file_id=file_id, )) call_tg_func( update.inline_query, "answer", args=[results], kwargs={ "next_offset": next_offset, "cache_time": 1, "is_personal": True, }, )
def deluxe_user(bot, update, session, chat, user): """Limit the result set of a user's search to deluxe stickers.""" if user.deluxe: return "You're already opt in for deluxe sticker packs." user.deluxe = True call_tg_func(update.message.chat, 'send_message', ["You will now only see deluxe sticker sets."], {'reply_markup': get_main_keyboard(user)})
def undeluxe_user(bot, update, session, chat, user): """Change the language of the user to the non default langage.""" if not user.deluxe: return "You're already opt out of deluxe sticker packs." user.deluxe = False call_tg_func(update.message.chat, 'send_message', ["You will now see all sticker sets again."], {'reply_markup': get_main_keyboard(user)})
def cancel(bot, update, session, chat, user): """Send a help text.""" if not send_tagged_count_message(session, bot, user, chat): keyboard = admin_keyboard if chat.is_maintenance else main_keyboard call_tg_func(update.message.chat, 'send_message', ['All running commands are canceled'], {'reply_markup': keyboard}) chat.cancel()
def handle_tag_next(session, context): """Send the next sticker for tagging.""" chat = context.chat current_sticker = chat.current_sticker handle_next(session, context.bot, chat, context.tg_chat, context.user) if chat.current_sticker is not None: keyboard = get_fix_sticker_tags_keyboard(current_sticker.file_id) call_tg_func(context.query.message, "edit_reply_markup", [], {"reply_markup": keyboard})
def deluxe_user(bot, update, session, chat, user): """Change the language of the user to the non default langage.""" user.deluxe = not user.deluxe if user.deluxe: text = f"You will only see sticker sets marked as deluxe now." else: text = f"You will now see sticker sets again." keyboard = admin_keyboard if chat.is_maintenance else main_keyboard call_tg_func(update.message.chat, 'send_message', [text], {'reply_markup': keyboard})
def handle_next(session, bot, chat, tg_chat, user): """Handle the /next call or the 'next' button click.""" # We are tagging a whole sticker set. Skip the current sticker if chat.tag_mode == TagMode.STICKER_SET: # Check there is a next sticker stickers = chat.current_sticker.sticker_set.stickers for index, sticker in enumerate(stickers): if sticker == chat.current_sticker and index + 1 < len(stickers): # We found the next sticker. Send the messages and return chat.current_sticker = stickers[index + 1] send_tag_messages(chat, tg_chat, user) return # There are no stickers left, reset the chat and send success message. chat.current_sticker.sticker_set.completely_tagged = True send_tagged_count_message(session, bot, user, chat) tg_chat.send_message('The full sticker set is now tagged.', reply_markup=get_main_keyboard(user)) chat.cancel(bot) # Find a random sticker with no changes elif chat.tag_mode == TagMode.RANDOM: base_query = session.query(Sticker) \ .outerjoin(Sticker.changes) \ .join(Sticker.sticker_set) \ .filter(Change.id.is_(None)) \ .filter(StickerSet.international.is_(False)) \ .filter(StickerSet.banned.is_(False)) \ .filter(StickerSet.nsfw.is_(False)) \ .filter(StickerSet.furry.is_(False)) \ # Let the users tag the deluxe sticker set first. # If there are no more deluxe sets, just tag another random sticker. # Remove the favoring of deluxe stickers until the deluxe pool is bigger again. # sticker = base_query.filter(StickerSet.deluxe.is_(True)) \ # .order_by(func.random()) \ # .limit(1) \ # .one_or_none() # if sticker is None: sticker = base_query \ .order_by(func.random()) \ .limit(1) \ .one_or_none() # No stickers for tagging left :) if not sticker: call_tg_func(tg_chat, 'send_message', ['It looks like all stickers are already tagged :).'], {'reply_markup': get_main_keyboard(user)}) chat.cancel(bot) return # Found a sticker. Send the messages chat.current_sticker = sticker send_tag_messages(chat, tg_chat, user, send_set_info=True)
def handle_deluxe_set_user_chat(session, bot, action, query, payload, user): """Make a set a deluxe set.""" sticker_set = session.query(StickerSet).get(payload) if CallbackResult(action).name == 'ok': sticker_set.deluxe = True elif CallbackResult(action).name == 'ban': sticker_set.deluxe = False keyboard = get_tag_this_set_keyboard(sticker_set, user) call_tg_func(query.message, 'edit_reply_markup', [], {'reply_markup': keyboard})
def handle_change_set_language(session, action, query, payload, chat, tg_chat): """Handle the change language button in newsfeed chats.""" sticker_set = session.query(StickerSet).get(payload.lower()) if CallbackResult(action).name == 'international': sticker_set.is_default_language = False elif CallbackResult(action).name == 'default': sticker_set.is_default_language = True keyboard = get_nsfw_ban_keyboard(sticker_set) call_tg_func(query.message, 'edit_reply_markup', [], {'reply_markup': keyboard})