Много изменений:

- исправлена проблема кэша
- в рассылке можно отправлять файлы и фото
- отпечаток оценок обновляется при запросе
This commit is contained in:
serafim.urukov 2021-09-12 14:32:05 +03:00
parent f74b970c2b
commit a2ad18ca8c
5 changed files with 106 additions and 36 deletions

View File

@ -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:

View File

@ -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],

View File

@ -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

View File

@ -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<i>— {update.message.from_user.name}</i>"
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<i>— {message.from_user.name}</i>"
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%")
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()

View File

@ -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)