コード例 #1
0
def test_word_bank():
    word_bank = WordBank(local=True,
                         local_path="tests/resources/word_bank.csv")
    random_word = word_bank.get_random(
        exclude=["WID1", "WID2", "WID3", "WID4"])
    tc.assertEqual(random_word.get("word_id"), "WID5")

    random_word = word_bank.get_random(
        exclude=["WID1", "WID2", "WID3", "WID4", "WID5"])
    tc.assertIn("word_id", random_word)
    tc.assertIn("es", random_word)
    tc.assertIn("de", random_word)
    tc.assertIn("examples", random_word)
コード例 #2
0
def run():  # pragma: no cover
    """Run bot"""
    logger.info("Started app")

    global dao, word_bank

    dao = DAO(config.REDIS_HOST)
    word_bank = WordBank(config.WORD_BANK_LOCAL)

    updater = Updater(config.BOT_TOKEN)
    updater.bot.set_my_commands(user_bot_commands)

    updater.job_queue.run_custom(
        send_word,
        job_kwargs=dict(
            trigger="cron",
            day="*",
            hour="10,18,20",
            minute="30",
            # second="10,20,30,40,50,0"  # test
        ))
    updater.job_queue.run_custom(
        lambda x: word_bank.update(),
        job_kwargs=dict(
            trigger="cron",
            day="*",
            hour="7",
            # second="10,20,30,40,50,0"  # test
        ))

    dispatcher = updater.dispatcher

    dispatcher.add_handler(CommandHandler("start", on_start_callback))
    dispatcher.add_handler(CommandHandler("stop", on_stop_callback))
    dispatcher.add_handler(CommandHandler("help", on_help_callback))
    dispatcher.add_handler(CommandHandler("users", on_users_callback))
    dispatcher.add_handler(
        CommandHandler("wordbankinfo", on_wordbankinfo_callback))
    dispatcher.add_handler(
        CommandHandler("blockedwords", on_get_blockwords_callback))
    dispatcher.add_handler(CallbackQueryHandler(inline_keyboard_callbacks))

    updater.start_polling()
    updater.idle()
コード例 #3
0
def test_get_words():
    word_bank = WordBank(local=True,
                         local_path="tests/resources/word_bank.csv")
    word_ids = ["WID1", "WID2", "WID3"]
    result = word_bank.get_words(word_ids)
    expected = [{
        "word_id": 'WID1',
        "de": 'ab und zu',
        "es": 'de vez en cuando'
    }, {
        "word_id": 'WID2',
        "de": 'abändern',
        "es": 'modificar'
    }, {
        "word_id": 'WID3',
        "de": 'abdrehen',
        "es": 'cerrar'
    }]
    assert result == expected
コード例 #4
0
def test_word_bank():
    # user excludes some words and has all levels assigned
    word_bank = WordBank(local=True,
                         local_path="tests/resources/word_bank.csv")
    random_word = word_bank.get_random(
        ['beginner', 'intermediate', 'advanced'],
        exclude=["WID1", "WID2", "WID3", "WID4"])
    tc.assertEqual(random_word.get("word_id"), "WID5")

    # user excludes some words, has only one level assigned, which there are no words for
    word_bank = WordBank(local=True,
                         local_path="tests/resources/word_bank.csv")
    random_word = word_bank.get_random(
        ['advanced'], exclude=["WID1", "WID2", "WID3", "WID4"])
    tc.assertEqual(random_word, {})

    # user excludes some words, has only one level assigned, which there are words for
    random_word = word_bank.get_random(
        ['intermediate'], exclude=["WID1", "WID2", "WID3", "WID4", "WID5"])
    tc.assertIn("word_id", random_word)
    tc.assertIn("es", random_word)
    tc.assertIn("de", random_word)
    tc.assertIn("examples", random_word)

    # user excludes no words and has only one level assigned, which there are words for (including a word with no level)
    random_word = word_bank.get_random(['intermediate'], exclude=[])
    tc.assertIn("word_id", random_word)
    tc.assertIn("es", random_word)
    tc.assertIn("de", random_word)
    tc.assertIn("examples", random_word)
    tc.assertTrue("WID1" == random_word.get("word_id")
                  or "WID2" == random_word.get("word_id"))

    # user excludes no words, and has no levels assigned
    random_word = word_bank.get_random([], exclude=[])
    tc.assertIn("word_id", random_word)
    tc.assertIn("es", random_word)
    tc.assertIn("de", random_word)
    tc.assertIn("examples", random_word)
