From 471cf43f601d4ab0917e66b8c444cc974b6ad80e Mon Sep 17 00:00:00 2001 From: "serafim.urukov" Date: Tue, 10 Aug 2021 01:01:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=20=D0=BF=D0=BE=D0=B4=D1=85=D0=BE=D0=B4=20=D0=BA=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B5=20=D1=81=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpreadsheetsService/app.py | 80 +++++++++----------- TelegramBot/bot.py | 122 +++++++++++++++++++++++-------- TelegramBot/messages/messages.py | 22 +++--- 3 files changed, 141 insertions(+), 83 deletions(-) diff --git a/SpreadsheetsService/app.py b/SpreadsheetsService/app.py index 455f67a..07fe10d 100644 --- a/SpreadsheetsService/app.py +++ b/SpreadsheetsService/app.py @@ -1,27 +1,12 @@ from functools import wraps + +from werkzeug.exceptions import * from manager import * import flask import logging import models import time -UNEXPECTED_ERROR_DICT = dict( - error=True, - desc='UnexpectedError' - ) -STUDENT_NOT_FOUND_DICT = dict( - error=True, - desc='StudentNotFound' - ) -STUDENT_ALREADY_AUTHED_DICT = dict( - error = True, - desc='StudentAlreadyAuthed' - ) -ANOTHER_STUDENT_ALREADY_AUTHED_DICT = dict( - error = True, - desc='AnotherStudentAlreadyAuthed' - ) - logging.basicConfig() LOG = logging.getLogger(__name__) LOG.setLevel(logging.DEBUG) @@ -60,22 +45,12 @@ def hello_world(): @app.route("/auth") @availability def auth(): - token = flask.request.args.get("token") - tg_id = flask.request.args.get("tg_id") - try: - student = app.db.auth_by_token(token, tg_id) - return flask.jsonify(dict( - student=student, - error=False - )) - except StudentAlreadyAuthed: - return flask.jsonify(STUDENT_ALREADY_AUTHED_DICT) - except AnotherStudentAlreadyAuthed: - return flask.jsonify(ANOTHER_STUDENT_ALREADY_AUTHED_DICT) - except StudentNotFound: - return flask.jsonify(STUDENT_NOT_FOUND_DICT) - except: - return flask.jsonify(UNEXPECTED_ERROR_DICT) + token = flask.request.args.get("token", None) + tg_id = flask.request.args.get("tg_id", None) + if token is None or tg_id is None: + raise BadRequest("Не предоставлены token и tg_id!") + student = app.db.auth_by_token(token, tg_id) + return flask.jsonify(dict(student=student)) @app.route("/grades") @@ -83,24 +58,41 @@ def auth(): def grades(): """Возвращает оценки по id пользователя из телеграма.""" tg_id = flask.request.args.get("tg_id") - - try: - student = app.db.get_student_by_tg_id(tg_id) - app.db.set_assignments_for_student(student) - return flask.jsonify( + student = app.db.get_student_by_tg_id(tg_id) + app.db.set_assignments_for_student(student) + return flask.jsonify( dict( - error=False, score=student.score, grade=student.grade, assignments=student.assignments, ) ) - except StudentNotFound: - return flask.jsonify(STUDENT_NOT_FOUND_DICT) - except: - return flask.jsonify(UNEXPECTED_ERROR_DICT) - + +@app.errorhandler(StudentAlreadyAuthed) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": err.message}), 409 + + +@app.errorhandler(AnotherStudentAlreadyAuthed) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": err.message}), 403 + + +@app.errorhandler(StudentNotFound) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": err.message}), 404 + + +@app.errorhandler(BadRequest) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": err.description}), 400 + + +# Любая другая ошибка вылетит вот здесь +@app.errorhandler(Exception) +def show_error(err): + return flask.jsonify({"error": type(err).__name__, "message": str(err)}), 500 if __name__ == "__main__": diff --git a/TelegramBot/bot.py b/TelegramBot/bot.py index 0e32e21..489b9da 100644 --- a/TelegramBot/bot.py +++ b/TelegramBot/bot.py @@ -1,5 +1,7 @@ import logging +from typing import Any, Optional import requests +import time from pathlib import Path @@ -18,21 +20,82 @@ from messages import Messages TOKEN = Path(".secrets/bot_token.txt").read_text() MESSAGES = Messages() HOST = "http://carolyn-spreadsheets:5000" +"""ID канала где собираются логи""" CHAT_LOG_ID = -507530583 + +class TgLogger(logging.Logger): + """Обертка на логгер, чтобы можно было использовать обычные лог-функции, + но при этом получать логи в телеграм чат. + + Есть баг, который не позволяет вызывать лог с ленивыми аргументами. Если + вызывать + ```python + LOG.warning("%s", var) + ``` + , то можно получить такую ошибку: + ```python + TypeError: _log_to_telegram() got multiple values for argument 'msg' + ``` + Поэтому используйте, + ``` + LOG.warning(f"{var}") + ``` + """ + + def __init__(self, name, level=logging.NOTSET): + self._bot = None + self._chat_log_id = CHAT_LOG_ID + return super(TgLogger, self).__init__(name, level) + + @property + def bot(self): + return self._bot + + @bot.setter + def bot(self, value): + self._bot = value + + def _log_to_telegram( + self, + msg: str, + *args, + emoji: str, + ): + if self._bot is not None: + txt = f"{emoji} {msg};" + self._bot.send_message(chat_id=CHAT_LOG_ID, text=txt) + + def warning(self, msg: Any, *args: Any, **kwargs: Any) -> None: + self._log_to_telegram(msg=msg, emoji="⚠", *args) + return super().warning( + msg, + *args, + **kwargs, + ) + + def error(self, msg: Any, *args: Any, **kwargs: Any) -> None: + self._log_to_telegram(msg=msg, emoji="💢", *args) + return super().error( + msg, + *args, + **kwargs, + ) + + def exception(self, msg, *args: Any, **kwargs: Any) -> None: + self._log_to_telegram(msg=msg, emoji="💥", *args) + return super().exception(msg, *args, **kwargs) + + +updater = Updater(token=TOKEN, use_context=True) +dispatcher = updater.dispatcher + +logging.setLoggerClass(TgLogger) logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO ) LOG = logging.getLogger(__name__) - -LOG.debug("Token is %s", TOKEN) -updater = Updater(token=TOKEN, use_context=True) -dispatcher = updater.dispatcher - - -def log_to_group_chat(update: Update, text: str): - update.message.bot.send_message(chat_id=CHAT_LOG_ID, text=text) - +LOG.bot = updater.bot # ================================================================ BOT COMMANDS @@ -41,45 +104,38 @@ def start(update: Update, context: CallbackContext): """Стартовое сообщение (к команде /start)""" user_id = update.message.from_user.id + username = update.message.from_user.username + student_token = context.args[0] if len(context.args) > 0 else None - p = context.args[0] if len(context.args) > 0 else None - - if p is None: + if student_token is None: update.message.reply_markdown_v2(text=MESSAGES.auth.NO_AUTH) return + # Длинная операция, сообщим о запущенном процессе. update.message.reply_text(text=MESSAGES.auth.START) LOG.info(f"Authenticanting {user_id}") try: - data = requests.get( + req = requests.get( f"{HOST}/auth", - params={"token": p, "tg_id": user_id}, - ).json() - except requests.exceptions.ConnectionError: - log_to_group_chat(update, - text=MESSAGES.auth.failure_log( - {'desc':'ConnectionError'}) + params={"token": student_token, "tg_id": user_id}, ) + data = req.json() + except requests.exceptions.RequestException: + LOG.exception("@{username} вызвал: Ошибка подключения к сервису Spreadsheets.") update.message.reply_sticker(MESSAGES.stickers.DEAD) return - if data['error']: - log_to_group_chat(update, - text=MESSAGES.auth.failure_log(data) - ) - update.message.reply_text( - text=MESSAGES.auth.failure(data) - ) + if req.status_code != 200: + 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) return - log_to_group_chat(update, - text=MESSAGES.auth.success_log(data['student']) - ) update.message.reply_text( - text=MESSAGES.auth.hello(data['student']), + text=MESSAGES.auth.hello(data["student"]), ) @@ -87,6 +143,10 @@ def grades(update: Update, context: CallbackContext): """Команда: Получить оценки студента.""" user_id = update.message.from_user.id + + start = time.monotonic() + # Длинная операция, сообщим о запущенном процессе. + look_msg = update.message.reply_html(MESSAGES.score.START) try: data = requests.get(f"{HOST}/grades", params={"tg_id": user_id}).json() score = data["score"] @@ -99,6 +159,8 @@ def grades(update: Update, context: CallbackContext): except: LOG.exception("Failed to get grades.") update.message.reply_sticker(MESSAGES.stickers.DEAD) + diff = time.monotonic() - start + look_msg.edit_text(MESSAGES.score.timeit(diff)) def echo(update: Update, context: CallbackContext): diff --git a/TelegramBot/messages/messages.py b/TelegramBot/messages/messages.py index 0c233d9..b327ad3 100644 --- a/TelegramBot/messages/messages.py +++ b/TelegramBot/messages/messages.py @@ -11,15 +11,13 @@ class Messages: def hello(self, data): return f"Привет, {data['name']} 👋" - def failure(self, data): - if data['desc'] == 'StudentNotFound': - return "⛔ Ссылка больше не работает" - elif data['desc'] == 'StudentAlreadyAuthed': - return "⛔ Регистрация по этой ссылке уже прошла" - elif data['desc'] == 'AnotherStudentAlreadyAuthed': - return "😨 Кто-то другой уже зерегистрировался по этой ссылке" - else: - return "😟 Возникли неполадки" + def failure(self, err: str): + cases = { + "StudentNotFound": "⛔ Ссылка больше не работает", + "StudentAlreadyAuthed": "⛔ Регистрация по этой ссылке уже прошла", + "AnotherStudentAlreadyAuthed": "😨 Кто-то другой уже зерегистрировался по этой ссылке", + } + return cases.get(err, "😟 Возникли неполадки") def success_log(self, data): return f"✅ AUTH: {data['name']} " @@ -28,6 +26,8 @@ class Messages: return f"🟥 AUTH: {data['desc']}" class Score: + START = "👀 Посмотрим (~3с) ..." + def get(self, grade, score, n_of_assignments): summary = ( f"Ранг: {score:.2f} {n_of_assignments} зад.\n" @@ -35,6 +35,10 @@ class Messages: ) return summary + @staticmethod + def timeit(t: float): + return f"⏱ Посмотрели за {t:.2f}с" + class Spreadsheets: def __init__(self): self.DEAD = "⚰ Сервис «таблицы» недоступен"