From 881794535e97ffe4f4e57d1104007c5e5bf2a981 Mon Sep 17 00:00:00 2001 From: "serafim.urukov" Date: Fri, 3 Sep 2021 00:53:42 +0300 Subject: [PATCH] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F:=20-=20=D0=B2=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=B5=20assignment=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80?= =?UTF-8?q?=D0=B0=D1=89=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D1=83=D0=BD=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B8=D0=B4=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=BE=D1=80;?= =?UTF-8?q?=20-=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=20ConversationHandl?= =?UTF-8?q?er=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B?= =?UTF-8?q?=20=D1=81=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=D0=BC;?= =?UTF-8?q?=20-=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=BE=20?= =?UTF-8?q?=D1=8D=D0=BC=D0=BE=D0=B4=D0=B7=D0=B8=20=D0=BD=D0=B5=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F;=20-=20=D0=BF=D0=BE=D0=BF?= =?UTF-8?q?=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=84=D1=83=D0=BD?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20inline-=D0=BC=D0=B5=D0=BD=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpreadsheetsService/models/assignment.py | 17 +- TelegramBot/bot.py | 200 ++++++++++------------- TelegramBot/messages/messages.py | 26 +-- TelegramBot/utils/inline_keyboard.py | 35 ++-- 4 files changed, 133 insertions(+), 145 deletions(-) diff --git a/SpreadsheetsService/models/assignment.py b/SpreadsheetsService/models/assignment.py index 4c686b3..3d09cb4 100644 --- a/SpreadsheetsService/models/assignment.py +++ b/SpreadsheetsService/models/assignment.py @@ -1,13 +1,20 @@ from logging import addLevelName +import uuid class Assignment(dict): """Задание.""" - def __init__(self, name: str, points: list, weight: float = 1, - subject: str = None, allow_to_display: bool = False, - how_to_display: str = 'z,1,1', - notes: str = 'Замечаний по работе нет.'): + def __init__( + self, + name: str, + points: list, + weight: float = 1, + subject: str = None, + allow_to_display: bool = False, + how_to_display: str = "z,1,1", + notes: str = "Замечаний по работе нет.", + ): self.name = name self.subject = subject self.weight = weight @@ -15,6 +22,7 @@ class Assignment(dict): self.allow_to_display = allow_to_display self.how_to_display = how_to_display self.notes = notes + self.uuid = uuid.uuid3(uuid.NAMESPACE_DNS, f"{name}{subject}") dict.__init__( self, name=self.name, @@ -25,6 +33,7 @@ class Assignment(dict): allow_to_display=self.allow_to_display, how_to_display=self.how_to_display, notes=self.notes, + uuid=self.uuid, ) @property diff --git a/TelegramBot/bot.py b/TelegramBot/bot.py index 16be060..ac23889 100644 --- a/TelegramBot/bot.py +++ b/TelegramBot/bot.py @@ -27,7 +27,7 @@ TOKEN = Path(".secrets/bot_token.txt").read_text() MESSAGES = Messages() HOST = "http://carolyn-spreadsheets:5000" BROADCAST_MESSAGE, BROADCAST_PUBLISH_DONE = range(2) -GRADES_CALLBACK = 1 +GRADES_ASSNT, GRADES_VIEW = range(2) logging.setLoggerClass(TgLogger) logging.basicConfig( @@ -77,118 +77,105 @@ def start(update: Update, context: CallbackContext): ) -def grades(update: Update, context: CallbackContext): - """Команда: Получить оценки студента.""" +def grades_start(update: Update, context: CallbackContext): + """Сообщение 'Выбери дисциплину'. + Обновляет текст сообщения и дает клавиатуру с выбором дисциплины. + """ user_id = update.message.from_user.id - start = time.monotonic() # Длинная операция, сообщим о запущенном процессе. look_msg = update.message.reply_html(MESSAGES.Assignments.START) + try: data = requests.get(f"{HOST}/grades", params={"tg_id": user_id}).json() - - subjects = data["subjects"] - assignments = data["assignments"] - - if len(subjects) > 1: - context.user_data["subjects"] = subjects - context.user_data["all_assignments"] = assignments - reply_markup = build_menu_of_subjects(subjects) - msg = update.message.reply_text( - text=MESSAGES.Assignments.SELECT_COURSE, reply_markup=reply_markup - ) - else: - context.user_data["assignments"] = assignments - reply_markup = build_menu_of_assignments(assignments) - msg = update.message.reply_text( - text=MESSAGES.Assignments.SELECT_ASSNT, reply_markup=reply_markup - ) - - context.user_data["msg"] = msg - return_value = GRADES_CALLBACK - except: LOG.exception("Failed to get grades.") update.message.reply_sticker(MESSAGES.Stickers.DEAD) - return_value = ConversationHandler.END - diff = time.monotonic() - start - look_msg.edit_text(MESSAGES.Assignments.timeit(diff)) - return return_value - - -def callback(update: Update, context: CallbackContext): - - call = update.callback_query.data - - # Нажатие кнопок при выборе задания - if call.startswith("assignment#"): - - if "$back$" in call: - subjects = context.user_data["subjects"] - reply_markup = build_menu_of_subjects(subjects) - msg = update.callback_query.message.edit_text( - text=MESSAGES.Assignments.SELECT_COURSE, reply_markup=reply_markup - ) - context.user_data["msg"] = msg - return GRADES_CALLBACK - - assignments = context.user_data["assignments"] - name, subject = call.split("#")[1:3] - - reply_markup = build_menu_of_assignments( - assignments, name, has_back_button=context.user_data["has_back_button"] - ) - - f = lambda ass: ass["name"] == name and ass["subject"] == subject - assignment = next(filter(f, assignments)) - msg = update.callback_query.message.edit_text( - text=MESSAGES.Assignments.get(assignment['name'], - assignment['points'], - assignment['how_to_display'], - assignment['notes']), - parse_mode=ParseMode.HTML, - reply_markup=reply_markup - ) - context.user_data["msg"] = msg - - # Нажатие кнопок при выборе дисциплины - if call.startswith("subject#"): - all_assignments = context.user_data["all_assignments"] - subject = call.split("#")[1] - - f = lambda ass: ass["subject"] == subject - assignments = list(filter(f, all_assignments)) - context.user_data["assignments"] = assignments - context.user_data["has_back_button"] = True - - reply_markup = build_menu_of_assignments(assignments, has_back_button=True) - msg = update.callback_query.message.edit_text( - MESSAGES.Assignments.SELECT_ASSNT, reply_markup=reply_markup - ) - context.user_data["msg"] = msg - - if call.startswith("cancel#"): - update.callback_query.message.edit_text( - MESSAGES.Assignments.END, - ) return ConversationHandler.END - return GRADES_CALLBACK - -def timeout(update: Update, context: CallbackContext): - msg = context.user_data["msg"] - msg.edit_text( - MESSAGES.Assignments.TIMEOUT, - ) - return ConversationHandler.END - -def grades_end(update: Update, context: CallbackContext): - msg = context.user_data["msg"] - msg.edit_text( - MESSAGES.Assignments.END + context.user_data["subjects"] = data["subjects"] + context.user_data["assignments"] = data["assignments"] + diff = time.monotonic() - start + look_msg.edit_text(MESSAGES.Assignments.timeit(diff)) + update.message.reply_text( + MESSAGES.Assignments.SELECT_COURSE, + reply_markup=build_menu_of_subjects(data["subjects"]), ) - return ConversationHandler.END + return GRADES_ASSNT + + +def grades_pick_assignment(update: Update, context: CallbackContext): + """Сообщение 'Выбери задание'. + + Обновляет текст сообщения и дает клавиатуру с выбором дисциплины. + В `callback_query` содержится имя предмета, либо ключевое слово. + Может завершить диалог. + """ + query = update.callback_query + query.answer() + if query.data == "cancel": + query.edit_message_text(MESSAGES.Assignments.END, reply_markup=None) + return ConversationHandler.END + context.user_data["selected_subject"] = query.data + assignments = [ + a for a in context.user_data["assignments"] if a["subject"] == query.data + ] + query.edit_message_text( + MESSAGES.Assignments.SELECT_ASSNT, + reply_markup=build_menu_of_assignments(assignments, has_back_button=True), + ) + return GRADES_VIEW + + +def grades_view(update: Update, context: CallbackContext): + """Отображение домашки. + + В `callback_query` содержится UUID, идентификатор задания. Либо команда: + cancel или back. Может завершить диалог. + """ + query = update.callback_query + query.answer() + + if query.data == "cancel": + query.edit_message_text(MESSAGES.Assignments.END, reply_markup=None) + return ConversationHandler.END + + if query.data == "back": + subjects = context.user_data["subjects"] + query.edit_message_text( + MESSAGES.Assignments.SELECT_COURSE, + reply_markup=build_menu_of_subjects(subjects), + ) + return GRADES_ASSNT + + assignments_map = {a["uuid"]: a for a in context.user_data["assignments"]} + assignment = assignments_map[query.data] + subject = context.user_data["selected_subject"] + + raw_menu = build_menu( + [ + InlineKeyboardButton("Отмена", callback_data="cancel"), + InlineKeyboardButton("⏮ Назад", callback_data=subject), + ], + 2, + ) + + reply_markup = InlineKeyboardMarkup(raw_menu) + + query.edit_message_text( + text=MESSAGES.Assignments.get( + assignment["name"], + assignment["points"], + assignment["how_to_display"], + assignment["notes"], + ), + parse_mode=ParseMode.HTML, + reply_markup=reply_markup, + ) + + return GRADES_ASSNT + def broadcast(update: Update, context: CallbackContext): """Команда: разослать студентам.""" @@ -268,18 +255,12 @@ def main() -> None: start_handler = CommandHandler("start", start) grades_handler = ConversationHandler( - entry_points=[CommandHandler("grades", grades)], + entry_points=[CommandHandler("grades", grades_start)], states={ - GRADES_CALLBACK: [CallbackQueryHandler(callback)], - ConversationHandler.TIMEOUT: [ - MessageHandler(Filters.text | Filters.command, timeout) - ], - ConversationHandler.END: [ - MessageHandler(Filters.text | Filters.command, grades_end) - ] + GRADES_ASSNT: [CallbackQueryHandler(grades_pick_assignment)], + GRADES_VIEW: [CallbackQueryHandler(grades_view)], }, fallbacks=[CommandHandler("cancel", cancel)], - #conversation_timeout=10, ) broadcast_handler = ConversationHandler( @@ -294,7 +275,6 @@ def main() -> None: dispatcher.add_handler(start_handler) dispatcher.add_handler(grades_handler) dispatcher.add_handler(broadcast_handler) - #dispatcher.add_handler(callback_handler) # Начало работы бота updater.start_polling() diff --git a/TelegramBot/messages/messages.py b/TelegramBot/messages/messages.py index daaa16f..661383e 100644 --- a/TelegramBot/messages/messages.py +++ b/TelegramBot/messages/messages.py @@ -30,20 +30,24 @@ class Messages: @staticmethod def get(name: str, assignment: list, how_to_display: str, notes: str): - is_float, n_cols, n_rows = how_to_display.split(',') + is_float, n_cols, n_rows = how_to_display.split(",") n_cols, n_rows = int(n_cols), int(n_rows) - summary = f'{name}:\n' - if is_float == 'z': - d = {0 : '❎', 1 : '✅'} + summary = f"{name}:\n" + if is_float == "z": + d = {0: "🟥", 1: "✅"} for item in range(0, len(assignment), n_cols): - summary += ''.join(list(map(d.get, - assignment[item : item + n_cols]))) + '\n' - elif is_float == 'r': - to_str = lambda x: f'{x:.2f}' + summary += ( + "".join(list(map(d.get, assignment[item : item + n_cols]))) + + "\n" + ) + elif is_float == "r": + to_str = lambda x: f"{x:.2f}" for item in range(0, len(assignment), n_cols): - summary += ' '.join(list(map(to_str, - assignment[item: item + n_cols]))) + '\n' - summary += f'Σ: {to_str(sum(assignment))}\n' + summary += ( + " ".join(list(map(to_str, assignment[item : item + n_cols]))) + + "\n" + ) + summary += f"Σ: {to_str(sum(assignment))}\n" summary += f"\n{notes}" return summary diff --git a/TelegramBot/utils/inline_keyboard.py b/TelegramBot/utils/inline_keyboard.py index b443c0c..fc3cd30 100644 --- a/TelegramBot/utils/inline_keyboard.py +++ b/TelegramBot/utils/inline_keyboard.py @@ -5,41 +5,36 @@ from telegram import ( def build_menu_of_subjects(subjects: list) -> InlineKeyboardMarkup: - button_list = [] - for subject in subjects: - button_list.append( - InlineKeyboardButton(subject, callback_data=f"subject#{subject}") - ) - end_button = [InlineKeyboardButton("Отмена", callback_data="cancel#")] - return InlineKeyboardMarkup(build_menu(button_list, n_cols=2, - footer_buttons=end_button)) + button_list = [ + InlineKeyboardButton(subject, callback_data=subject) for subject in subjects + ] + end_button = [InlineKeyboardButton("Отмена", callback_data="cancel")] + return InlineKeyboardMarkup( + build_menu(button_list, n_cols=2, footer_buttons=end_button) + ) def build_menu_of_assignments( - assignments: dict, pressed_name: str = None, has_back_button: bool = False + assignments: dict, has_back_button: bool = False ) -> InlineKeyboardMarkup: button_list = [] for assignment in assignments: - if not assignment['allow_to_display']: + if not assignment["allow_to_display"]: continue - if pressed_name and assignment["name"] == pressed_name: - title = f'💁‍♀️ {assignment["name"]}' else: title = assignment["name"] button_list.append( InlineKeyboardButton( title, - callback_data=f'assignment#{assignment["name"]}' - + f'#{assignment["subject"]}', + callback_data=assignment["uuid"], ) ) if has_back_button: - button_list.append( - InlineKeyboardButton("Назад", callback_data=f"assignment#$back$") - ) - end_button = [InlineKeyboardButton("Отмена", callback_data="cancel#")] - return InlineKeyboardMarkup(build_menu(button_list, n_cols=3, - footer_buttons=end_button)) + button_list.append(InlineKeyboardButton("⏮ Назад", callback_data="back")) + end_button = [InlineKeyboardButton("Отмена", callback_data="cancel")] + return InlineKeyboardMarkup( + build_menu(button_list, n_cols=3, footer_buttons=end_button) + ) def build_menu(buttons, n_cols, header_buttons=None, footer_buttons=None):