Изменения:

- в объекте assignment возвращается уникальный идентификатор;
- сделан ConversationHandler для работы с заданием;
- изменено эмодзи неправильного задания;
- поправлены функции построения inline-меню
This commit is contained in:
serafim.urukov 2021-09-03 00:53:42 +03:00
parent 93b313690e
commit 881794535e
4 changed files with 133 additions and 145 deletions

View File

@ -1,13 +1,20 @@
from logging import addLevelName from logging import addLevelName
import uuid
class Assignment(dict): class Assignment(dict):
"""Задание.""" """Задание."""
def __init__(self, name: str, points: list, weight: float = 1, def __init__(
subject: str = None, allow_to_display: bool = False, self,
how_to_display: str = 'z,1,1', name: str,
notes: 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.name = name
self.subject = subject self.subject = subject
self.weight = weight self.weight = weight
@ -15,6 +22,7 @@ class Assignment(dict):
self.allow_to_display = allow_to_display self.allow_to_display = allow_to_display
self.how_to_display = how_to_display self.how_to_display = how_to_display
self.notes = notes self.notes = notes
self.uuid = uuid.uuid3(uuid.NAMESPACE_DNS, f"{name}{subject}")
dict.__init__( dict.__init__(
self, self,
name=self.name, name=self.name,
@ -25,6 +33,7 @@ class Assignment(dict):
allow_to_display=self.allow_to_display, allow_to_display=self.allow_to_display,
how_to_display=self.how_to_display, how_to_display=self.how_to_display,
notes=self.notes, notes=self.notes,
uuid=self.uuid,
) )
@property @property

View File

@ -27,7 +27,7 @@ TOKEN = Path(".secrets/bot_token.txt").read_text()
MESSAGES = Messages() MESSAGES = Messages()
HOST = "http://carolyn-spreadsheets:5000" HOST = "http://carolyn-spreadsheets:5000"
BROADCAST_MESSAGE, BROADCAST_PUBLISH_DONE = range(2) BROADCAST_MESSAGE, BROADCAST_PUBLISH_DONE = range(2)
GRADES_CALLBACK = 1 GRADES_ASSNT, GRADES_VIEW = range(2)
logging.setLoggerClass(TgLogger) logging.setLoggerClass(TgLogger)
logging.basicConfig( 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 user_id = update.message.from_user.id
start = time.monotonic() start = time.monotonic()
# Длинная операция, сообщим о запущенном процессе. # Длинная операция, сообщим о запущенном процессе.
look_msg = update.message.reply_html(MESSAGES.Assignments.START) look_msg = update.message.reply_html(MESSAGES.Assignments.START)
try: try:
data = requests.get(f"{HOST}/grades", params={"tg_id": user_id}).json() 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: except:
LOG.exception("Failed to get grades.") LOG.exception("Failed to get grades.")
update.message.reply_sticker(MESSAGES.Stickers.DEAD) 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 ConversationHandler.END
return GRADES_CALLBACK context.user_data["subjects"] = data["subjects"]
context.user_data["assignments"] = data["assignments"]
def timeout(update: Update, context: CallbackContext): diff = time.monotonic() - start
msg = context.user_data["msg"] look_msg.edit_text(MESSAGES.Assignments.timeit(diff))
msg.edit_text( update.message.reply_text(
MESSAGES.Assignments.TIMEOUT, MESSAGES.Assignments.SELECT_COURSE,
) reply_markup=build_menu_of_subjects(data["subjects"]),
return ConversationHandler.END
def grades_end(update: Update, context: CallbackContext):
msg = context.user_data["msg"]
msg.edit_text(
MESSAGES.Assignments.END
) )
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): def broadcast(update: Update, context: CallbackContext):
"""Команда: разослать студентам.""" """Команда: разослать студентам."""
@ -268,18 +255,12 @@ def main() -> None:
start_handler = CommandHandler("start", start) start_handler = CommandHandler("start", start)
grades_handler = ConversationHandler( grades_handler = ConversationHandler(
entry_points=[CommandHandler("grades", grades)], entry_points=[CommandHandler("grades", grades_start)],
states={ states={
GRADES_CALLBACK: [CallbackQueryHandler(callback)], GRADES_ASSNT: [CallbackQueryHandler(grades_pick_assignment)],
ConversationHandler.TIMEOUT: [ GRADES_VIEW: [CallbackQueryHandler(grades_view)],
MessageHandler(Filters.text | Filters.command, timeout)
],
ConversationHandler.END: [
MessageHandler(Filters.text | Filters.command, grades_end)
]
}, },
fallbacks=[CommandHandler("cancel", cancel)], fallbacks=[CommandHandler("cancel", cancel)],
#conversation_timeout=10,
) )
broadcast_handler = ConversationHandler( broadcast_handler = ConversationHandler(
@ -294,7 +275,6 @@ def main() -> None:
dispatcher.add_handler(start_handler) dispatcher.add_handler(start_handler)
dispatcher.add_handler(grades_handler) dispatcher.add_handler(grades_handler)
dispatcher.add_handler(broadcast_handler) dispatcher.add_handler(broadcast_handler)
#dispatcher.add_handler(callback_handler)
# Начало работы бота # Начало работы бота
updater.start_polling() updater.start_polling()

View File

@ -30,20 +30,24 @@ class Messages:
@staticmethod @staticmethod
def get(name: str, assignment: list, how_to_display: str, notes: str): 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) n_cols, n_rows = int(n_cols), int(n_rows)
summary = f'<b>{name}</b>:\n' summary = f"<b>{name}</b>:\n"
if is_float == 'z': if is_float == "z":
d = {0 : '', 1 : ''} d = {0: "🟥", 1: ""}
for item in range(0, len(assignment), n_cols): for item in range(0, len(assignment), n_cols):
summary += ''.join(list(map(d.get, summary += (
assignment[item : item + n_cols]))) + '\n' "".join(list(map(d.get, assignment[item : item + n_cols])))
elif is_float == 'r': + "\n"
to_str = lambda x: f'{x:.2f}' )
elif is_float == "r":
to_str = lambda x: f"{x:.2f}"
for item in range(0, len(assignment), n_cols): for item in range(0, len(assignment), n_cols):
summary += ' '.join(list(map(to_str, summary += (
assignment[item: item + n_cols]))) + '\n' " ".join(list(map(to_str, assignment[item : item + n_cols])))
summary += f'Σ: {to_str(sum(assignment))}\n' + "\n"
)
summary += f"Σ: {to_str(sum(assignment))}\n"
summary += f"\n{notes}" summary += f"\n{notes}"
return summary return summary

View File

@ -5,41 +5,36 @@ from telegram import (
def build_menu_of_subjects(subjects: list) -> InlineKeyboardMarkup: def build_menu_of_subjects(subjects: list) -> InlineKeyboardMarkup:
button_list = [] button_list = [
for subject in subjects: InlineKeyboardButton(subject, callback_data=subject) for subject in subjects
button_list.append( ]
InlineKeyboardButton(subject, callback_data=f"subject#{subject}") end_button = [InlineKeyboardButton("Отмена", callback_data="cancel")]
) return InlineKeyboardMarkup(
end_button = [InlineKeyboardButton("Отмена", callback_data="cancel#")] build_menu(button_list, n_cols=2, footer_buttons=end_button)
return InlineKeyboardMarkup(build_menu(button_list, n_cols=2, )
footer_buttons=end_button))
def build_menu_of_assignments( def build_menu_of_assignments(
assignments: dict, pressed_name: str = None, has_back_button: bool = False assignments: dict, has_back_button: bool = False
) -> InlineKeyboardMarkup: ) -> InlineKeyboardMarkup:
button_list = [] button_list = []
for assignment in assignments: for assignment in assignments:
if not assignment['allow_to_display']: if not assignment["allow_to_display"]:
continue continue
if pressed_name and assignment["name"] == pressed_name:
title = f'💁‍♀️ {assignment["name"]}'
else: else:
title = assignment["name"] title = assignment["name"]
button_list.append( button_list.append(
InlineKeyboardButton( InlineKeyboardButton(
title, title,
callback_data=f'assignment#{assignment["name"]}' callback_data=assignment["uuid"],
+ f'#{assignment["subject"]}',
) )
) )
if has_back_button: if has_back_button:
button_list.append( button_list.append(InlineKeyboardButton("⏮ Назад", callback_data="back"))
InlineKeyboardButton("Назад", callback_data=f"assignment#$back$") end_button = [InlineKeyboardButton("Отмена", callback_data="cancel")]
) return InlineKeyboardMarkup(
end_button = [InlineKeyboardButton("Отмена", callback_data="cancel#")] build_menu(button_list, n_cols=3, footer_buttons=end_button)
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): def build_menu(buttons, n_cols, header_buttons=None, footer_buttons=None):