コード例 #5
0
ファイル: app.py プロジェクト: rooom13/dailyWord-bot
class App:

    ####################
    # Common msgs      #
    ####################
    admin_msg = "This command is reserved for <pre>admins</pre> 😏😏"

    ####################
    # Callbacks        #
    ####################

    def callback_on_help(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        update.message.reply_text(available_commands_msg)

    def callback_on_start(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        chat_id = update.effective_message.chat.id
        # check if user already has levels assigned
        levels = self.dao.get_user_levels(chat_id)
        user_levels = levels if levels else list(utils.POSSIBLE_USER_LEVELS)

        message = update.message or update.callback_query.message
        self.dao.save_user(message, user_levels)

        msg = f"Hello {message.chat.first_name}! " + available_commands_msg
        reply_markup = InlineKeyboardMarkup([
            [InlineKeyboardButton("/stop", callback_data='/stop')]
        ])

        if update.callback_query:
            update.callback_query.answer()
            update.callback_query.edit_message_text(msg, reply_markup=reply_markup)
        else:
            update.effective_message.reply_text(msg, reply_markup=reply_markup)

    def callback_on_stop(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        chat_id = update.effective_message.chat.id
        self.dao.set_user_inactive(chat_id)

        msg = "You will no longer receive words!\n...Unles you use /start"
        reply_markup = InlineKeyboardMarkup([
            [InlineKeyboardButton("/start", callback_data='/start')]
        ])

        if update.callback_query:
            update.callback_query.answer()
            update.callback_query.edit_message_text(msg, reply_markup=reply_markup)
        else:
            update.effective_message.reply_text(msg, reply_markup=reply_markup)

    def callback_on_mylevels(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        # get user information from the message
        chat_id = update.effective_message.chat_id

        # look for the levels of the user in the db
        levels = self.dao.get_user_levels(chat_id)

        # generate answer message and the markup
        answer = utils.build_levels_answer(levels)
        msg, reply_markup = answer.get('msg'), answer.get('reply_markup')

        # answer the user
        if update.callback_query:
            update.callback_query.answer()
            update.callback_query.edit_message_text(msg, reply_markup=reply_markup)
        else:
            update.effective_message.reply_text(msg, reply_markup=reply_markup)

    def callback_on_removelevel(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        # get user information from the message
        chat_id = update.effective_message.chat.id

        callback_data = update.callback_query.data.split(" ")
        level = " ".join(callback_data[1:])

        # remove level from the user
        self.dao.remove_user_level(chat_id, level)

        # show user levels
        self.callback_on_mylevels(update, context)

    def callback_on_addlevel(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        # get user information from the message
        chat_id = update.effective_message.chat.id

        callback_data = update.callback_query.data.split(" ")
        level = " ".join(callback_data[1:])

        # add level to the user
        self.dao.add_user_level(chat_id, level)

        # show user levels
        self.callback_on_mylevels(update, context)

    def callback_on_info(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        msg = f"""Version: <i>{config.VERSION}</i> deployed on {self.start_date}
        \nWord bank info:\n - {len(self.word_bank.df.index)} words, last updated on {self.word_bank.last_updated_at}"""
        update.message.reply_text(msg, parse_mode='HTML')

    def callback_on_get_blockwords_callback(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover

        chat_id = update.effective_message.chat.id

        blocked_word_ids = self.dao.get_user_blocked_words(chat_id)
        blocked_words = self.word_bank.get_words(blocked_word_ids)

        inline_keyboard_buttons = []

        for blocked_word in blocked_words:
            word_id = blocked_word["word_id"]
            german_word = blocked_word["de"]
            spanish_word = blocked_word["es"]
            spanish_and_german_word = "🇪🇸" + spanish_word + " | " + "🇩🇪" + german_word
            inline_keyboard_buttons.append([InlineKeyboardButton(spanish_and_german_word, callback_data=f'/unblockword_fbw {word_id}')])

        reply_markup = InlineKeyboardMarkup(inline_keyboard_buttons)

        msg = "These are your blocked words. Click to unblock them." if inline_keyboard_buttons else "You don't have any blocked words."

        if update.callback_query:
            update.callback_query.edit_message_text(msg, reply_markup=reply_markup)
        else:
            update.message.reply_text(msg, reply_markup=reply_markup)

    def callback_on_blockword(self, update: Update, context: CallbackContext):  # pragma: no cover
        update.callback_query.answer()
        callback_data = update.callback_query.data.split(" ")
        word_id = " ".join(callback_data[1:])

        self.dao.save_user_blocked_word(update.callback_query.message, word_id)
        reply_markup = InlineKeyboardMarkup([
            [InlineKeyboardButton("Zurücknehmen - Deshacer", callback_data=f"/unblockword {word_id}")]
        ])
        msg = "✅\n" + update.callback_query.message.text
        update.callback_query.edit_message_text(msg, reply_markup=reply_markup)
        update.callback_query.answer()

    def callback_on_unblockword(self, update: Update, context: CallbackContext, is_from_blocked_words=False):  # pragma: no cover
        """Arg is_from_blocked_words indicates whether it was called from the /blockedwords comand. If so,
        it should show the updated /blockedwords output"""

        update.callback_query.answer()
        callback_data = update.callback_query.data.split(" ")
        word_id = " ".join(callback_data[1:])

        self.dao.remove_user_blocked_word(update.callback_query.message, word_id)

        # if called from blocked words, should show the updated blocked words
        if is_from_blocked_words:
            return self.callback_on_get_blockwords_callback(update, context)
        else:
            reply_markup = InlineKeyboardMarkup([
                [InlineKeyboardButton("Gelernt! - Aprendida!", callback_data=f"/blockword {word_id}")]
            ])
            msg = update.callback_query.message.text[2:]  # remove '✅\n'
            update.callback_query.edit_message_text(msg, reply_markup=reply_markup)

    def callback_on_cancel(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        update.callback_query and update.callback_query.answer()
        msg = "Cancelled."
        update.effective_message.reply_text(msg)
        return ConversationHandler.END

    # Admin Callbacks

    @admin_only
    def callback_on_users(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        users = list(self.dao.get_all_users())
        msg = utils.build_users_msg(users)
        update.message.reply_text(msg)

    @admin_only
    def callback_on_broadcast(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        msg = "Type the message you wanna broadcast:"
        update.effective_message.reply_text(msg, reply_markup=InlineKeyboardMarkup(Buttons.cancel))
        return States.BROADCAST_TYPE

    @admin_only
    def callback_on_broadcast_confirm(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        msg = utils.build_broadcast_preview_msg(update.message.text)
        update.effective_message.reply_text(msg, parse_mode='HTML', reply_markup=InlineKeyboardMarkup(
            [[InlineKeyboardButton("Send", callback_data="/broadcast_send"), (Buttons.cancel[0][0])]]
        ))
        return States.BROADCAST_CONFIRM

    @admin_only
    def callback_on_broadcast_send(self, update: Update, context: CallbackContext) -> None:  # pragma: no cover
        update.callback_query.answer()
        msg = utils.get_broadcast_msg_from_preview(update.callback_query.message.text)
        users = self.dao.get_all_active_users()
        for user in users:
            self.send_message_to_user(user, msg)
        return ConversationHandler.END

    ####################
    # Job Callbacks    #
    ####################

    def job_callback_send_word(self, context: CallbackContext):  # pragma: no cover
        users = self.dao.get_all_active_users()
        logger.info(f"sending words to {len(users)} users")

        for user in users:
            self.send_user_word(user)

    ####################
    # Core Stuff       #
    ####################

    @handle_error_send_user
    def send_message_to_user(self, user: dict, msg: str, reply_markup=None):
        chat_id = user["chatId"]
        self.updater.bot.send_message(chat_id=chat_id, text=msg, reply_markup=reply_markup, parse_mode='HTML')

    @handle_error_send_user
    def send_user_word(self, user: dict):  # pragma: no cover
        chat_id = user["chatId"]
        exclude = self.dao.get_user_blocked_words(chat_id)
        levels = self.dao.get_user_levels(chat_id)
        word_data = self.word_bank.get_random(levels, exclude=exclude)

        if word_data:
            msg = utils.build_word_msg(word_data)
            reply_markup = InlineKeyboardMarkup([
                [InlineKeyboardButton("Gelernt! - ¡Aprendida!", callback_data=f"/blockword {word_data['word_id']}")]
            ])
        else:
            msg = 'Du hast alles gelernt! - ¡Te lo has aprendido todo!'
            reply_markup = None

        self.send_message_to_user(user, msg, reply_markup=reply_markup)

    def callback_chrono_backup(self, context: CallbackContext):  # pragma: no cover
        self.backup_service.backup()
        logger.info("Backed up!")

    def send_message_to_admins(self, msg: str):  # pragma: no cover
        for chat_id in config.ADMIN_CHAT_IDS:
            self.updater.bot.send_message(chat_id=chat_id, text=msg, parse_mode='HTML')

    @staticmethod
    def is_admin(chat_id: str):
        return str(chat_id) in config.ADMIN_CHAT_IDS

    def error_handler(self, update: object, context: CallbackContext) -> None:  # pragma: no cover
        """Send error msg to admins"""
        logger.error(msg="Exception while handling an update:", exc_info=context.error)

        traceback_str = ''.join(traceback.format_exception(None, context.error, context.error.__traceback__))

        user_str = ""
        if update:
            chat_id = update.effective_message.chat.id
            first_name = update.effective_message.chat.first_name
            user_str = f"Happened to user: {chat_id} - {first_name}"
        msg = (
            f'An exception was raised while handling an update. {user_str}\n\n'
            f'<pre>{html.escape(traceback_str)}</pre>'
        )

        self.send_message_to_admins(msg)

    def __init__(self):
        self.start_date = datetime.now()
        self.dao = DAO(config.REDIS_HOST)
        self.word_bank = WordBank(config.WORD_BANK_LOCAL)
        self.backup_service = BackupService()

    def run(self):  # pragma: no cover
        """Run bot"""
        logger.info("Started app")

        self.updater = Updater(config.BOT_TOKEN)
        self.updater.bot.set_my_commands(user_bot_commands)

        self.updater.job_queue.run_custom(self.job_callback_send_word, job_kwargs=dict(
            trigger="cron",
            day="*",
            hour="10,18,20",
            minute="30",
            # second="10,20,30,40,50,0"  # test
        ))
        self.updater.job_queue.run_custom(lambda x: self.word_bank.update(), job_kwargs=dict(
            trigger="cron",
            day="*",
            hour="7",
            # second="10,20,30,40,50,0"  # test
        ))

        # daily backup
        self.updater.job_queue.run_custom(self.callback_chrono_backup, job_kwargs=dict(
            trigger="cron",
            hour="22"
        ))

        # Bot conversation flow logic
        broadcast_handler = ConversationHandler(
            entry_points=[
                CommandHandler("start", self.callback_on_start),
                CallbackQueryHandler(self.callback_on_start, pattern=r"\/start"),

                CommandHandler("stop", self.callback_on_stop),
                CallbackQueryHandler(self.callback_on_stop, pattern=r"\/stop"),

                CommandHandler("blockedwords", self.callback_on_get_blockwords_callback),
                CallbackQueryHandler(self.callback_on_blockword, pattern=r"\/blockword .*"),
                CallbackQueryHandler(self.callback_on_unblockword, pattern=r"\/unblockword .*"),
                # fbw = from blocked words
                CallbackQueryHandler(lambda u, c: self.callback_on_unblockword(u, c, is_from_blocked_words=True), pattern=r"\/unblockword_fbw .*"),

                CommandHandler("mylevels", self.callback_on_mylevels),
                CallbackQueryHandler(self.callback_on_addlevel, pattern=r"\/addlevel .*"),
                CallbackQueryHandler(self.callback_on_removelevel, pattern=r"\/removelevel .*"),

                CommandHandler("help", self.callback_on_help),
                CommandHandler("info", self.callback_on_info),
                # admin
                CommandHandler("users", self.callback_on_users),
                CommandHandler("broadcast", self.callback_on_broadcast),
            ],
            states={
                States.BROADCAST_TYPE: [
                    MessageHandler(Filters.regex("^(?!/).*"), self.callback_on_broadcast_confirm)
                ],
                States.BROADCAST_CONFIRM: [
                    CallbackQueryHandler(self.callback_on_broadcast_send, pattern=r"\/broadcast_send")
                ],
            },
            fallbacks=[
                CommandHandler("cancel", self.callback_on_cancel),
                CallbackQueryHandler(self.callback_on_cancel, pattern=r"\/cancel")
            ],
        )

        self.updater.dispatcher.add_handler(broadcast_handler)
        self.updater.dispatcher.add_error_handler(self.error_handler)

        self.updater.start_polling()
        self.updater.idle()
コード例 #6
0
ファイル: app.py プロジェクト: rooom13/dailyWord-bot
 def __init__(self):
     self.start_date = datetime.now()
     self.dao = DAO(config.REDIS_HOST)
     self.word_bank = WordBank(config.WORD_BANK_LOCAL)
     self.backup_service = BackupService()