diff options
| -rw-r--r-- | backend_api.py | 40 | ||||
| -rw-r--r-- | bot.py | 115 | ||||
| -rw-r--r-- | keyboards.py | 69 | ||||
| -rw-r--r-- | states.py | 109 | ||||
| -rw-r--r-- | utils.py | 14 |
5 files changed, 259 insertions, 88 deletions
diff --git a/backend_api.py b/backend_api.py index 2c8f268..4fb11cf 100644 --- a/backend_api.py +++ b/backend_api.py @@ -1,4 +1,5 @@ import requests +import urllib from config import BACKEND_URL import logging @@ -9,15 +10,20 @@ logger.setLevel(logging.DEBUG) def register_user(tg_id, username, fullname) -> bool: logger.debug(f"Registering user with id={tg_id}; username={username}") - response = requests.post(f"{BACKEND_URL}/profiles/", data={ - "tg_id": tg_id, - "username": username, - "fullname": fullname - }) + try: + response = requests.post(f"{BACKEND_URL}/profiles/", data={ + "tg_id": tg_id, + "username": username, + "fullname": fullname + }) + except Exception as e: + logger.debug(f"Got exception while making request: {e}") + return False + logger.debug( f"Got response from backend: " f"Status={response.status_code}; " - f"Text={response.text}" + f"Text={response.text[:200]}..." ) return response.status_code == 201 @@ -26,3 +32,25 @@ def register_user(tg_id, username, fullname) -> bool: def get_tasks(): response = requests.get(f"{BACKEND_URL}/tasks/") return response.json() + + +def get_task(title: str): + logger.debug(f"Trying to retrieve task with title={title}") + try: + response = requests.get( + "http://127.0.0.1:8000/api/get_task/" + + urllib.parse.quote(title) + ) + task = response.json() + + except Exception as e: + logger.debug(f"Got exception while making request: {e}") + return 500, {} + + logger.debug( + f"Got response from backend: " + f"Status={response.status_code}; " + f"Text={response.text[:200]}..." + ) + + return response.status_code, task @@ -9,6 +9,12 @@ from os import environ from config import TG_TOKEN, REQUEST_KWARGS import backend_api +from keyboards import ( + MenuKeyboard, TasksKeyboard, TaskChosenKeyboard, ContinueKeyboard, + AnsweringKeyboard, +) +from utils import * +from states import States logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) @@ -17,91 +23,29 @@ logger.setLevel(logging.DEBUG) def start(bot: Bot, update: Update, user_data): update.message.reply_text( - "Привет! Ты вошел в телеграм-квиз с мемами про наш любимый КНиИТ.", + "Привет! Ты вошел в телеграм-квиз с мемами про наш любимый КНиИТ!", reply_markup=ReplyKeyboardRemove() ) user: User = update.message.from_user if user.username is None: - update.message.reply_text( - "По правилам квиза ты не можешь участвовать, если у тебя не указано " - "имя пользователя, поэтому укажи его и возвращайся как только это сделаешь!", - reply_markup=ReplyKeyboardMarkup([['Я указал имя пользователя']]) - ) - return USERNAME_CHECK + return States.wait_for_username(bot, update, user_data) else: logger.debug(backend_api.register_user(user.id, user.username, user.full_name)) update.message.reply_text("Ты успешно зарегистрирован в системе!") - - update.message.reply_text("Твой счет: 0") - update.message.reply_text( - "Выбери следующее действие...", - reply_markup=ReplyKeyboardMarkup([ - ["Сдать задачу"], - ["Топ-10", "Правила"] - ]) - ) - - return MAIN_MENU + return States.main_menu(bot, update, user_data) def username_check(bot: Bot, update: Update, user_data): user: User = update.message.from_user if user.username is None: - update.message.reply_text( - "Ты все еще не указал имя пользователя!", - reply_markup=ReplyKeyboardMarkup([['Я указал имя пользователя']]) - ) - return USERNAME_CHECK + return States.wait_for_username(bot, update, user_data) else: logger.debug(backend_api.register_user(user.id, user.username, user.full_name)) update.message.reply_text("Ты успешно зарегистрирован в системе!") - - update.message.reply_text("Твой счет: 0") - update.message.reply_text( - "Выбери следующее действие...", - reply_markup=ReplyKeyboardMarkup([ - ["Сдать задачу"], - ["Топ-10", "Правила"] - ]) - ) - - return MAIN_MENU - - -def main_menu(bot, update, user_data): - text = update.message.text - - if text == "Сдать задачу": - update.message.reply_text("А пока что нельзя!!") - return MAIN_MENU - - elif text == "Топ-10": - update.message.reply_text("Топ-1:\n1.Андрей Гущин") - return MAIN_MENU - - elif text == "Правила": - update.message.reply_text("Какие-то правила!!!!") - return MAIN_MENU - - return MAIN_MENU - - -def rules(bot: Bot, update: Update, user_data): - update.message.reply_text("Какие-то правила!!!!") - return MAIN_MENU - - -def top_10(bot: Bot, update: Update, user_data): - update.message.reply_text("Топ-1:\n1.Андрей Гущин") - return MAIN_MENU - - -def task_choose(bot: Bot, update: Update, user_data): - update.message.reply_text("А пока что нельзя!!") - return MAIN_MENU + return States.main_menu(bot, update, user_data) def stop(bot, update): @@ -125,26 +69,33 @@ def main(): updater.idle() -( - USERNAME_CHECK, USERNAME_HOLD, - MAIN_MENU, TOP_10, RULES, - TASK_CHOOSE, CANCEL_CHOOSE, - TASK_SHOW, CANCEL_SHOW, - ENTER_ANSWER, CANCEL_ANSWER, - *_ -) = range(100) - conversation_handler = ConversationHandler( entry_points=[ - CommandHandler('start', start, pass_user_data=True) + CommandHandler('start', start, pass_user_data=True), ], states={ - USERNAME_CHECK: [MessageHandler(Filters.text, username_check, pass_user_data=True)], - MAIN_MENU: [MessageHandler(Filters.text, main_menu, pass_user_data=True)], - RULES: [MessageHandler(Filters.text, rules, pass_user_data=True)], - TOP_10: [MessageHandler(Filters.text, top_10, pass_user_data=True)], - TASK_CHOOSE: [MessageHandler(Filters.text, task_choose, pass_user_data=True)], + WAIT_FOR_USERNAME: [MessageHandler(Filters.text, username_check, pass_user_data=True)], + + MAIN_MENU: [ + MessageHandler(Filters.regex(MenuKeyboard.CHOOSE_TASK), States.choose_task, pass_user_data=True), + MessageHandler(Filters.regex(MenuKeyboard.TOP_10), States.top_10, pass_user_data=True), + MessageHandler(Filters.regex(MenuKeyboard.RULES), States.rules, pass_user_data=True), + ], + TASK_CHOOSING: [ + MessageHandler(Filters.regex(TasksKeyboard.CANCEL), States.main_menu, pass_user_data=True), + MessageHandler(Filters.text, States.show_task, pass_user_data=True), + ], + TASK_SHOWN: [ + MessageHandler(Filters.regex(TaskChosenKeyboard.CANCEL), States.choose_task, pass_user_data=True), + MessageHandler(Filters.regex(TaskChosenKeyboard.TYPE_ANSWER), States.type_answer, pass_user_data=True), + ], + ANSWERING: [ + MessageHandler(Filters.regex(AnsweringKeyboard.CANCEL), States.show_task, pass_user_data=True), + MessageHandler(Filters.text, States.accept_answer, pass_user_data=True), + ], + ANSWER_RIGHT: [MessageHandler(Filters.text, States.main_menu, pass_user_data=True)], + ANSWER_WRONG: [MessageHandler(Filters.text, States.show_task, pass_user_data=True)] }, fallbacks=[CommandHandler('stop', stop)] diff --git a/keyboards.py b/keyboards.py new file mode 100644 index 0000000..5966d90 --- /dev/null +++ b/keyboards.py @@ -0,0 +1,69 @@ +import backend_api +from abc import ABC + + +class Keyboard(ABC): + @classmethod + def get_keyboard(cls, telegram_id=None): + pass + + +class MenuKeyboard(Keyboard): + CHOOSE_TASK = "Выбрать задание" + TOP_10 = "Топ-10" + RULES = "Правила" + ADMIN = "/admin" + + @classmethod + def get_keyboard(cls, telegram_id=None): + return [ + [cls.CHOOSE_TASK], + [cls.TOP_10, cls.RULES], + ] + + +class BackToMenuKeyboard(Keyboard): + CANCEL = "Вернуться в меню" + + @classmethod + def get_keyboard(cls, telegram_id=None): + return [[cls.CANCEL]] + + +class TasksKeyboard(Keyboard): + CANCEL = "Вернуться в меню" + + @classmethod + def get_keyboard(cls, telegram_id=None): + tasks = backend_api.get_tasks() + titles_keyboard = [[cls.CANCEL]] + titles_keyboard.extend([task.get("title")] for task in tasks) + return titles_keyboard + + +class TaskChosenKeyboard(Keyboard): + TYPE_ANSWER = "Ввести ответ" + CANCEL = "Назад" + + @classmethod + def get_keyboard(cls, telegram_id=None): + return [ + [cls.TYPE_ANSWER], + [cls.CANCEL], + ] + + +class ContinueKeyboard(Keyboard): + CONTINUE = "Продолжить" + + @classmethod + def get_keyboard(cls, telegram_id=None): + return [[cls.CONTINUE]] + + +class AnsweringKeyboard(Keyboard): + CANCEL = "Отмена" + + @classmethod + def get_keyboard(cls, telegram_id=None): + return [[cls.CANCEL]] diff --git a/states.py b/states.py new file mode 100644 index 0000000..ccd4318 --- /dev/null +++ b/states.py @@ -0,0 +1,109 @@ +from telegram import ReplyKeyboardMarkup, ReplyKeyboardRemove + +from keyboards import ( + MenuKeyboard, TasksKeyboard, TaskChosenKeyboard, ContinueKeyboard, + AnsweringKeyboard, +) +from utils import * +import backend_api + +# Typing +from telegram import Update, User, Bot + + +class States: + @staticmethod + def wait_for_username(bot: Bot, update: Update, user_data: dict): + update.message.reply_text( + "По правилам квиза ты не можешь участвовать, если у тебя не указано " + "имя пользователя, поэтому укажи его и возвращайся как только это сделаешь!", + reply_markup=ReplyKeyboardMarkup([['Я указал имя пользователя']]) + ) + return WAIT_FOR_USERNAME + + @staticmethod + def main_menu(bot: Bot, update: Update, user_data: dict): + user_data["chosen_task"] = None + + update.message.reply_text("Твой счет: 0") + update.message.reply_text( + "Выбери следующее действие...", + reply_markup=ReplyKeyboardMarkup(MenuKeyboard.get_keyboard()) + ) + + return MAIN_MENU + + @staticmethod + def choose_task(bot: Bot, update: Update, user_data: dict): + user_data["chosen_task"] = None + + update.message.reply_text( + "Какую задачу ты хочешь сдать?", + reply_markup=ReplyKeyboardMarkup(TasksKeyboard.get_keyboard()) + ) + return TASK_CHOOSING + + @staticmethod + def top_10(bot: Bot, update: Update, user_data: dict): + update.message.reply_text("какая то хуйня") + return MAIN_MENU + + @staticmethod + def rules(bot: Bot, update: Update, user_data: dict): + update.message.reply_text("какая то хуйня") + return MAIN_MENU + + @staticmethod + def show_task(bot: Bot, update: Update, user_data: dict): + if "chosen_task" in user_data and user_data["chosen_task"] is not None: + task_title = user_data["chosen_task"] + else: + task_title = get_input(update, user_data) + user_data["chosen_task"] = task_title + + status_code, task = backend_api.get_task(task_title) + if status_code != 200: + message = "Произошла ошибка в работе квиза. Мы уже работаем над её устранением!" + keyboard = ContinueKeyboard.get_keyboard() + + else: + message = '\n'.join([ + f"*{task['title']}*", + f"{task['statement']}", + "", + f"_Теги: {task['tags']}_", + ]) + keyboard = TaskChosenKeyboard.get_keyboard() + + update.message.reply_text( + message, parse_mode="Markdown", + reply_markup=ReplyKeyboardMarkup(keyboard) + ) + + return TASK_SHOWN + + @staticmethod + def type_answer(bot: Bot, update: Update, user_data: dict): + update.message.reply_text( + "Вводи свой ответ, я его проверю.", + reply_markup=ReplyKeyboardMarkup(AnsweringKeyboard.get_keyboard()) + ) + return ANSWERING + + @staticmethod + def accept_answer(bot: Bot, update: Update, user_data: dict): + answer = get_input(update, user_data) + if answer == "хуй": + update.message.reply_text( + "Ты ввел правильный ответ! Возвращайся к другим задачам", + reply_markup=ReplyKeyboardMarkup(ContinueKeyboard.get_keyboard()) + ) + + return ANSWER_RIGHT + else: + update.message.reply_text( + "К сожалению, твой ответ неверный =(", + reply_markup=ReplyKeyboardMarkup(ContinueKeyboard.get_keyboard()) + ) + + return ANSWER_WRONG diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..a25571e --- /dev/null +++ b/utils.py @@ -0,0 +1,14 @@ +def get_input(update, user_data): + if "resuming" in user_data and user_data["resuming"] == True: + return user_data["text"] + else: + return update.message.text + + +( + WAIT_FOR_USERNAME, + MAIN_MENU, TOP_10, RULES, + TASK_CHOOSING, TASK_SHOWN, ANSWERING, + ANSWER_RIGHT, ANSWER_WRONG, + *_ +) = range(100)
\ No newline at end of file |