diff --git a/SpreadsheetsService/app.py b/SpreadsheetsService/app.py index 31dfd56..d6b5d0e 100644 --- a/SpreadsheetsService/app.py +++ b/SpreadsheetsService/app.py @@ -4,12 +4,11 @@ from werkzeug.exceptions import * from manager import * import flask import logging -import uuid logging.basicConfig() LOG = logging.getLogger(__name__) LOG.setLevel(logging.DEBUG) -SUPER_USER = (420, 228) +ADMIN = "admin" app = flask.Flask(__name__) app.db = None @@ -40,11 +39,11 @@ def availability(func): def superuser(func): @wraps(func) def wrapper(*args, **kwargs): - god_mode = flask.request.args.get("god_mode", False) + god_mode = flask.request.args.get("god_mode", str(False)) if god_mode.lower() != "true": tg_id = flask.request.args.get("tg_id", -1) student: Student = app.db.get_student_by_tg_id(tg_id) - if student.number not in SUPER_USER: + if student.group_id != ADMIN: raise Forbidden(f"Доступ запрещен для студента {student.name}") result = func(*args, **kwargs) return result @@ -79,6 +78,7 @@ def grades(): dict( subjects=student.subjects, assignments=student.assignments, + fingerprint=student.fingerprint, ) ) @@ -92,10 +92,11 @@ def fingerprint(): app.db.set_assignments_for_student(student) return flask.jsonify( dict( - fingerprint=uuid.uuid3(uuid.NAMESPACE_DNS, "%s" % student.assignments), + fingerprint=student.fingerprint, ) ) + @app.route("/student") @availability def student(): @@ -104,12 +105,13 @@ def student(): student = app.db.get_student_by_tg_id(tg_id) return flask.jsonify(dict(student=student)) + @app.route("/students") @availability @superuser def broadcast(*args, **kwargs): """Выполняет рассылку студентам.""" - sub_only = flask.request.args.get("sub_only", False) + sub_only = flask.request.args.get("sub_only", str(False)) if sub_only.lower() == "true": return flask.jsonify(app.db.get_all_groups_only_sub_students()) else: diff --git a/SpreadsheetsService/manager/manager.py b/SpreadsheetsService/manager/manager.py index 2410f41..a81dc79 100644 --- a/SpreadsheetsService/manager/manager.py +++ b/SpreadsheetsService/manager/manager.py @@ -47,8 +47,6 @@ def timed_lru_cache(seconds: int, maxsize: int = 128): if datetime.utcnow() >= func.expiration: func.cache_clear() func.expiration = datetime.utcnow() + func.lifetime - # if args[1] in NON_CACHED_RANGES: - # func.cache_clear() print(*args) return func(*args, **kwargs) @@ -118,10 +116,17 @@ class Manager: service = build("sheets", "v4", credentials=creds) self.sheet = service.spreadsheets() - @timed_lru_cache(seconds=150) - def get_values( - self, range_name: str = "StudentList" - ) -> list: # TODO: change subject + def get_values(self, range_name: str = "StudentList") -> list: + if range_name in NON_CACHED_RANGES: + return self._get_values(range_name) + else: + return self._cached_get_values(range_name) + + @timed_lru_cache(minutes=25) + def _cached_get_values(self, range_name): + return self._get_values(range_name) + + def _get_values(self, range_name: str = "StudentList") -> list: """Получить значения с таблицы. Returns: @@ -256,8 +261,7 @@ class Manager: assignment_value = list(map(toFloat, assignment_values[student.number - 1])) notes_range, n_row = row[ASSIGNMENT_NOTES_RANGES].split(",") note = self.get_values(notes_range)[student.number - 1][int(n_row)] - note = "Замечаний по работе нет." if note == "-" \ - else f"Замечания:\n{note}" + note = "Замечаний по работе нет." if note == "-" else f"Замечания:\n{note}" student.add_assignment( Assignment( name=row[ASSIGNMENT_NAMES], diff --git a/SpreadsheetsService/models/student.py b/SpreadsheetsService/models/student.py index bd982db..04443b5 100644 --- a/SpreadsheetsService/models/student.py +++ b/SpreadsheetsService/models/student.py @@ -1,12 +1,19 @@ from .assignment import Assignment +import uuid class Student(dict): """Позволяет выполнять операции над студентами.""" - def __init__(self, number: int, name: str, group_id: str, - tg_id: int = None, subjects: list = None, - is_subbed: bool = False): + def __init__( + self, + number: int, + name: str, + group_id: str, + tg_id: int = None, + subjects: list = None, + is_subbed: bool = False, + ): self.number: int = number self.name: str = name self.group_id: str = group_id @@ -14,6 +21,7 @@ class Student(dict): self.subjects = subjects self.assignments = [] self.is_subbed = is_subbed + self.fingerprint = "" dict.__init__( self, @@ -22,18 +30,20 @@ class Student(dict): group_id=self.group_id, tg_id=self.tg_id, assignments=self.assignments, - is_subbed=self.is_subbed + fingerprint=self.fingerprint, + is_subbed=self.is_subbed, ) def add_assignment(self, assignment: Assignment): self.assignments.append(assignment) - - @property - def grade(self) -> float: - """Получить среднюю оценку студента по текущему контролю.""" - return 2 + self._update_fingerprint() @property def score(self): """Количество баллов у студента.""" return sum([a.total for a in self.assignments]) + + def _update_fingerprint(self) -> None: + fingerprint = uuid.uuid3(uuid.NAMESPACE_DNS, "%s" % self.assignments) + self.fingerprint = fingerprint + self["fingerprint"] = fingerprint diff --git a/TelegramBot/bot.py b/TelegramBot/bot.py index caee0ec..8758a53 100644 --- a/TelegramBot/bot.py +++ b/TelegramBot/bot.py @@ -11,6 +11,7 @@ from telegram import ( Update, ParseMode, ) +from telegram import message from telegram.ext import ( CallbackContext, CommandHandler, @@ -25,10 +26,12 @@ from telegram.ext.callbackqueryhandler import CallbackQueryHandler from utils.inline_keyboard import * from messages import Messages -TOKEN = Path(".secrets/bot_token.txt").read_text() -MESSAGES = Messages() +ADMIN = "admin" HOST = "http://carolyn-spreadsheets:5000" -SUB_TIME_DELTA = datetime.timedelta(minutes=15) +MESSAGES = Messages() +SUB_TIME_DELTA = datetime.timedelta(minutes=30) +TOKEN = Path(".secrets/bot_token.txt").read_text() +DEV_CHAT = -507530583 # ID канала где собираются логи BROADCAST_MESSAGE, BROADCAST_PUBLISH_DONE = range(2) GRADES_ASSNT, GRADES_VIEW = range(2) @@ -88,6 +91,7 @@ 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) @@ -98,6 +102,12 @@ def grades_start(update: Update, context: CallbackContext): update.message.reply_sticker(MESSAGES.Stickers.DEAD) return ConversationHandler.END + # При инициализации бота словарь отсутствует + if "fingerprints" not in context.bot_data: + context.bot_data["fingerprints"] = defaultdict(str) + + context.bot_data["fingerprints"][user_id] = data["fingerprint"] + context.user_data["subjects"] = data["subjects"] context.user_data["assignments"] = data["assignments"] diff = time.monotonic() - start @@ -228,17 +238,51 @@ def publish_message(update: Update, context: CallbackContext): def publish_done(update: Update, context: CallbackContext): """Рассылка выполняется здесь.""" - message = f"📣 {update.message.text}\n— {update.message.from_user.name}" + + def send_message(chat_id: int, message: telegram.Message): + txt = message.text if message.text else message.caption + txt = "" if not txt else txt + txt = f"📣 {txt}\n— {message.from_user.name}" + + if message.document: + context.bot.send_document( + chat_id=chat_id, + caption=txt, + parse_mode="HTML", + document=message.document, + ) + elif message.photo: + file = message.photo[-1] + context.bot.send_photo( + chat_id=chat_id, caption=txt, parse_mode="HTML", photo=file + ) + elif message.animation: + context.bot.send_animation( + chat_id=chat_id, + caption=txt, + parse_mode="HTML", + animation=message.animation, + ) + else: + context.bot.send_message(chat_id=chat_id, text=txt, parse_mode="HTML") + group = context.user_data["broadcast_to"] send_status = update.message.reply_text("0%") - students = context.bot_data["groups"][group]["students"] + try: + students = context.bot_data["groups"][group]["students"] + except: + LOG.exception( + "Не получилось взять студента с группой, %s, группы %s", + group, + context.bot_data["groups"], + ) for i, student in enumerate(students): progress = i / len(students) * 100 if student["tg_id"]: - context.bot.send_message( - chat_id=student["tg_id"], text=message, parse_mode="HTML" - ) + send_message(student["tg_id"], update.message) send_status.edit_text(f"{progress:.1f}% 🏃‍♂️ Рассылка") + + send_message(DEV_CHAT, update.message) send_status.edit_text("100% 👍 Все сообщения разосланы") return ConversationHandler.END @@ -288,8 +332,6 @@ def unsub(update: Update, context: CallbackContext): # Длинная операция, сообщим о запущенном процессе. msg = update.message.reply_text(text=MESSAGES.Unsub.START) - # LOG.info(f"Unsubcripting {user_id}") - try: req = requests.get( f"{HOST}/unsub", @@ -310,6 +352,7 @@ def unsub(update: Update, context: CallbackContext): msg.edit_text(text=MESSAGES.Unsub.UNSUBBED) + def variant(update: Update, context: CallbackContext): """Проверка своего варианта внутри группы""" @@ -339,6 +382,14 @@ def variant(update: Update, context: CallbackContext): msg.edit_text(text=MESSAGES.Variant.get(data["student"]["number"])) + +def error(update: Update, context: CallbackContext): + LOG.error(msg="Exception while handling an update:", exc_info=context.error) + update.message.reply_text("Ой, кажется у меня %s" % repr(context.error)) + update.message.reply_sticker(MESSAGES.Stickers.DEAD) + return ConversationHandler.END + + # ======================================================================= SCHED @@ -410,7 +461,9 @@ def main() -> None: entry_points=[CommandHandler("broadcast", broadcast)], states={ BROADCAST_MESSAGE: [MessageHandler(Filters.text, publish_message)], - BROADCAST_PUBLISH_DONE: [MessageHandler(Filters.text, publish_done)], + BROADCAST_PUBLISH_DONE: [ + MessageHandler(Filters.all & ~Filters.command, publish_done) + ], }, fallbacks=[CommandHandler("cancel", cancel)], ) @@ -425,6 +478,7 @@ def main() -> None: dispatcher.add_handler(sub_handler) dispatcher.add_handler(unsub_handler) dispatcher.add_handler(variant_handler) + dispatcher.add_error_handler(error) # Начало работы бота updater.start_polling() diff --git a/TelegramBot/utils/tg_logger.py b/TelegramBot/utils/tg_logger.py index 662faa7..2bede3e 100644 --- a/TelegramBot/utils/tg_logger.py +++ b/TelegramBot/utils/tg_logger.py @@ -21,7 +21,7 @@ class TgLogger(logging.Logger): ``` """ - CHAT_LOG_ID = -507530583 # ID канала где собираются логи + DEV_CHAT = -507530583 # ID канала где собираются логи def __init__(self, name, level=logging.NOTSET): self._bot = None @@ -43,7 +43,7 @@ class TgLogger(logging.Logger): ): if self._bot is not None: txt = f"{emoji} {msg};" - self._bot.send_message(chat_id=self.CHAT_LOG_ID, text=txt) + self._bot.send_message(chat_id=self.DEV_CHAT, text=txt) def warning(self, msg: Any, *args: Any, **kwargs: Any) -> None: self._log_to_telegram(msg=msg, emoji="⚠", *args)