Example #1
0
def on_pack_name(update: Update, context: CallbackContext):
    logger.info('user selected the pack name from the keyboard')
    logger.debug('user_data: %s', context.user_data)

    if re.search(r'^GO BACK$', update.message.text, re.I):
        with session_scope() as session:
            pack_titles = [t.title for t in session.query(Pack.title).filter_by(user_id=update.effective_user.id).all()]

        markup = Keyboard.from_list(pack_titles)
        update.message.reply_text(Strings.ADD_STICKER_SELECT_PACK, reply_markup=markup)

        return Status.WAITING_TITLE

    # the buttons list has the name without "_by_botusername"
    selected_name = '{}_by_{}'.format(update.message.text, context.bot.username)

    with session_scope() as session:
        pack = session.query(Pack).filter_by(name=selected_name, user_id=update.effective_user.id).first().name
        pack_name = pack.name
        pack_animated = pack.is_animated

    if not pack_name:
        logger.error('user %d does not have any pack with name %s', update.effective_user.id, selected_name)
        update.message.reply_text(Strings.ADD_STICKER_SELECTED_NAME_DOESNT_EXIST)
        # do not reset the user status
        return Status.WAITING_NAME

    logger.info('selected pack is animated: %s', pack_animated)

    context.user_data['pack'] = dict(name=pack_name, animated=pack_animated)
    pack_link = utils.name2link(pack_name)
    base_string = Strings.ADD_STICKER_PACK_SELECTED_STATIC if not pack_animated else Strings.ADD_STICKER_PACK_SELECTED_ANIMATED
    update.message.reply_html(base_string.format(pack_link), reply_markup=Keyboard.HIDE)

    return Status.WAITING_STATIC_STICKERS
Example #2
0
def on_cleanup_command(update: Update, context: CallbackContext):
    logger.info('/cleanup')

    # packs = db.get_user_packs(update.effective_user.id, as_namedtuple=True)
    with session_scope() as session:
        packs = session.query(Pack).filter_by(
            user_id=update.effective_user.id).all()
        packs = [(p.title, p.name, p.is_animated) for p in packs]

    if not packs:
        update.message.reply_text(Strings.LIST_NO_PACKS)
        return

    update.message.reply_html(Strings.CLEANUP_WAIT)

    packs_to_delete = list()
    for pack in packs:
        logger.debug('checking pack: %s', pack[1])

        request_payload = dict(user_id=update.effective_user.id, name=pack[1])

        try:
            context.bot.get_sticker_set(**request_payload)
        except TelegramError as telegram_error:
            if telegram_error.message == 'Stickerset_invalid':
                logger.debug('this pack will be removed from the db (%s)',
                             telegram_error.message)
                packs_to_delete.append(
                    pack)  # list of packs we will have to delete
            else:
                logger.debug('api exception: %s', telegram_error.message)

    if not packs_to_delete:
        update.message.reply_text(Strings.CLEANUP_NO_PACK)
        return

    with session_scope() as session:
        for _, pack_name, _ in packs_to_delete:
            logger.info('deleting pack from db...')
            session.query(Pack).filter(
                Pack.user_id == update.effective_user.id,
                Pack.name == pack_name).delete()

        logger.info('done')

    packs_links = [
        '<a href="{}">{}</a>'.format(utils.name2link(pack[1]), pack[0])
        for pack in packs_to_delete
    ]

    update.message.reply_html(Strings.CLEANUP_HEADER +
                              '• {}'.format('\n• '.join(packs_links)))

    return ConversationHandler.END  # /cleanup should end whatever conversation the user was having
Example #3
0
def on_forgetme_command(update: Update, _):
    logger.info('/forgetme')

    with session_scope() as session:
        deleted_rows = session.query(Pack).filter(Pack.user_id==update.effective_user.id).delete()
        logger.info('deleted rows: %d', deleted_rows or 0)

    update.message.reply_text(Strings.FORGETME_SUCCESS)
