Exemple #1
0
    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
Exemple #2
0
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
Exemple #3
0
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)})
Exemple #5
0
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'})
Exemple #6
0
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})
Exemple #8
0
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"
Exemple #9
0
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})
Exemple #10
0
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})
Exemple #11
0
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})
Exemple #12
0
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
Exemple #13
0
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
Exemple #14
0
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})
Exemple #15
0
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
Exemple #16
0
        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()
Exemple #17
0
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'})
Exemple #18
0
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'})
Exemple #19
0
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})
Exemple #20
0
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})
Exemple #21
0
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})
Exemple #22
0
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,
        },
    )
Exemple #23
0
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)})
Exemple #24
0
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()
Exemple #26
0
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})
Exemple #27
0
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})
Exemple #28
0
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)
Exemple #29
0
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})
Exemple #30
0
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})