From 9a89cde7484402dcc2bb68d747820d8d86f2853f Mon Sep 17 00:00:00 2001 From: faridik Date: Fri, 3 Sep 2021 22:52:16 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20su?= =?UTF-8?q?b=20=D0=B8=20unsub?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpreadsheetsService/app.py | 25 ++++++- SpreadsheetsService/manager/manager.py | 92 ++++++++++++++++++++++---- TelegramBot/bot.py | 70 +++++++++++++++++++- TelegramBot/messages/messages.py | 26 ++++++++ 4 files changed, 198 insertions(+), 15 deletions(-) diff --git a/SpreadsheetsService/app.py b/SpreadsheetsService/app.py index 4bdaeb3..4e51990 100644 --- a/SpreadsheetsService/app.py +++ b/SpreadsheetsService/app.py @@ -87,7 +87,23 @@ def grades(): @superuser def broadcast(student): """Выполняет рассылку студентам.""" - return flask.jsonify(app.db.get_all_groups()) + return flask.jsonify(app.db.get_all_groups_only_sub_students()) + + +@app.route("/sub") +@availability +def sub(): + tg_id = flask.request.args.get("tg_id", None) + is_subbed = app.db.sub_by_tg_id(tg_id) + return flask.jsonify(dict(is_subbed=is_subbed)) + +@app.route("/unsub") +@availability +def unsub(): + tg_id = flask.request.args.get("tg_id", None) + is_unsubbed = app.db.unsub_by_tg_id(tg_id) + return flask.jsonify(dict(is_unsubbed=is_unsubbed)) + @app.errorhandler(StudentAlreadyAuthed) @@ -104,6 +120,13 @@ def show_error(err): def show_error(err): return flask.jsonify({"error": type(err).__name__, "message": err.message}), 404 +@app.errorhandler(StudentAlreadySubbed) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": err.message}), 405 + +@app.errorhandler(StudentAlreadyUnsubbed) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": err.message}), 406 @app.errorhandler(BadRequest) def show_error(err): diff --git a/SpreadsheetsService/manager/manager.py b/SpreadsheetsService/manager/manager.py index 33dca40..ca6e3fd 100644 --- a/SpreadsheetsService/manager/manager.py +++ b/SpreadsheetsService/manager/manager.py @@ -18,8 +18,9 @@ STUDENT_NUMBERS = 0 STUDENT_NAMES = 1 GROUP_IDS = 2 # for StudentList and AssignmentList STUDENT_SUBJECTS = 3 -TELEGRAM_IDS = 4 +STUDENT_TELEGRAM_IDS = 4 AUTH_TOKEN = 5 +STUDENT_SUBSCRIPTION = 6 ASSIGNMENT_NAMES = 1 ASSIGNMENT_GROUP = 2 ASSIGNMENT_SUBJECT = 3 @@ -62,6 +63,15 @@ class AnotherStudentAlreadyAuthed(Exception): self.message = message super().__init__(self.message) +class StudentAlreadySubbed(Exception): + def __init__(self, message): + self.message = message + super().__init__(self.message) + +class StudentAlreadyUnsubbed(Exception): + def __init__(self, message): + self.message = message + super().__init__(self.message) class Manager: """Позволяет выполнять операции с Google таблицами.""" @@ -148,7 +158,7 @@ class Manager: int(row[STUDENT_NUMBERS]), row[STUDENT_NAMES], group_id, - row[TELEGRAM_IDS], + row[STUDENT_TELEGRAM_IDS], ) ) @@ -162,11 +172,32 @@ class Manager: groups[row[GROUP_IDS]].group_id = row[GROUP_IDS] groups[row[GROUP_IDS]].add_student( Student( - int(row[STUDENT_NUMBERS]), - row[STUDENT_NAMES], - row[GROUP_IDS], - row[TELEGRAM_IDS], - row[STUDENT_SUBJECTS].split(',') + number=int(row[STUDENT_NUMBERS]), + name=row[STUDENT_NAMES], + group_id=row[GROUP_IDS], + tg_id=row[STUDENT_TELEGRAM_IDS], + subjects=row[STUDENT_SUBJECTS].split(','), + is_subbed=bool(int(row[STUDENT_SUBSCRIPTION])), + ) + ) + + return groups + + def get_all_groups_only_sub_students(self) -> list: + """Получить список всех групп.""" + values = self.get_values() + groups = defaultdict(lambda: Group("0")) + f = lambda row: row[STUDENT_SUBSCRIPTION] == '1' + for row in filter(f,values): + groups[row[GROUP_IDS]].group_id = row[GROUP_IDS] + groups[row[GROUP_IDS]].add_student( + Student( + number=int(row[STUDENT_NUMBERS]), + name=row[STUDENT_NAMES], + group_id=row[GROUP_IDS], + tg_id=row[STUDENT_TELEGRAM_IDS], + subjects=row[STUDENT_SUBJECTS].split(','), + is_subbed=bool(int(row[STUDENT_SUBSCRIPTION])), ) ) @@ -183,7 +214,7 @@ class Manager: return Student( int(row[STUDENT_NUMBERS]), name, row[GROUP_IDS], - row[TELEGRAM_IDS], + row[STUDENT_TELEGRAM_IDS], row[STUDENT_SUBJECTS].split(',') ) except StopIteration: @@ -194,7 +225,7 @@ class Manager: values = self.get_values("StudentList") - f = lambda row: tg_id == row[TELEGRAM_IDS] + f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS] try: row = next(filter(f, values)) return Student( @@ -242,13 +273,13 @@ class Manager: f = lambda row: token == row[AUTH_TOKEN] try: row = next(filter(f, values)) - if row[TELEGRAM_IDS] == tg_id: + if row[STUDENT_TELEGRAM_IDS] == tg_id: raise StudentAlreadyAuthed("Студент уже зарегистрировался") - elif row[TELEGRAM_IDS]: + elif row[STUDENT_TELEGRAM_IDS]: raise AnotherStudentAlreadyAuthed( "Другой студент зарегистрировался по token" ) - row[TELEGRAM_IDS] = tg_id + row[STUDENT_TELEGRAM_IDS] = tg_id self.write_values(values, "StudentList") return Student( int(row[STUDENT_NUMBERS]), @@ -258,3 +289,40 @@ class Manager: ) except StopIteration: raise StudentNotFound("Не могу найти студента по token") + + + def sub_by_tg_id(self, tg_id: str) -> bool: + """Подписка пользователя по tg_id""" + + values = self.get_values("StudentList") + + f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS] + try: + row = next(filter(f, values)) + if row[STUDENT_SUBSCRIPTION] == '1': + raise StudentAlreadySubbed("Студент уже подписался") + + row[STUDENT_SUBSCRIPTION] = 1 + self.write_values(values, "StudentList") + return True + except StopIteration: + raise StudentNotFound("Не могу найти студента по tg_id") + + def unsub_by_tg_id(self, tg_id: str) -> bool: + """Подписка пользователя по tg_id""" + + values = self.get_values("StudentList") + + f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS] + try: + row = next(filter(f, values)) + if row[STUDENT_SUBSCRIPTION] == '0': + raise StudentAlreadyUnsubbed( + "Студент уже не подписан (или никогда не был)" + ) + + row[STUDENT_SUBSCRIPTION] = 0 + self.write_values(values, "StudentList") + return True + except StopIteration: + raise StudentNotFound("Не могу найти студента по tg_id") diff --git a/TelegramBot/bot.py b/TelegramBot/bot.py index ac23889..706414b 100644 --- a/TelegramBot/bot.py +++ b/TelegramBot/bot.py @@ -66,7 +66,7 @@ def start(update: Update, context: CallbackContext): return if req.status_code != 200: - LOG.error(f"Ошибка авторизации, у пользователя @{username} {data}") + LOG.error(f"Ошибка авторизации у пользователя @{username} {data}") err = data.get("error", "") update.message.reply_text(text=MESSAGES.Auth.failure(err)) update.message.reply_sticker(MESSAGES.Stickers.DEAD) @@ -176,7 +176,6 @@ def grades_view(update: Update, context: CallbackContext): return GRADES_ASSNT - def broadcast(update: Update, context: CallbackContext): """Команда: разослать студентам.""" access_message = update.message.reply_text("🔐 Проверка доступа...") @@ -244,6 +243,68 @@ def cancel(update: Update, context: CallbackContext): return ConversationHandler.END +def sub(update: Update, context: CallbackContext): + """Стартовое сообщение (к команде /start)""" + + user_id = update.message.from_user.id + username = update.message.from_user.username + + # Длинная операция, сообщим о запущенном процессе. + update.message.reply_text(text=MESSAGES.Sub.START) + + LOG.info(f"Subcripting {user_id}") + + try: + req = requests.get( + f"{HOST}/sub", + params={"tg_id": user_id}, + ) + data = req.json() + except requests.exceptions.RequestException: + LOG.exception("@{user_id} вызвал: Ошибка подключения к сервису Spreadsheets.") + update.message.reply_sticker(MESSAGES.Stickers.DEAD) + return + + if req.status_code != 200: + LOG.error(f"Ошибка подписки, у пользователя @{username} {data}") + err = data.get("error", "") + update.message.reply_text(text=MESSAGES.Sub.failure(err)) + update.message.reply_sticker(MESSAGES.Stickers.bad()) + return + + update.message.reply_text(text=MESSAGES.Sub.SUBBED) + +def unsub(update: Update, context: CallbackContext): + """Стартовое сообщение (к команде /start)""" + + user_id = update.message.from_user.id + username = update.message.from_user.username + + # Длинная операция, сообщим о запущенном процессе. + update.message.reply_text(text=MESSAGES.Unsub.START) + + LOG.info(f"Unsubcripting {user_id}") + + try: + req = requests.get( + f"{HOST}/unsub", + params={"tg_id": user_id}, + ) + data = req.json() + except requests.exceptions.RequestException: + LOG.exception("@{user_id} вызвал: Ошибка подключения к сервису Spreadsheets.") + update.message.reply_sticker(MESSAGES.Stickers.DEAD) + return + + if req.status_code != 200: + LOG.error(f"Ошибка отписки, у пользователя @{username} {data}") + err = data.get("error", "") + update.message.reply_text(text=MESSAGES.Unsub.failure(err)) + update.message.reply_sticker(MESSAGES.Stickers.bad()) + return + + update.message.reply_text(text=MESSAGES.Unsub.UNSUBBED) + # ==================================================================== HANDLERS @@ -272,9 +333,14 @@ def main() -> None: fallbacks=[CommandHandler("cancel", cancel)], ) + sub_handler = CommandHandler("sub", sub) + unsub_handler = CommandHandler("unsub", unsub) + dispatcher.add_handler(start_handler) dispatcher.add_handler(grades_handler) dispatcher.add_handler(broadcast_handler) + dispatcher.add_handler(sub_handler) + dispatcher.add_handler(unsub_handler) # Начало работы бота updater.start_polling() diff --git a/TelegramBot/messages/messages.py b/TelegramBot/messages/messages.py index 661383e..451c6aa 100644 --- a/TelegramBot/messages/messages.py +++ b/TelegramBot/messages/messages.py @@ -55,6 +55,32 @@ class Messages: def timeit(t: float): return f"⏱ Посмотрели за {t:.2f}с" + class Sub: + START = "📜✍ Оформляю подписку на уведомления..." + SUBBED = "👌 Подписка на уведомления оформлена" + + @staticmethod + def failure(err: str): + cases = { + "StudentNotFound": "⛔ Для подписки нужно зарегистрироваться." + + "Команда /start поможет с регистрацией", + "StudentAlreadySubbed": "💁‍♀️ Подписка уже оформлена ранее", + } + return cases.get(err, "😟 Возникли неполадки") + + class Unsub: + START = "📜✍ Оформляю отписку от уведомлений..." + UNSUBBED = "🙄 Подписка на уведомления отменена" + + @staticmethod + def failure(err: str): + cases = { + "StudentNotFound": "⛔ Для отпидски от уведомлений нужно зарегистрироваться." + + "Команда /start поможет с регистрацией", + "StudentAlreadyUnsubbed": "💁‍♀️ Подписка на уведомления не была оформлена или уже отменена", + } + return cases.get(err, "😟 Возникли неполадки") + class Spreadsheets: pass