Example #4
0
def on_forgetme_command(update: Update, _):
    logger.info('/forgetme')

    with session_scope() as session:
        deleted_rows = session.query(Pack).filter(
            Pack.user_id == update.effective_user.id).delete()
        logger.info('deleted rows: %d', deleted_rows or 0)

    update.message.reply_text(Strings.FORGETME_SUCCESS)

    return ConversationHandler.END  # /forgetme should end whatever conversation the user was having
Example #5
0
def check_pack_name(user_id: int, pack_name: str, context: CallbackContext) -> [None, str]:
    pack_link = utils.name2link(pack_name, context.bot.username)

    if not re.search(r"^[a-z0-9][a-z0-9_]+[a-z0-9]$", pack_name, re.I):  # needs to be improved
        return Strings.READD_INVALID_PACK_NAME_PATTERN

    if not pack_name.endswith(PACK_SUFFIX):
        return Strings.READD_WRONG_PACK_NAME.format(pack_link, PACK_SUFFIX)

    with session_scope() as session:
        pack_exists = session.query(Pack).filter_by(name=pack_name, user_id=user_id).first() is not None

    if pack_exists:
        return Strings.READD_PACK_EXISTS.format(pack_link)
Example #6
0
def on_list_command(update: Update, _):
    logger.info('/list')

    # packs = db.get_user_packs(update.effective_user.id, as_namedtuple=True)
    with session_scope() as session:
        packs = session.query(Pack).filter_by(user_id=update.effective_user.id).all()
        packs = packs[:100]  # can't include more than 100 entities
        strings_list = ['<a href="{}">{}</a>'.format(utils.name2link(pack.name), pack.title) for pack in packs]

    if not strings_list:
        update.message.reply_text(Strings.LIST_NO_PACKS)
        return

    update.message.reply_html('• {}'.format('\n• '.join(strings_list)))
Example #7
0
def on_add_command(update: Update, _):
    logger.info('/add')

    with session_scope() as session:
        pack_titles = [t.title for t in session.query(Pack.title).filter_by(user_id=update.effective_user.id).all()]

    if not pack_titles:
        update.message.reply_text(Strings.ADD_STICKER_NO_PACKS)

        return ConversationHandler.END
    else:
        markup = Keyboard.from_list(pack_titles)
        update.message.reply_text(Strings.ADD_STICKER_SELECT_PACK, reply_markup=markup)

        return Status.WAITING_TITLE
Example #8
0
def on_list_command(update: Update, _):
    logger.info('/list')

    # packs = db.get_user_packs(update.effective_user.id, as_namedtuple=True)
    with session_scope() as session:
        packs = session.query(Pack).filter_by(user_id=update.effective_user.id).order_by(Pack.title).all()
        packs = packs[:98]  # can't include more than 100 entities
        strings_list = ['<a href="{}">{}</a> ({})'.format(utils.name2link(pack.name), pack.title, 'a' if pack.is_animated else 's') for pack in packs]

    if not strings_list:
        update.message.reply_text(Strings.LIST_NO_PACKS)
        return

    update.message.reply_html('• {}'.format('\n• '.join(strings_list)) + Strings.LIST_FOOTER)

    return ConversationHandler.END  # /list should end whatever conversation the user was having
Example #9
0
def on_pack_name_receive(update: Update, context: CallbackContext):
    logger.info('received possible pack name (link)')
    logger.debug('user_data: %s', context.user_data)

    candidate_name = update.message.text
    max_name_len = 64 - (len(context.bot.username) + 4)
    if len(candidate_name) > max_name_len:
        logger.info('pack name too long (%d/%d)', len(candidate_name),
                    max_name_len)
        update.message.reply_text(
            Strings.PACK_NAME_TOO_LONG.format(len(update.message.text),
                                              max_name_len))
        # do not change the user status and let him send another name
        return Status.CREATE_WAITING_NAME

    if not re.search(r'^[a-z](?!__)\w+$', candidate_name, re.IGNORECASE):
        logger.info('pack name not valid: %s', update.message.text)
        update.message.reply_html(Strings.PACK_NAME_INVALID)
        # do not change the user status and let him send another name
        return Status.CREATE_WAITING_NAME

    name_already_used = False
    with session_scope() as session:
        # https://stackoverflow.com/a/34112760
        if session.query(Pack).filter(
                Pack.user_id == update.effective_user.id,
                Pack.name == candidate_name).first() is not None:
            logger.info('pack name already saved: %s', candidate_name)
            name_already_used = True

    if name_already_used:
        update.message.reply_text(Strings.PACK_NAME_DUPLICATE)
        # do not change the user status and let him send another name
        return Status.CREATE_WAITING_NAME

    logger.info('valid pack name: %s', candidate_name)

    context.user_data['pack']['name'] = candidate_name

    if context.user_data['pack']['animated']:
        text = Strings.PACK_CREATION_WAITING_FIRST_ANIMATED_STICKER
    else:
        text = Strings.PACK_CREATION_WAITING_FIRST_STATIC_STICKER

    update.message.reply_text(text)

    return Status.CREATE_WAITING_FIRST_STICKER
