mirror of
https://github.com/Faridik/Carolyn.git
synced 2026-07-08 13:21:53 +00:00
- добавлен новый узел /grades/fingerprint
- в планировщик добавлен джоб, который проверяет не изменились ли оценки студентов
This commit is contained in:
parent
9a89cde748
commit
2d975df4b1
@ -4,8 +4,7 @@ from werkzeug.exceptions import *
|
|||||||
from manager import *
|
from manager import *
|
||||||
import flask
|
import flask
|
||||||
import logging
|
import logging
|
||||||
import models
|
import uuid
|
||||||
import time
|
|
||||||
|
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -41,11 +40,13 @@ def availability(func):
|
|||||||
def superuser(func):
|
def superuser(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
tg_id = flask.request.args.get("tg_id", -1)
|
god_mode = flask.request.args.get("god_mode", False)
|
||||||
student: Student = app.db.get_student_by_tg_id(tg_id)
|
if god_mode.lower() != "true":
|
||||||
if student.number not in SUPER_USER:
|
tg_id = flask.request.args.get("tg_id", -1)
|
||||||
raise Forbidden(f"Доступ запрещен для студента {student.name}")
|
student: Student = app.db.get_student_by_tg_id(tg_id)
|
||||||
result = func(student, *args, **kwargs)
|
if student.number not in SUPER_USER:
|
||||||
|
raise Forbidden(f"Доступ запрещен для студента {student.name}")
|
||||||
|
result = func(*args, **kwargs)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
@ -82,12 +83,30 @@ def grades():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/broadcast")
|
@app.route("/grades/fingerprint")
|
||||||
|
@availability
|
||||||
|
def fingerprint():
|
||||||
|
"""Возвращает оценки в виде хеш-суммы."""
|
||||||
|
tg_id = flask.request.args.get("tg_id")
|
||||||
|
student = app.db.get_student_by_tg_id(tg_id)
|
||||||
|
app.db.set_assignments_for_student(student)
|
||||||
|
return flask.jsonify(
|
||||||
|
dict(
|
||||||
|
fingerprint=uuid.uuid3(uuid.NAMESPACE_DNS, "%s" % student.assignments),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/students")
|
||||||
@availability
|
@availability
|
||||||
@superuser
|
@superuser
|
||||||
def broadcast(student):
|
def broadcast(*args, **kwargs):
|
||||||
"""Выполняет рассылку студентам."""
|
"""Выполняет рассылку студентам."""
|
||||||
return flask.jsonify(app.db.get_all_groups_only_sub_students())
|
sub_only = flask.request.args.get("sub_only", False)
|
||||||
|
if sub_only.lower() == "true":
|
||||||
|
return flask.jsonify(app.db.get_all_groups_only_sub_students())
|
||||||
|
else:
|
||||||
|
return flask.jsonify(app.db.get_all_groups())
|
||||||
|
|
||||||
|
|
||||||
@app.route("/sub")
|
@app.route("/sub")
|
||||||
@ -97,6 +116,7 @@ def sub():
|
|||||||
is_subbed = app.db.sub_by_tg_id(tg_id)
|
is_subbed = app.db.sub_by_tg_id(tg_id)
|
||||||
return flask.jsonify(dict(is_subbed=is_subbed))
|
return flask.jsonify(dict(is_subbed=is_subbed))
|
||||||
|
|
||||||
|
|
||||||
@app.route("/unsub")
|
@app.route("/unsub")
|
||||||
@availability
|
@availability
|
||||||
def unsub():
|
def unsub():
|
||||||
@ -105,10 +125,9 @@ def unsub():
|
|||||||
return flask.jsonify(dict(is_unsubbed=is_unsubbed))
|
return flask.jsonify(dict(is_unsubbed=is_unsubbed))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(StudentAlreadyAuthed)
|
@app.errorhandler(StudentAlreadyAuthed)
|
||||||
def show_error(err):
|
def show_error(err):
|
||||||
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 409
|
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 400
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(AnotherStudentAlreadyAuthed)
|
@app.errorhandler(AnotherStudentAlreadyAuthed)
|
||||||
@ -120,13 +139,16 @@ def show_error(err):
|
|||||||
def show_error(err):
|
def show_error(err):
|
||||||
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 404
|
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 404
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(StudentAlreadySubbed)
|
@app.errorhandler(StudentAlreadySubbed)
|
||||||
def show_error(err):
|
def show_error(err):
|
||||||
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 405
|
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 400
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(StudentAlreadyUnsubbed)
|
@app.errorhandler(StudentAlreadyUnsubbed)
|
||||||
def show_error(err):
|
def show_error(err):
|
||||||
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 406
|
return flask.jsonify({"error": type(err).__name__, "message": err.message}), 400
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(BadRequest)
|
@app.errorhandler(BadRequest)
|
||||||
def show_error(err):
|
def show_error(err):
|
||||||
|
|||||||
@ -31,6 +31,7 @@ ASSIGNMENT_HOW_TO_DISPLAY = 7
|
|||||||
ASSIGNMENT_NOTES_RANGES = 8
|
ASSIGNMENT_NOTES_RANGES = 8
|
||||||
TOKEN_FILE = pathlib.Path() / ".secrets" / "token.json"
|
TOKEN_FILE = pathlib.Path() / ".secrets" / "token.json"
|
||||||
CLIENT_SECRET_FILE = pathlib.Path() / ".secrets" / "client_secret.json"
|
CLIENT_SECRET_FILE = pathlib.Path() / ".secrets" / "client_secret.json"
|
||||||
|
NON_CACHED_RANGES = ("StudentList",)
|
||||||
|
|
||||||
|
|
||||||
def timed_lru_cache(seconds: int, maxsize: int = 128):
|
def timed_lru_cache(seconds: int, maxsize: int = 128):
|
||||||
@ -44,7 +45,9 @@ def timed_lru_cache(seconds: int, maxsize: int = 128):
|
|||||||
if datetime.utcnow() >= func.expiration:
|
if datetime.utcnow() >= func.expiration:
|
||||||
func.cache_clear()
|
func.cache_clear()
|
||||||
func.expiration = datetime.utcnow() + func.lifetime
|
func.expiration = datetime.utcnow() + func.lifetime
|
||||||
|
# if args[1] in NON_CACHED_RANGES:
|
||||||
|
# func.cache_clear()
|
||||||
|
print(*args)
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
return wrapped_func
|
return wrapped_func
|
||||||
@ -63,16 +66,19 @@ class AnotherStudentAlreadyAuthed(Exception):
|
|||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
class StudentAlreadySubbed(Exception):
|
class StudentAlreadySubbed(Exception):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
class StudentAlreadyUnsubbed(Exception):
|
class StudentAlreadyUnsubbed(Exception):
|
||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(self.message)
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
class Manager:
|
class Manager:
|
||||||
"""Позволяет выполнять операции с Google таблицами."""
|
"""Позволяет выполнять операции с Google таблицами."""
|
||||||
|
|
||||||
@ -99,7 +105,6 @@ class Manager:
|
|||||||
if creds and creds.expired and creds.refresh_token:
|
if creds and creds.expired and creds.refresh_token:
|
||||||
creds.refresh(Request())
|
creds.refresh(Request())
|
||||||
else:
|
else:
|
||||||
print(CLIENT_SECRET_FILE)
|
|
||||||
flow = InstalledAppFlow.from_client_secrets_file(
|
flow = InstalledAppFlow.from_client_secrets_file(
|
||||||
str(CLIENT_SECRET_FILE), scopes
|
str(CLIENT_SECRET_FILE), scopes
|
||||||
)
|
)
|
||||||
@ -120,13 +125,11 @@ class Manager:
|
|||||||
Returns:
|
Returns:
|
||||||
list of rows [[val1, val2, ...], [], ...]
|
list of rows [[val1, val2, ...], [], ...]
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = (
|
result = (
|
||||||
self.sheet.values()
|
self.sheet.values()
|
||||||
.get(spreadsheetId=self.spreadsheet_id, range=range_name)
|
.get(spreadsheetId=self.spreadsheet_id, range=range_name)
|
||||||
.execute()
|
.execute()
|
||||||
)
|
)
|
||||||
|
|
||||||
return result.get("values", [])
|
return result.get("values", [])
|
||||||
|
|
||||||
def write_values(self, values: list, range_name: str):
|
def write_values(self, values: list, range_name: str):
|
||||||
@ -176,7 +179,7 @@ class Manager:
|
|||||||
name=row[STUDENT_NAMES],
|
name=row[STUDENT_NAMES],
|
||||||
group_id=row[GROUP_IDS],
|
group_id=row[GROUP_IDS],
|
||||||
tg_id=row[STUDENT_TELEGRAM_IDS],
|
tg_id=row[STUDENT_TELEGRAM_IDS],
|
||||||
subjects=row[STUDENT_SUBJECTS].split(','),
|
subjects=row[STUDENT_SUBJECTS].split(","),
|
||||||
is_subbed=bool(int(row[STUDENT_SUBSCRIPTION])),
|
is_subbed=bool(int(row[STUDENT_SUBSCRIPTION])),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -187,8 +190,8 @@ class Manager:
|
|||||||
"""Получить список всех групп."""
|
"""Получить список всех групп."""
|
||||||
values = self.get_values()
|
values = self.get_values()
|
||||||
groups = defaultdict(lambda: Group("0"))
|
groups = defaultdict(lambda: Group("0"))
|
||||||
f = lambda row: row[STUDENT_SUBSCRIPTION] == '1'
|
f = lambda row: row[STUDENT_SUBSCRIPTION] == "1"
|
||||||
for row in filter(f,values):
|
for row in filter(f, values):
|
||||||
groups[row[GROUP_IDS]].group_id = row[GROUP_IDS]
|
groups[row[GROUP_IDS]].group_id = row[GROUP_IDS]
|
||||||
groups[row[GROUP_IDS]].add_student(
|
groups[row[GROUP_IDS]].add_student(
|
||||||
Student(
|
Student(
|
||||||
@ -196,7 +199,7 @@ class Manager:
|
|||||||
name=row[STUDENT_NAMES],
|
name=row[STUDENT_NAMES],
|
||||||
group_id=row[GROUP_IDS],
|
group_id=row[GROUP_IDS],
|
||||||
tg_id=row[STUDENT_TELEGRAM_IDS],
|
tg_id=row[STUDENT_TELEGRAM_IDS],
|
||||||
subjects=row[STUDENT_SUBJECTS].split(','),
|
subjects=row[STUDENT_SUBJECTS].split(","),
|
||||||
is_subbed=bool(int(row[STUDENT_SUBSCRIPTION])),
|
is_subbed=bool(int(row[STUDENT_SUBSCRIPTION])),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -212,10 +215,11 @@ class Manager:
|
|||||||
try:
|
try:
|
||||||
row = next(filter(f, values))
|
row = next(filter(f, values))
|
||||||
return Student(
|
return Student(
|
||||||
int(row[STUDENT_NUMBERS]), name,
|
int(row[STUDENT_NUMBERS]),
|
||||||
row[GROUP_IDS],
|
name,
|
||||||
row[STUDENT_TELEGRAM_IDS],
|
row[GROUP_IDS],
|
||||||
row[STUDENT_SUBJECTS].split(',')
|
row[STUDENT_TELEGRAM_IDS],
|
||||||
|
row[STUDENT_SUBJECTS].split(","),
|
||||||
)
|
)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise StudentNotFound("Не могу найти студента по имени")
|
raise StudentNotFound("Не могу найти студента по имени")
|
||||||
@ -229,10 +233,11 @@ class Manager:
|
|||||||
try:
|
try:
|
||||||
row = next(filter(f, values))
|
row = next(filter(f, values))
|
||||||
return Student(
|
return Student(
|
||||||
int(row[STUDENT_NUMBERS]),
|
int(row[STUDENT_NUMBERS]),
|
||||||
row[STUDENT_NAMES],
|
row[STUDENT_NAMES],
|
||||||
row[GROUP_IDS], tg_id,
|
row[GROUP_IDS],
|
||||||
row[STUDENT_SUBJECTS].split(',')
|
tg_id,
|
||||||
|
row[STUDENT_SUBJECTS].split(","),
|
||||||
)
|
)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise StudentNotFound("Не могу найти студента по telegram id")
|
raise StudentNotFound("Не могу найти студента по telegram id")
|
||||||
@ -246,22 +251,22 @@ class Manager:
|
|||||||
for row in filter(f, values):
|
for row in filter(f, values):
|
||||||
assignment_values = self.get_values(row[ASSIGNMENT_RANGES])
|
assignment_values = self.get_values(row[ASSIGNMENT_RANGES])
|
||||||
toFloat = lambda x: float(x.replace(",", "."))
|
toFloat = lambda x: float(x.replace(",", "."))
|
||||||
assignment_value = list(map(toFloat, assignment_values[student.number-1]))
|
assignment_value = list(map(toFloat, assignment_values[student.number - 1]))
|
||||||
notes_range, n_row = row[ASSIGNMENT_NOTES_RANGES].split(',')
|
notes_range, n_row = row[ASSIGNMENT_NOTES_RANGES].split(",")
|
||||||
note = self.get_values(notes_range)[student.number-1][int(n_row)]
|
note = self.get_values(notes_range)[student.number - 1][int(n_row)]
|
||||||
if note == '-':
|
if note == "-":
|
||||||
note = "Замечаний по работе нет."
|
note = "Замечаний по работе нет."
|
||||||
else:
|
else:
|
||||||
note = f"Замечания:\n {note}"
|
note = f"Замечания:\n {note}"
|
||||||
student.add_assignment(
|
student.add_assignment(
|
||||||
Assignment(
|
Assignment(
|
||||||
name=row[ASSIGNMENT_NAMES],
|
name=row[ASSIGNMENT_NAMES],
|
||||||
points=assignment_value,
|
points=assignment_value,
|
||||||
weight=toFloat(row[ASSIGNMENT_WEIGHTS]),
|
weight=toFloat(row[ASSIGNMENT_WEIGHTS]),
|
||||||
subject=row[ASSIGNMENT_SUBJECT],
|
subject=row[ASSIGNMENT_SUBJECT],
|
||||||
allow_to_display=bool(int(row[ASSIGNMENT_ALLOWS])),
|
allow_to_display=bool(int(row[ASSIGNMENT_ALLOWS])),
|
||||||
how_to_display=row[ASSIGNMENT_HOW_TO_DISPLAY],
|
how_to_display=row[ASSIGNMENT_HOW_TO_DISPLAY],
|
||||||
notes=note
|
notes=note,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -282,15 +287,15 @@ class Manager:
|
|||||||
row[STUDENT_TELEGRAM_IDS] = tg_id
|
row[STUDENT_TELEGRAM_IDS] = tg_id
|
||||||
self.write_values(values, "StudentList")
|
self.write_values(values, "StudentList")
|
||||||
return Student(
|
return Student(
|
||||||
int(row[STUDENT_NUMBERS]),
|
int(row[STUDENT_NUMBERS]),
|
||||||
row[STUDENT_NAMES],
|
row[STUDENT_NAMES],
|
||||||
row[GROUP_IDS], tg_id,
|
row[GROUP_IDS],
|
||||||
row[STUDENT_SUBJECTS].split(',')
|
tg_id,
|
||||||
|
row[STUDENT_SUBJECTS].split(","),
|
||||||
)
|
)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
raise StudentNotFound("Не могу найти студента по token")
|
raise StudentNotFound("Не могу найти студента по token")
|
||||||
|
|
||||||
|
|
||||||
def sub_by_tg_id(self, tg_id: str) -> bool:
|
def sub_by_tg_id(self, tg_id: str) -> bool:
|
||||||
"""Подписка пользователя по tg_id"""
|
"""Подписка пользователя по tg_id"""
|
||||||
|
|
||||||
@ -299,7 +304,7 @@ class Manager:
|
|||||||
f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS]
|
f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS]
|
||||||
try:
|
try:
|
||||||
row = next(filter(f, values))
|
row = next(filter(f, values))
|
||||||
if row[STUDENT_SUBSCRIPTION] == '1':
|
if row[STUDENT_SUBSCRIPTION] == "1":
|
||||||
raise StudentAlreadySubbed("Студент уже подписался")
|
raise StudentAlreadySubbed("Студент уже подписался")
|
||||||
|
|
||||||
row[STUDENT_SUBSCRIPTION] = 1
|
row[STUDENT_SUBSCRIPTION] = 1
|
||||||
@ -316,10 +321,10 @@ class Manager:
|
|||||||
f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS]
|
f = lambda row: tg_id == row[STUDENT_TELEGRAM_IDS]
|
||||||
try:
|
try:
|
||||||
row = next(filter(f, values))
|
row = next(filter(f, values))
|
||||||
if row[STUDENT_SUBSCRIPTION] == '0':
|
if row[STUDENT_SUBSCRIPTION] == "0":
|
||||||
raise StudentAlreadyUnsubbed(
|
raise StudentAlreadyUnsubbed(
|
||||||
"Студент уже не подписан (или никогда не был)"
|
"Студент уже не подписан (или никогда не был)"
|
||||||
)
|
)
|
||||||
|
|
||||||
row[STUDENT_SUBSCRIPTION] = 0
|
row[STUDENT_SUBSCRIPTION] = 0
|
||||||
self.write_values(values, "StudentList")
|
self.write_values(values, "StudentList")
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
|
from collections import defaultdict
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
|
import datetime
|
||||||
|
|
||||||
import telegram
|
import telegram
|
||||||
from telegram import (
|
from telegram import (
|
||||||
@ -19,17 +21,19 @@ from telegram.ext import (
|
|||||||
)
|
)
|
||||||
from telegram.ext.callbackqueryhandler import CallbackQueryHandler
|
from telegram.ext.callbackqueryhandler import CallbackQueryHandler
|
||||||
|
|
||||||
from utils import TgLogger
|
# from utils import TgLogger
|
||||||
from utils.inline_keyboard import *
|
from utils.inline_keyboard import *
|
||||||
from messages import Messages
|
from messages import Messages
|
||||||
|
|
||||||
TOKEN = Path(".secrets/bot_token.txt").read_text()
|
TOKEN = Path(".secrets/bot_token.txt").read_text()
|
||||||
MESSAGES = Messages()
|
MESSAGES = Messages()
|
||||||
HOST = "http://carolyn-spreadsheets:5000"
|
HOST = "http://carolyn-spreadsheets:5000"
|
||||||
|
SUB_TIME_DELTA = datetime.timedelta(minutes=15)
|
||||||
|
|
||||||
BROADCAST_MESSAGE, BROADCAST_PUBLISH_DONE = range(2)
|
BROADCAST_MESSAGE, BROADCAST_PUBLISH_DONE = range(2)
|
||||||
GRADES_ASSNT, GRADES_VIEW = range(2)
|
GRADES_ASSNT, GRADES_VIEW = range(2)
|
||||||
|
|
||||||
logging.setLoggerClass(TgLogger)
|
# logging.setLoggerClass(TgLogger)
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||||
)
|
)
|
||||||
@ -176,12 +180,13 @@ def grades_view(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
return GRADES_ASSNT
|
return GRADES_ASSNT
|
||||||
|
|
||||||
|
|
||||||
def broadcast(update: Update, context: CallbackContext):
|
def broadcast(update: Update, context: CallbackContext):
|
||||||
"""Команда: разослать студентам."""
|
"""Команда: разослать студентам."""
|
||||||
access_message = update.message.reply_text("🔐 Проверка доступа...")
|
access_message = update.message.reply_text("🔐 Проверка доступа...")
|
||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
try:
|
try:
|
||||||
r = requests.get(f"{HOST}/broadcast", params={"tg_id": user_id})
|
r = requests.get(f"{HOST}/students", params={"tg_id": user_id})
|
||||||
assert r.status_code == 200, "Доступ запрещен к /broadcast"
|
assert r.status_code == 200, "Доступ запрещен к /broadcast"
|
||||||
data = r.json()
|
data = r.json()
|
||||||
context.bot_data["groups"] = data
|
context.bot_data["groups"] = data
|
||||||
@ -250,9 +255,7 @@ def sub(update: Update, context: CallbackContext):
|
|||||||
username = update.message.from_user.username
|
username = update.message.from_user.username
|
||||||
|
|
||||||
# Длинная операция, сообщим о запущенном процессе.
|
# Длинная операция, сообщим о запущенном процессе.
|
||||||
update.message.reply_text(text=MESSAGES.Sub.START)
|
msg = update.message.reply_text(text=MESSAGES.Sub.START)
|
||||||
|
|
||||||
LOG.info(f"Subcripting {user_id}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = requests.get(
|
req = requests.get(
|
||||||
@ -272,7 +275,8 @@ def sub(update: Update, context: CallbackContext):
|
|||||||
update.message.reply_sticker(MESSAGES.Stickers.bad())
|
update.message.reply_sticker(MESSAGES.Stickers.bad())
|
||||||
return
|
return
|
||||||
|
|
||||||
update.message.reply_text(text=MESSAGES.Sub.SUBBED)
|
msg.edit_text(text=MESSAGES.Sub.SUBBED)
|
||||||
|
|
||||||
|
|
||||||
def unsub(update: Update, context: CallbackContext):
|
def unsub(update: Update, context: CallbackContext):
|
||||||
"""Стартовое сообщение (к команде /start)"""
|
"""Стартовое сообщение (к команде /start)"""
|
||||||
@ -281,9 +285,9 @@ def unsub(update: Update, context: CallbackContext):
|
|||||||
username = update.message.from_user.username
|
username = update.message.from_user.username
|
||||||
|
|
||||||
# Длинная операция, сообщим о запущенном процессе.
|
# Длинная операция, сообщим о запущенном процессе.
|
||||||
update.message.reply_text(text=MESSAGES.Unsub.START)
|
msg = update.message.reply_text(text=MESSAGES.Unsub.START)
|
||||||
|
|
||||||
LOG.info(f"Unsubcripting {user_id}")
|
# LOG.info(f"Unsubcripting {user_id}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = requests.get(
|
req = requests.get(
|
||||||
@ -303,7 +307,54 @@ def unsub(update: Update, context: CallbackContext):
|
|||||||
update.message.reply_sticker(MESSAGES.Stickers.bad())
|
update.message.reply_sticker(MESSAGES.Stickers.bad())
|
||||||
return
|
return
|
||||||
|
|
||||||
update.message.reply_text(text=MESSAGES.Unsub.UNSUBBED)
|
msg.edit_text(text=MESSAGES.Unsub.UNSUBBED)
|
||||||
|
|
||||||
|
|
||||||
|
# ======================================================================= SCHED
|
||||||
|
|
||||||
|
|
||||||
|
def grades_checker(context: CallbackContext):
|
||||||
|
"""Задача по проверке оценок.
|
||||||
|
|
||||||
|
Пробегает по списку подписавшихся и для каждого студента запрашивает
|
||||||
|
`/grades/fingerprint`. Отпечаток (fingerprint) - текстовая строка
|
||||||
|
захешированных оценок. При изменении оценок, меняется и их хеш. Бот с каждым
|
||||||
|
вызовом задачи запоминает эти хеши всех студентов и сверяет их. Если вдруг
|
||||||
|
несовпадение, то значит, что данные об оценках были изменены.
|
||||||
|
|
||||||
|
Это простой и универсальный способ смотреть изменения, но он не дает инфы о
|
||||||
|
том, в каком задании появилась новая оценка.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
r = requests.get(f"{HOST}/students", params={"god_mode": True, "sub_only": True})
|
||||||
|
data = r.json()
|
||||||
|
context.bot_data["groups"] = data
|
||||||
|
|
||||||
|
# При инициализации бота словарь отсутствует
|
||||||
|
if "fingerprints" not in context.bot_data:
|
||||||
|
context.bot_data["fingerprints"] = defaultdict(str)
|
||||||
|
|
||||||
|
for group in data.values():
|
||||||
|
for student in group["students"]:
|
||||||
|
tg_id = student["tg_id"]
|
||||||
|
r = requests.get(f"{HOST}/grades/fingerprint", params={"tg_id": tg_id})
|
||||||
|
fingerprint = r.json().get("fingerprint", "")
|
||||||
|
old_fingerprint = context.bot_data["fingerprints"][tg_id]
|
||||||
|
# При инициализации у старого отпечатка ещё не установлено значение.
|
||||||
|
# Там находится пустая строка, поэтому нужна дополн. проверка.
|
||||||
|
if old_fingerprint != fingerprint and old_fingerprint != "":
|
||||||
|
context.bot.send_message(
|
||||||
|
chat_id=tg_id,
|
||||||
|
text=MESSAGES.Sub.GRADES_CHANGED,
|
||||||
|
)
|
||||||
|
LOG.info(
|
||||||
|
"Fingerprint = %s, old = %s",
|
||||||
|
fingerprint,
|
||||||
|
context.bot_data["fingerprints"][tg_id],
|
||||||
|
)
|
||||||
|
context.bot_data["fingerprints"][tg_id] = fingerprint
|
||||||
|
|
||||||
|
|
||||||
# ==================================================================== HANDLERS
|
# ==================================================================== HANDLERS
|
||||||
|
|
||||||
@ -311,6 +362,8 @@ def unsub(update: Update, context: CallbackContext):
|
|||||||
def main() -> None:
|
def main() -> None:
|
||||||
updater = Updater(token=TOKEN, use_context=True)
|
updater = Updater(token=TOKEN, use_context=True)
|
||||||
dispatcher = updater.dispatcher
|
dispatcher = updater.dispatcher
|
||||||
|
jobber = updater.job_queue
|
||||||
|
jobber.run_repeating(grades_checker, SUB_TIME_DELTA)
|
||||||
LOG.bot = updater.bot
|
LOG.bot = updater.bot
|
||||||
|
|
||||||
start_handler = CommandHandler("start", start)
|
start_handler = CommandHandler("start", start)
|
||||||
|
|||||||
@ -22,7 +22,7 @@ class Messages:
|
|||||||
return cases.get(err, "😟 Возникли неполадки")
|
return cases.get(err, "😟 Возникли неполадки")
|
||||||
|
|
||||||
class Assignments:
|
class Assignments:
|
||||||
START = "👀 Посмотрим (~3с) ..."
|
START = "👀 Посмотрим (~5с) ..."
|
||||||
SELECT_COURSE = "👇 Выбери дисциплину"
|
SELECT_COURSE = "👇 Выбери дисциплину"
|
||||||
SELECT_ASSNT = "👇 Выбери работу"
|
SELECT_ASSNT = "👇 Выбери работу"
|
||||||
END = "😉 Всего наилучшего"
|
END = "😉 Всего наилучшего"
|
||||||
@ -58,12 +58,15 @@ class Messages:
|
|||||||
class Sub:
|
class Sub:
|
||||||
START = "📜✍ Оформляю подписку на уведомления..."
|
START = "📜✍ Оформляю подписку на уведомления..."
|
||||||
SUBBED = "👌 Подписка на уведомления оформлена"
|
SUBBED = "👌 Подписка на уведомления оформлена"
|
||||||
|
GRADES_CHANGED = (
|
||||||
|
"💌 Твои оценки изменились! Вызови команду /grades, чтобы узнать!"
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def failure(err: str):
|
def failure(err: str):
|
||||||
cases = {
|
cases = {
|
||||||
"StudentNotFound": "⛔ Для подписки нужно зарегистрироваться."
|
"StudentNotFound": "⛔ Для подписки нужно зарегистрироваться."
|
||||||
+ "Команда /start поможет с регистрацией",
|
+ "Команда /start поможет с регистрацией",
|
||||||
"StudentAlreadySubbed": "💁♀️ Подписка уже оформлена ранее",
|
"StudentAlreadySubbed": "💁♀️ Подписка уже оформлена ранее",
|
||||||
}
|
}
|
||||||
return cases.get(err, "😟 Возникли неполадки")
|
return cases.get(err, "😟 Возникли неполадки")
|
||||||
@ -75,8 +78,8 @@ class Messages:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def failure(err: str):
|
def failure(err: str):
|
||||||
cases = {
|
cases = {
|
||||||
"StudentNotFound": "⛔ Для отпидски от уведомлений нужно зарегистрироваться."
|
"StudentNotFound": "⛔ Для отпидски от уведомлений нужно зарегистрироваться."
|
||||||
+ "Команда /start поможет с регистрацией",
|
+ "Команда /start поможет с регистрацией",
|
||||||
"StudentAlreadyUnsubbed": "💁♀️ Подписка на уведомления не была оформлена или уже отменена",
|
"StudentAlreadyUnsubbed": "💁♀️ Подписка на уведомления не была оформлена или уже отменена",
|
||||||
}
|
}
|
||||||
return cases.get(err, "😟 Возникли неполадки")
|
return cases.get(err, "😟 Возникли неполадки")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user