Example #10
0
def on_pack_title(update: Update, context: CallbackContext):
    logger.info('user selected the pack title from the keyboard')

    selected_title = update.message.text
    user_id = update.effective_user.id

    with session_scope() as session:
        packs_by_title = session.query(Pack).filter_by(title=selected_title, user_id=user_id).order_by(Pack.name).all()

        # for some reason, accessing a Pack attribute outside of a session
        # raises an error: https://docs.sqlalchemy.org/en/13/errors.html#object-relational-mapping
        # so we preload the list here in case we're going to need it later, to avoid a more complex handling
        # of the session
        by_bot_part = '_by_' + context.bot.username
        pack_names = [pack.name.replace(by_bot_part, '', 1) for pack in packs_by_title]  # strip the '_by_bot' part
        pack_animated = packs_by_title[0].is_animated  # we need this in case there's only one pack and we need to know whether it is animated or not

    if not packs_by_title:
        logger.error('cannot find any pack with this title: %s', selected_title)
        update.message.reply_text(Strings.ADD_STICKER_SELECTED_TITLE_DOESNT_EXIST.format(selected_title[:150]))
        # do not change the user status
        return Status.ADD_WAITING_TITLE

    if len(packs_by_title) > 1:
        logger.info('user has multiple packs with this title: %s', selected_title)

        markup = Keyboard.from_list(pack_names, add_back_button=True)

        # list with the links to the involved packs
        pack_links = ['<a href="{}">{}</a>'.format(utils.name2link(pack_name, bot_username=context.bot.username), pack_name) for pack_name in pack_names]
        text = Strings.ADD_STICKER_SELECTED_TITLE_MULTIPLE.format(selected_title, '\n• '.join(pack_links))
        update.message.reply_html(text, reply_markup=markup)

        return Status.ADD_WAITING_NAME  # we now have to wait for the user to tap on a pack name

    logger.info('there is only one pack with the selected title (animated: %s), proceeding...', pack_animated)
    pack_name = '{}_by_{}'.format(pack_names[0], context.bot.username)

    context.user_data['pack'] = dict(name=pack_name, animated=pack_animated)
    pack_link = utils.name2link(pack_name)
    base_string = Strings.ADD_STICKER_PACK_SELECTED_STATIC if not pack_animated else Strings.ADD_STICKER_PACK_SELECTED_ANIMATED
    update.message.reply_html(base_string.format(pack_link), reply_markup=Keyboard.HIDE)

    if pack_animated:
        return Status.WAITING_ANIMATED_STICKERS
    else:
        return Status.WAITING_STATIC_STICKERS
Example #11
0
def on_count_command(update: Update, context: CallbackContext):
    logger.info('/count')

    with session_scope() as session:
        packs = session.query(Pack).filter_by(
            user_id=update.effective_user.id).order_by(Pack.title).all()
        packs = [(p.title, p.name, p.is_animated) for p in packs]

    if not packs:
        update.message.reply_text(Strings.LIST_NO_PACKS)
        return

    update.message.reply_html("Hold on, this might take some time...")

    results_list = []
    for pack in packs:
        logger.debug('checking pack: %s', pack[1])

        pack_result_dict = dict(title=pack[0], name=pack[1], result=None)

        try:
            sticker_set = context.bot.get_sticker_set(
                user_id=update.effective_user.id, name=pack[1])
            pack_result_dict['result'] = len(sticker_set.stickers)
        except TelegramError as telegram_error:
            logger.debug('api exception: %s', telegram_error.message)
            pack_result_dict['result'] = telegram_error.message

        results_list.append(pack_result_dict)

    strings_list = [
        '<a href="{}">{}</a>: {}'.format(utils.name2link(p['name']),
                                         p['title'], p['result'])
        for p in results_list
    ]

    update.message.reply_html('• {}'.format('\n• '.join(strings_list)))
Example #12
0
def on_first_sticker_receive(update: Update, context: CallbackContext):
    logger.info('first sticker of the pack received')
    logger.debug('user_data: %s', context.user_data)

    animated_pack = context.user_data['pack']['animated']
    if update.message.document:
        animated_sticker = False
    else:
        animated_sticker = update.message.sticker.is_animated

    if animated_pack and not animated_sticker:
        logger.info('invalid sticker: static sticker for an animated pack')
        update.message.reply_text(Strings.ADD_STICKER_EXPECTING_ANIMATED)
        return Status.CREATE_WAITING_FIRST_STICKER
    elif not animated_pack and animated_sticker:
        logger.info('invalid sticker: animated sticker for a static pack')
        update.message.reply_text(Strings.ADD_STICKER_EXPECTING_STATIC)
        return Status.CREATE_WAITING_FIRST_STICKER
    else:
        logger.info(
            'sticker type ok (animated pack: %s, animated sticker: %s)',
            animated_pack, animated_sticker)

    title, name = context.user_data['pack'].get(
        'title', None), context.user_data['pack'].get('name', None)
    if not title or not name:
        logger.error('pack title or name missing (title: %s, name: %s)', title,
                     name)
        update.message.reply_text(
            Strings.PACK_CREATION_FIRST_STICKER_PACK_DATA_MISSING)

        context.user_data.pop('pack', None)  # remove temp info

        return ConversationHandler.END

    full_name = '{}_by_{}'.format(name, context.bot.username)

    user_emojis = context.user_data['pack'].pop('emojis',
                                                None)  # we also remove them
    sticker = StickerFile(bot=context.bot,
                          message=update.message,
                          emojis=user_emojis)
    sticker.download()

    try:
        logger.debug('executing API request...')
        request_payload = dict(
            user_id=update.effective_user.id,
            title=title,
            name=full_name,
            emojis=''.join(sticker.emojis),
        )

        sticker.create_set(**request_payload)
    except (error.PackInvalid, error.NameInvalid,
            error.NameAlreadyOccupied) as e:
        logger.error('Telegram error while creating stickers pack: %s',
                     e.message)
        if isinstance(e, error.NameAlreadyOccupied):
            # there's already a pack with that link
            update.message.reply_html(
                Strings.PACK_CREATION_ERROR_DUPLICATE_NAME.format(
                    utils.name2link(full_name)))
        elif isinstance(e, (error.PackInvalid, error.NameInvalid)):
            update.message.reply_text(Strings.PACK_CREATION_ERROR_INVALID_NAME)

        context.user_data['pack'].pop('name', None)  # remove pack name
        sticker.close()

        return Status.CREATE_WAITING_NAME  # do not continue, wait for another name
    except error.InvalidAnimatedSticker as e:
        logger.error('Telegram error while creating animated pack: %s',
                     e.message)
        update.message.reply_html(Strings.ADD_STICKER_INVALID_ANIMATED,
                                  quote=True,
                                  disable_web_page_preview=True)

        return Status.CREATE_WAITING_FIRST_STICKER
    except error.FloodControlExceeded as e:
        logger.error('Telegram error while creating animated pack: %s',
                     e.message)
        retry_in = re.search(r'retry in (\d+) seconds', e.message,
                             re.I).group(1)  # Retry in 8 seconds
        text = Strings.ADD_STICKER_FLOOD_EXCEPTION.format(retry_in)

        update.message.reply_html(text,
                                  quote=True,
                                  disable_web_page_preview=True)

        return ConversationHandler.END  # do not continue, end the conversation
    except error.UnknwonError as e:
        logger.error('Unknown error while creating the pack: %s', e.message)
        update.message.reply_html(
            Strings.PACK_CREATION_ERROR_GENERIC.format(e.message))

        context.user_data.pop('pack', None)  # remove temp data
        sticker.close()

        return ConversationHandler.END  # do not continue, end the conversation
    else:
        # success

        pack_row = Pack(user_id=update.effective_user.id,
                        name=full_name,
                        title=title,
                        is_animated=animated_pack)
        with session_scope() as session:
            session.add(pack_row)

        # db.save_pack(update.effective_user.id, full_name, title)
        pack_link = utils.name2link(full_name)
        update.message.reply_html(
            Strings.PACK_CREATION_PACK_CREATED.format(pack_link))

        sticker.close()  # remove sticker files

        context.user_data['pack']['name'] = full_name
        # do not remove temporary data (user_data['pack']) because we are still adding stickers

        # wait for other stickers
        if animated_pack:
            return Status.WAITING_ANIMATED_STICKERS
        else:
            return Status.WAITING_STATIC_STICKERS
Example #13
0
def add_sticker_to_set(update: Update, context: CallbackContext,
                       animated_pack):
    name = context.user_data['pack'].get('name', None)
    if not name:
        logger.error('pack name missing (%s)', name)
        update.message.reply_text(Strings.ADD_STICKER_PACK_DATA_MISSING)

        context.user_data.pop('pack', None)  # remove temp info

        return ConversationHandler.END

    user_emojis = context.user_data['pack'].pop('emojis',
                                                None)  # we also remove them
    sticker = StickerFile(bot=context.bot,
                          message=update.message,
                          emojis=user_emojis)
    sticker.download(prepare_png=True)

    pack_link = utils.name2link(name)

    # we edit this flag so the 'finally' statement can end the conversation if needed by an 'except'
    end_conversation = False
    try:
        logger.debug('executing request...')
        sticker.add_to_set(name)
    except error.PackFull:
        max_pack_size = MAX_PACK_SIZE_ANIMATED if animated_pack else MAX_PACK_SIZE_STATIC
        update.message.reply_html(Strings.ADD_STICKER_PACK_FULL.format(
            pack_link, max_pack_size),
                                  quote=True)

        end_conversation = True  # end the conversation when a pack is full
    except error.FileDimensionInvalid:
        logger.error('resized sticker has the wrong size: %s', str(sticker))
        update.message.reply_html(
            Strings.ADD_STICKER_SIZE_ERROR.format(*sticker.size), quote=True)
    except error.InvalidAnimatedSticker:
        update.message.reply_html(Strings.ADD_STICKER_INVALID_ANIMATED,
                                  quote=True,
                                  disable_web_page_preview=True)
    except error.PackInvalid:
        # pack name invalid or that pack has been deleted: delete it from the db
        with session_scope() as session:
            deleted_rows = session.query(Pack).filter(
                Pack.user_id == update.effective_user.id,
                Pack.name == name).delete('fetch')
            logger.debug('rows deleted: %d', deleted_rows or 0)

            # get the remaining packs' titles
            pack_titles = [
                t.title for t in session.query(Pack.title).filter_by(
                    user_id=update.effective_user.id).all()
            ]

        if not pack_titles:
            # user doesn't have any other pack to chose from, reset his status
            update.message.reply_html(
                Strings.ADD_STICKER_PACK_NOT_VALID_NO_PACKS.format(pack_link))

            logger.debug('calling sticker.delete()...')
            sticker.close()
            return ConversationHandler.END
        else:
            # make the user select another pack from the keyboard
            markup = Keyboard.from_list(pack_titles)
            update.message.reply_html(
                Strings.ADD_STICKER_PACK_NOT_VALID.format(pack_link),
                reply_markup=markup)
            context.user_data['pack'].pop('name',
                                          None)  # remove temporary data

            logger.debug('calling sticker.delete()...')
            sticker.close()
            return Status.ADD_WAITING_TITLE
    except error.UnknwonError as e:
        update.message.reply_html(Strings.ADD_STICKER_GENERIC_ERROR.format(
            pack_link, e.message),
                                  quote=True)
    except Exception as e:
        logger.error('non-telegram exception while adding a sticker to a set',
                     exc_info=True)
        raise e  # this is not raised
    else:
        if not user_emojis:
            text = Strings.ADD_STICKER_SUCCESS.format(pack_link)
        else:
            text = Strings.ADD_STICKER_SUCCESS_USER_EMOJIS.format(
                pack_link, ''.join(user_emojis))

        update.message.reply_html(text, quote=True)
    finally:
        # this is entered even when we enter the 'else' or we return in an 'except'
        # https://stackoverflow.com/a/19805746
        logger.debug('calling sticker.close()...')
        sticker.close()

        if end_conversation:
            return ConversationHandler.END

        if animated_pack:
            return Status.WAITING_ANIMATED_STICKERS
        else:
            return Status.WAITING_STATIC_STICKERS
Example #14
0
def on_first_sticker_receive(update: Update, context: CallbackContext):
    logger.info('first sticker of the pack received')
    logger.debug('user_data: %s', context.user_data)

    title, name = context.user_data['pack'].get(
        'title', None), context.user_data['pack'].get('name', None)
    if not title or not name:
        logger.error('pack title or name missing (title: %s, name: %s)', title,
                     name)
        update.message.reply_text(
            Strings.PACK_CREATION_FIRST_STICKER_PACK_DATA_MISSING)

        context.user_data.pop('pack', None)  # remove temp info

        return ConversationHandler.END

    full_name = '{}_by_{}'.format(name, context.bot.username)

    sticker = StickerFile(update.message.sticker or update.message.document,
                          caption=update.message.caption)
    sticker.download(prepare_png=True)

    try:
        logger.debug('executing API request...')
        StickerFile.create_set(
            bot=context.bot,
            user_id=update.effective_user.id,
            title=title,
            name=full_name,
            emojis=sticker.emoji,
            # we need to use an input file becase a tempfile.SpooledTemporaryFile has a 'name' attribute which
            # makes python-telegram-bot retrieve the file's path using os (https://github.com/python-telegram-bot/python-telegram-bot/blob/2a3169a22f7227834dd05a35f90306375136e41a/telegram/files/inputfile.py#L58)
            #  to populate the 'filename' attribute, which would result an exception since it is
            #  a byte object. That means we have to do it ourself by  creating the InputFile and
            #  assigning it a custom 'filename'
            png_sticker=sticker.png_input_file)
    except (error.PackInvalid, error.NameInvalid,
            error.NameAlreadyOccupied) as e:
        logger.error('Telegram error while creating stickers pack: %s',
                     e.message)
        if isinstance(e, error.NameAlreadyOccupied):
            # there's already a pack with that link
            update.message.reply_html(
                Strings.PACK_CREATION_ERROR_DUPLICATE_NAME.format(
                    utils.name2link(full_name)))
        elif isinstance(e, (error.PackInvalid, error.NameInvalid)):
            update.message.reply_text(Strings.PACK_CREATION_ERROR_INVALID_NAME)

        context.user_data['pack'].pop('name', None)  # remove pack name
        sticker.close()

        return WAITING_NAME  # do not continue, wait for another name
    except error.UnknwonError as e:
        logger.error('Unknown error while creating the pack: %s', e.message)
        update.message.reply_html(
            Strings.PACK_CREATION_ERROR_GENERIC.format(e.message))

        context.user_data.pop('pack', None)  # remove temp data
        sticker.close()

        return ConversationHandler.END  # do not continue, end the conversation
    else:
        # success

        pack_row = Pack(user_id=update.effective_user.id,
                        name=full_name,
                        title=title)
        with session_scope() as session:
            session.add(pack_row)

        # db.save_pack(update.effective_user.id, full_name, title)
        pack_link = utils.name2link(full_name)
        update.message.reply_html(
            Strings.PACK_CREATION_PACK_CREATED.format(pack_link))

        sticker.close()  # remove sticker files

        context.user_data['pack']['name'] = full_name
        # do not remove temporary data (user_data['pack']) because we are still adding stickers

        return ADDING_STICKERS  # wait for other stickers
Example #15
0
def process_pack(pack_name: str, update: Update, context: CallbackContext):
    pack_link = utils.name2link(pack_name, context.bot.username)

    warning_text = check_pack_name(update.effective_user.id, pack_name, context)
    if warning_text:
        update.message.reply_html(warning_text)
        return Status.WAITING_STICKER_OR_PACK_NAME

    refresh_dummy_file = False
    if context.user_data.get("dummy_png_file", None):
        now = datetime.datetime.utcnow()
        # refresh every two weeks
        refresh_on = context.user_data["dummy_png_file"]["generated_on"] + datetime.timedelta(14)
        if now > refresh_on:
            refresh_dummy_file = True
        else:
            dummy_png: File = context.user_data["dummy_png_file"]["file"]
    else:
        refresh_dummy_file = True

    if refresh_dummy_file:
        logger.debug("refreshing dummy png file")
        with open("assets/dummy_sticker.png", "rb") as f:
            dummy_png: File = context.bot.upload_sticker_file(update.effective_user.id, f)

        context.user_data["dummy_png_file"] = {"file": dummy_png, "generated_on": datetime.datetime.utcnow()}

    try:
        context.bot.add_sticker_to_set(
            user_id=update.effective_user.id,
            name=pack_name,
            png_sticker=dummy_png.file_id,
            emojis=DUMMY_EMOJI
        )
        logger.debug("successfully added dummy sticker to pack <%s>", pack_name)
    except (TelegramError, BadRequest) as e:
        error_message = e.message.lower()
        if "stickerset_invalid" in error_message:
            update.message.reply_html(Strings.READD_PACK_INVALID.format(pack_link))
            return Status.WAITING_STICKER_OR_PACK_NAME
        else:
            logger.error("/readd: api error while adding dummy sticker to pack <%s>: %s", pack_name, e.message)
            update.message.reply_html(Strings.READD_UNKNOWN_API_EXCEPTION.format(pack_link, e.message))
            return Status.WAITING_STICKER_OR_PACK_NAME

    sticker_set: StickerSet = context.bot.get_sticker_set(pack_name)
    if sticker_set.stickers[-1].emoji == DUMMY_EMOJI:
        sticker_to_remove = sticker_set.stickers[-1]
    else:
        logger.warning("dummy emoji and the emoji of the last sticker in the set do not match")
        sticker_to_remove = None

    pack_row = Pack(
        user_id=update.effective_user.id,
        name=sticker_set.name,
        title=sticker_set.title,
        is_animated=sticker_set.is_animated
    )
    with session_scope() as session:
        session.add(pack_row)

    stickerset_title_link = utils.stickerset_title_link(sticker_set)
    update.message.reply_html(
        Strings.READD_SAVED.format(stickerset_title_link)
    )

    # We do this here to let the API figure out we just added the sticker with that file_id to the pack
    # it will raise an exception anyway though (Sticker_invalid)
    # we might just ignore it. The user now can manage the pack, and can remove the dummy sticker manually
    # Also, it might be the case (IT *IS* THE CASE) that the dummy sticker added to the pack gets its own file_id, so
    # the file_id returned by upload_sticker_file should be used to remove the sticker.
    # We then use the file_id of the last sticker in the pack, but I guess we can't be 100% sure
    # the get_sticker_set request returned the pack with also the dummy sticker we added one second before
    if not sticker_to_remove:
        # the dummy emoji and the emoji of the last sticker in the pack did not match
        update.message.reply_html(Strings.READD_DUMMY_STICKER_NOT_REMOVED)
    else:
        try:
            context.bot.delete_sticker_from_set(sticker=sticker_to_remove.file_id)
            logger.debug("successfully removed dummy sticker from pack <%s>", pack_name)
        except (TelegramError, BadRequest) as e:
            error_message = e.message.lower()
            if "sticker_invalid" in error_message:
                update.message.reply_html(Strings.READD_DUMMY_STICKER_NOT_REMOVED)
            else:
                logger.error("/readd: api error while removing dummy sticker from pack <%s>: %s", pack_name, e.message)
                update.message.reply_html(Strings.READD_DUMMY_STICKER_NOT_REMOVED_UNKNOWN.format(error_message))

    return ConversationHandler.END