diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e6bb31f..c1096ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,6 +46,7 @@ deploy-prod: - prod only: - master + - hseapp when: manual script: - docker stack deploy --with-registry-auth -c ./.deploy/deploy-prod.yaml ruz-bot diff --git a/daemons/api.py b/daemons/api.py index 3018591..50e5c33 100644 --- a/daemons/api.py +++ b/daemons/api.py @@ -13,7 +13,7 @@ def api(): all_users = mongo.users_collection.count_documents({}) teachers = mongo.users_collection.count_documents({"is_teacher": True}) text = f"Всего пользователей: {all_users}
" \ - f"Пользователей прошедших регистрацию: {mongo.users_collection.count_documents({'hse_id': {'$ne': None}})}
" \ + f"Пользователей прошедших регистрацию: {mongo.users_collection.count_documents({'email': {'$ne': None}})}
" \ f"Студентов: {all_users - teachers}
" \ f"Преподавателей: {teachers}
" \ f"Отписались от уведомлений: {mongo.users_collection.count_documents({'notify_minutes': None})}
" \ @@ -22,13 +22,13 @@ def api(): f"
" \ f"
" \ f"Пользователей из Москвы: {mongo.users_collection.count_documents({'campus': 'Москва'}) + mongo.users_collection.count_documents({'campus': {'$exists': False}})}
" \ - f"Пользователей из Москвы (регистрация): {mongo.users_collection.count_documents({'campus': 'Москва', 'hse_id': {'$ne': None}}) + mongo.users_collection.count_documents({'campus': {'$exists': False}, 'hse_id': {'$ne': None}})}
" \ + f"Пользователей из Москвы (регистрация): {mongo.users_collection.count_documents({'campus': 'Москва', 'email': {'$ne': None}}) + mongo.users_collection.count_documents({'campus': {'$exists': False}, 'email': {'$ne': None}})}
" \ f"Пользователей из Перми: {mongo.users_collection.count_documents({'campus': 'Пермь'})}
" \ - f"Пользователей из Перми (регистрация): {mongo.users_collection.count_documents({'campus': 'Пермь', 'hse_id': {'$ne': None}})}
" \ + f"Пользователей из Перми (регистрация): {mongo.users_collection.count_documents({'campus': 'Пермь', 'email': {'$ne': None}})}
" \ f"Пользователей из Нижнего Новгорода: {mongo.users_collection.count_documents({'campus': 'Нижний Новгород'})}
" \ - f"Пользователей из Нижнего Новгорода (регистрация): {mongo.users_collection.count_documents({'campus': 'Нижний Новгород', 'hse_id': {'$ne': None}})}
" \ + f"Пользователей из Нижнего Новгорода (регистрация): {mongo.users_collection.count_documents({'campus': 'Нижний Новгород', 'email': {'$ne': None}})}
" \ f"Пользователей из Санкт-Петербурга: {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург'})}
" \ - f"Пользователей из Санкт-Петербурга (регистрация): {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург', 'hse_id': {'$ne': None}})}
" + f"Пользователей из Санкт-Петербурга (регистрация): {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург', 'email': {'$ne': None}})}
" return text @app.route('/alice', methods=['POST']) diff --git a/daemons/bot.py b/daemons/bot.py index 61bbbc4..973d718 100644 --- a/daemons/bot.py +++ b/daemons/bot.py @@ -16,5 +16,5 @@ def on_start(message: Message): @bot.message_handler() def do_action(message: Message): - from helpers.answer import answer - answer.process(message) + from helpers.answer import Answer + Answer(message).process() diff --git a/daemons/fetch.py b/daemons/fetch.py index 7491f39..17c13ec 100644 --- a/daemons/fetch.py +++ b/daemons/fetch.py @@ -2,16 +2,14 @@ import datetime import logging from time import sleep -from helpers import now, User -from helpers.models import UserSchema +from helpers import now, campus_timdelta from helpers.mongo import mongo from helpers.ruz import ruz -def fetch_schedule_for_user(user: User): +def fetch_schedule_for_user(user: dict): today = now(user) - next_day = today + datetime.timedelta(days=7) - schedule = ruz.get_schedule(user, today, next_day) + schedule = ruz.get_schedule(user, today) if schedule is None: return False saved_ids = [] @@ -23,35 +21,35 @@ def fetch_schedule_for_user(user: User): "discipline": element['discipline'], "auditorium": element['auditorium'], "link": element['url1'], - "hse_user_id": user.hse_id, + "user_email": user['email'], "begin": datetime.datetime( year=int(year), month=int(month), day=int(day), hour=int(begin_hour), minute=int(begin_minute), - ) + ) + datetime.timedelta(hours=campus_timdelta[user.get('campus', 'Москва')]) }) if lesson is None: result = mongo.lessons_collection.insert_one({ "discipline": element['discipline'], "auditorium": element['auditorium'], "link": element['url1'], - "hse_user_id": user.hse_id, + "user_email": user['email'], "begin": datetime.datetime( year=int(year), month=int(month), day=int(day), hour=int(begin_hour), minute=int(begin_minute), - ), + ) + datetime.timedelta(hours=campus_timdelta[user.get('campus', 'Москва')]), "end": datetime.datetime( year=int(year), month=int(month), day=int(day), hour=int(end_hour), minute=int(end_minute), - ), + ) + datetime.timedelta(hours=campus_timdelta[user.get('campus', 'Москва')]), "building": element['building'], "lecturer": element['lecturer'], "notified": False, @@ -60,13 +58,17 @@ def fetch_schedule_for_user(user: User): saved_ids.append(result.inserted_id) else: saved_ids.append(lesson['_id']) - mongo.lessons_collection.delete_many({"hse_user_id": user.hse_id, "_id": {"$nin": saved_ids}}) + mongo.lessons_collection.delete_many({"user_email": user['email'], "_id": {"$nin": saved_ids}}) + mongo.users_collection.update_one({"_id": user['_id']}, {"$set": {'last_schedule_fetch': datetime.datetime.now()}}) return True def process(): - for user in mongo.users_collection.find({"hse_id": {"$ne": None}}): - fetch_schedule_for_user(UserSchema().load(user)) + for user in mongo.users_collection.find({"email": {"$exists": True, "$ne": None}}).sort([ + ("last_schedule_fetch", 1) + ]): + fetch_schedule_for_user(user) + sleep(5) def delete_old(): @@ -77,9 +79,12 @@ def fetch(): while True: logging.info("fetch start") begin = datetime.datetime.now() + if begin.hour > 22 or begin.hour < 7: + logging.info("Too late, sleeping") + sleep(30 * 60) + continue process() end = datetime.datetime.now() logging.info('fetch finished') logging.info("time elapsed %s", (end - begin).total_seconds()) delete_old() - sleep(60 * 5) diff --git a/daemons/notify.py b/daemons/notify.py index f89c234..68228da 100644 --- a/daemons/notify.py +++ b/daemons/notify.py @@ -6,16 +6,15 @@ from telebot.apihelper import ApiTelegramException from daemons.bot import bot from helpers import now -from helpers.models import UserSchema from helpers.mongo import mongo from helpers.ruz import ruz def process(): - for user in mongo.users_collection.find({"notify_minutes": {"$ne": None}, "hse_id": {"$ne": None}}): - time_now = now(UserSchema().load(user)) + for user in mongo.users_collection.find({"notify_minutes": {"$ne": None}, "email": {"$ne": None}}): + time_now = now(user) for lesson in mongo.lessons_collection.find({ - "hse_user_id": user["hse_id"], + "user_email": user["email"], "begin": {"$lte": time_now + datetime.timedelta(minutes=user["notify_minutes"])}, "notified": False }): @@ -36,34 +35,33 @@ def process(): mongo.lessons_collection.update_one({"_id": lesson['_id']}, {"$set": {"notified": True}}) time_now = datetime.datetime.now() for user in mongo.users_collection.find({"next_daily_notify_time": {"$lte": time_now}}): - user_model = UserSchema().load(user) - deny_weekday = 6 if user_model.daily_notify_today else 5 + deny_weekday = 6 if user.get('daily_notify_today', True) else 5 if time_now.weekday() != deny_weekday: - if user_model.daily_notify_today: - lessons = mongo.get_today_lessons(user_model) + if user.get('daily_notify_today', True): + lessons = mongo.get_today_lessons(user) else: - lessons = mongo.get_tomorrow_lessons(user_model) + lessons = mongo.get_tomorrow_lessons(user) if len(lessons) == 0: - text = f"{'Сегодня' if user_model.daily_notify_today else 'Завтра'} у тебя нет пар, отдыхай." + text = f"{'Сегодня' if user.get('daily_notify_today', True) else 'Завтра'} у тебя нет пар, отдыхай." else: text = ruz.schedule_builder(lessons) try: bot.send_message( user["chat_id"], - f"Уведомляю о занятиях! Твое расписание на {'сегодня' if user_model.daily_notify_today else 'завтра'}:\n" + text, + f"Уведомляю о занятиях! Твое расписание на {'сегодня' if user.get('daily_notify_today', True) else 'завтра'}:\n" + text, parse_mode='Markdown' ) except: pass mongo.users_collection.update_one( {"chat_id": user["chat_id"]}, - {"$set": {"next_daily_notify_time": user_model.next_daily_notify_time + datetime.timedelta(days=1)}} + {"$set": {"next_daily_notify_time": user['next_daily_notify_time'] + datetime.timedelta(days=1)}} ) - for user in mongo.users_collection.find({"first_lesson_notify": {"$exists": True}, "first_lesson_notify": {"$ne": None}, "hse_id": {"$ne": None}}): - time_now = now(UserSchema().load(user)) + for user in mongo.users_collection.find({"first_lesson_notify": {"$exists": True, "$ne": None}, "email": {"$ne": None}}): + time_now = now(user) for lesson in mongo.lessons_collection.find({ - "hse_user_id": user["hse_id"], + "user_email": user["email"], "begin": {"$lte": time_now + datetime.timedelta(minutes=user["first_lesson_notify"])}, "notified_today": {"$ne": True} }).sort("begin"): @@ -92,7 +90,7 @@ def process(): except ApiTelegramException: pass start_of_day = datetime.datetime(year=time_now.year, month=time_now.month, day=time_now.day) - mongo.lessons_collection.update_many({"begin": {"$gte": start_of_day, "$lt": (start_of_day + datetime.timedelta(days=1))}, "hse_user_id": user["hse_id"]}, {"$set": {"notified_today": True}}) + mongo.lessons_collection.update_many({"begin": {"$gte": start_of_day, "$lt": (start_of_day + datetime.timedelta(days=1))}, "user_email": user["email"]}, {"$set": {"notified_today": True}}) break diff --git a/helpers/__init__.py b/helpers/__init__.py index e638794..1e740d3 100644 --- a/helpers/__init__.py +++ b/helpers/__init__.py @@ -1,9 +1,5 @@ import datetime import logging -import zoneinfo - -from helpers.models import User - campus_timdelta = { "Москва": 3, @@ -12,14 +8,15 @@ campus_timdelta = { "Пермь": 5 } -def now(user: User): - today = datetime.datetime.now() + datetime.timedelta(hours=campus_timdelta[user.campus]) + +def now(user: dict): + today = datetime.datetime.now() + datetime.timedelta(hours=campus_timdelta[user.get('campus', 'Москва')]) return today -def get_next_daily_notify_time(user: User) -> datetime.datetime: +def get_next_daily_notify_time(user: dict) -> datetime.datetime: time_now = now(user) - hours, minutes = map(int, user.daily_notify_time.split(":")) + hours, minutes = map(int, user['daily_notify_time'].split(":")) next_time = datetime.datetime( year=time_now.year, month=time_now.month, @@ -32,4 +29,4 @@ def get_next_daily_notify_time(user: User) -> datetime.datetime: if time_now.hour * 60 + time_now.minute > hours * 60 + minutes: logging.info('go to next day') next_time = next_time + datetime.timedelta(days=1) - return next_time - datetime.timedelta(hours=campus_timdelta[user.campus]) + return next_time - datetime.timedelta(hours=campus_timdelta[user.get('campus', 'Москва')]) diff --git a/helpers/alice.py b/helpers/alice.py index 022b2ba..ce48632 100644 --- a/helpers/alice.py +++ b/helpers/alice.py @@ -3,7 +3,6 @@ from typing import Optional from daemons.bot import bot from helpers import now -from helpers.models import UserSchema from helpers.mongo import mongo @@ -40,7 +39,6 @@ class Processor: def get_lesson_for_user(self, hse_id: int): user = mongo.users_collection.find_one({"hse_id": hse_id}) - user = UserSchema().load(user) t = now(user) for lesson in mongo.lessons_collection.find({"hse_user_id": hse_id, "begin": {"$gte": t}}).sort([("begin", 1)]): return lesson diff --git a/helpers/answer.py b/helpers/answer.py index bd5cce5..47ab1db 100644 --- a/helpers/answer.py +++ b/helpers/answer.py @@ -1,351 +1,266 @@ -from telebot.types import Message +import datetime + +from telebot.types import Message, ReplyKeyboardRemove from daemons.bot import bot from daemons.fetch import fetch_schedule_for_user from helpers import get_next_daily_notify_time -from helpers.keyboards import main_keyboard, notify_keyboard, yes_no_keyboard, again_keyboard, groups_keyboard, \ - no_daily_notify, student_or_teacher_keyboard, campus_keyboard, daily_notify_type, notify_type, first_lesson_notify -from helpers.models import UserSchema, User +from helpers.keyboards import main_keyboard, notify_keyboard, yes_no_keyboard, again_keyboard, no_daily_notify, \ + campus_keyboard, daily_notify_type, notify_type, first_lesson_notify from helpers.mongo import mongo from helpers.ruz import ruz -class BaseAnswer: - def process(self, message: Message): +class Answer: + + user: dict + message: Message + message_text: str + + def __init__(self, message: Message): + self.message = message + self.message = message + self.message_text = message.text or message.caption or "" user = mongo.users_collection.find_one({"chat_id": message.chat.id}) if user is None: - user = User(chat_id=message.chat.id) - mongo.users_collection.insert_one(UserSchema().dump(user)) - else: - user = UserSchema().load(user) - attr = getattr(self, "handle_state_" + user.state, None) - if attr is None: - raise NotImplementedError(f"handled state {user.state} but not implemented!") - attr(message, user) + user = { + "chat_id": message.chat.id, + 'email': None, + 'state': 'new', + 'notify_minutes': 10, + 'daily_notify_time': None, + 'next_daily_notify_time': None, + 'campus': "Москва", + 'daily_notify_today': True, + 'first_lesson_notify': None, + 'last_schedule_fetch': datetime.datetime.now(), + 'yandex_id': None + } + mongo.users_collection.insert_one(user) + self.user = user - def set_state(self, user: User, state: str): - user.state = state - mongo.users_collection.update_one({"chat_id": user.chat_id}, {"$set": {"state": state}}) + def process(self): + getattr( + self, + "handle_state_" + self.user['state'], + self.handle_state_default + )() + def handle_state_default(self): + raise NotImplementedError(f"handled state {self.user['state']} but not implemented!") -class Answer(BaseAnswer): + def send_message(self, text, reply_markup=None, remove_keyboard=True, **kwargs): + if reply_markup is None and remove_keyboard: + reply_markup = ReplyKeyboardRemove() + bot.send_message(self.user['chat_id'], text, reply_markup=reply_markup, **kwargs) - def handle_state_new(self, message: Message, user: User): - bot.send_message( - message.chat.id, + def set_state(self, state: str): + self.user['state'] = state + mongo.users_collection.update_one({"chat_id": self.user['chat_id']}, {"$set": {"state": state}}) + + def handle_state_new(self): + self.send_message( "Привет! Я буду помогать тебе выживать в вышке!\nИз какого ты кампуса?", reply_markup=campus_keyboard() ) - self.set_state(user, "wait_for_campus") + self.set_state("wait_for_campus") - def handle_state_wait_for_campus(self, message: Message, user: User): - if message.text not in ["Москва", "Санкт-Петербург", "Нижний Новгород", "Пермь"]: - bot.send_message( - user.chat_id, + def handle_state_wait_for_campus(self): + if self.message_text not in ["Москва", "Санкт-Петербург", "Нижний Новгород", "Пермь"]: + self.send_message( "Ты прислал мне что-то непонятное, используй кнопки. Из какого ты кампуса?", reply_markup=campus_keyboard() ) return - bot.send_message( - user.chat_id, - "Принято! Ты преподаватель или студент?", - reply_markup=student_or_teacher_keyboard() - ) + self.send_message("Принято! А теперь отправь мне свою корпоративную почту.") mongo.users_collection.update_one( - {"chat_id": user.chat_id}, - {"$set": {"campus": message.text, "state": "wait_for_student_or_teacher"}} + {"chat_id": self.user['chat_id']}, + {"$set": {"campus": self.message_text, "state": "wait_for_email"}} ) - def handle_state_wait_for_student_or_teacher(self, message: Message, user: User): - if message.text == "Студент 👨‍🎓": - bot.send_message(user.chat_id, "Принято! Теперь отправь мне свое ФИО.", reply_markup=again_keyboard()) - mongo.users_collection.update_one( - {"chat_id": user.chat_id}, - {"$set": {"is_teacher": False, "state": "wait_for_name"}} - ) - self.set_state(user, "wait_for_name") - elif message.text == "Преподаватель 👨‍🏫": - bot.send_message(user.chat_id, "Принято! Теперь отправь мне свое ФИО.", reply_markup=again_keyboard()) - mongo.users_collection.update_one( - {"chat_id": user.chat_id}, - {"$set": {"is_teacher": True, "state": "wait_for_name"}} - ) - self.set_state(user, "wait_for_name") - elif message.text == "Начать заново 🔄": - bot.send_message( - message.chat.id, - "Привет! Я буду помогать тебе выживать в вышке!\nИз какого ты кампуса?", - reply_markup=campus_keyboard() - ) - self.set_state(user, "wait_for_campus") + def handle_state_wait_for_email(self): + self.user['email'] = self.message_text + schedule = fetch_schedule_for_user(self.user) + if schedule is None: + self.send_message("Возможно, в РУЗе какие-то сбои, либо твой email неправильный. Попробуй ввести email еще раз.") return - else: - bot.send_message(user.chat_id, "Ты отправил мне что-то неправильное, используй кнопки. Ты преподаватель или студент?", reply_markup=student_or_teacher_keyboard()) - - def handle_state_wait_for_name(self, message: Message, user: User): - if message.text == "Начать заново 🔄": - bot.send_message( - message.chat.id, - "Привет! Я буду помогать тебе выживать в вышке!\nИз какого ты кампуса?", - reply_markup=campus_keyboard() - ) - self.set_state(user, "wait_for_campus") - return - user.name = message.text - data = ruz.find_person(user) - if data is None: - bot.send_message( - user.chat_id, - "В РУЗе какая-то поломка, попробуй еще раз позже." - ) - return - if len(data) == 0: - bot.send_message(user.chat_id, "К сожалению, в РУЗе не нашлось такого человека, попробуй еще раз.") - return - mongo.users_collection.update_one( - {"chat_id": user.chat_id}, - {"$set": {"name": user.name}}) - if user.is_teacher: - bot.send_message( - user.chat_id, - "Отлично! Теперь выбери из списка свой департамент.", - reply_markup=groups_keyboard(data) - ) - else: - bot.send_message( - user.chat_id, - "Отлично! Теперь выбери из списка свою группу.", - reply_markup=groups_keyboard(data) - ) - self.set_state(user, "wait_for_group") - - def handle_state_wait_for_group(self, message: Message, user: User): - if message.text == "Начать заново 🔄": - bot.send_message( - message.chat.id, - "Привет! Я буду помогать тебе выживать в вышке!\nИз какого ты кампуса?", - reply_markup=campus_keyboard() - ) - self.set_state(user, "wait_for_campus") - return - group = message.text - data = ruz.find_person(user) - if data is None: - bot.send_message( - user.chat_id, - "В РУЗе какая-то поломка, попробуй еще раз позже." - ) - return - for element in data: - if element['description'] == group: - user.hse_id = int(element['id']) - user.group = group - user.name = element['label'] - break - if user.group is None: - bot.send_message(user.chat_id, "Ты ввел что-то неправильно, попробуй еще раз сначала. Из какого ты кампуса?", reply_markup=campus_keyboard()) - self.set_state(user, "wait_for_campus") - return - mongo.users_collection.update_one({"chat_id": user.chat_id}, {"$set": { - "hse_id": user.hse_id, - "group": group, - "name": user.name + mongo.users_collection.update_one({"chat_id": self.user['chat_id']}, {"$set": { + "email": self.user['email'], }}) - bot.send_message( - user.chat_id, + self.send_message( "Я нашел тебя в базе РУЗ. Я буду подсказывать тебе расписание, а также уведомлять о предстоящих парах.", ) - success = fetch_schedule_for_user(user) - if success: - lessons = mongo.get_today_lessons(user) - if len(lessons) == 0: - bot.send_message( - user.chat_id, - "Сегодня у тебя нет пар, отдыхай", - reply_markup=main_keyboard() - ) - else: - bot.send_message( - user.chat_id, - "Твои пары сегодня:\n" + ruz.schedule_builder(lessons), - reply_markup=main_keyboard(), - parse_mode='Markdown', - ) - self.set_state(user, "ready") + lessons = mongo.get_today_lessons(self.user) + if len(lessons) == 0: + self.send_message( + "Сегодня у тебя нет пар, отдыхай", + reply_markup=main_keyboard() + ) + else: + self.send_message( + "Твои пары сегодня:\n" + ruz.schedule_builder(lessons), + reply_markup=main_keyboard(), + parse_mode='Markdown', + ) + self.set_state("ready") - def handle_state_ready(self, message: Message, user: User): - if message.text == "Пары сегодня": - lessons = mongo.get_today_lessons(user) + def handle_state_ready(self): + if self.message_text == "Пары сегодня": + lessons = mongo.get_today_lessons(self.user) if len(lessons) == 0: text = "Сегодня у тебя нет пар, отдыхай." else: text = ruz.schedule_builder(lessons) - elif message.text == "Пары завтра": - lessons = mongo.get_tomorrow_lessons(user) + elif self.message_text == "Пары завтра": + lessons = mongo.get_tomorrow_lessons(self.user) if len(lessons) == 0: text = "Завтра у тебя нет пар, отдыхай." else: text = ruz.schedule_builder(lessons) - elif message.text == "Расписание на неделю": - lessons = mongo.get_week_lessons(user) + elif self.message_text == "Расписание на неделю": + lessons = mongo.get_week_lessons(self.user) if len(lessons) == 0: text = "На этой неделе у тебя нет пар, отдыхай." else: text = ruz.schedule_builder(lessons) - elif message.text == "Напоминания о парах": - bot.send_message( - user.chat_id, + elif self.message_text == "Напоминания о парах": + self.send_message( "Я умею напоминать о каждой паре и о первой паре. Что хочешь настроить?", reply_markup=notify_type() ) - self.set_state(user, "notify_type") + self.set_state("notify_type") return - elif message.text == "Ежедневные уведомления": - bot.send_message( - user.chat_id, + elif self.message_text == "Ежедневные уведомления": + self.send_message( "Я могу присылать тебе расписание на текущий день или на следующий. Как ты хочешь чтобы я тебя уведомлял?", reply_markup=daily_notify_type() ) - self.set_state(user, "wait_for_daily_notify_type") + self.set_state("wait_for_daily_notify_type") return - elif message.text == "Сброс настроек": - bot.send_message(user.chat_id, "Ты уверен что хочешь сбросить все настройки и больше не получать уведомления?", reply_markup=yes_no_keyboard()) - self.set_state(user, "reset") + elif self.message_text == "Сброс настроек": + self.send_message("Ты уверен что хочешь сбросить все настройки и больше не получать уведомления?", reply_markup=yes_no_keyboard()) + self.set_state("reset") return - elif message.text == "Подключение Алисы": - if user.yandex_id is None: - text = "Для того, чтобы подключить Яндекс.Алису, вызови навык \"Расписание вышки\" и назови этот код: " + str(user.hse_id) + elif self.message_text == "Подключение Алисы": + if self.user['yandex_id'] is None: + text = "Для того, чтобы подключить Яндекс.Алису, вызови навык \"Расписание вышки\" и назови этот код: " + str(self.user['hse_id']) else: text = "Янедкс.Алиса уже подключена. Чтобы узнать ближайшую пару, вызови навык \"Расписание вышки\"" else: text = "Я не понимаю такой команды, используй кнопки." - bot.send_message( - user.chat_id, + self.send_message( text, reply_markup=main_keyboard(), parse_mode='Markdown' ) - def handle_state_notify_type(self, message: Message, user: User): - text = message.text - if text == "О каждой паре": - bot.send_message( - user.chat_id, + def handle_state_notify_type(self): + if self.message_text == "О каждой паре": + self.send_message( "Выбери за сколько минут мне нужно напомнить тебе о предстоящей паре", reply_markup=notify_keyboard() ) - self.set_state(user, "wait_for_notify") - elif text == "О первой паре": - bot.send_message( - user.chat_id, + self.set_state("wait_for_notify") + elif self.message_text == "О первой паре": + self.send_message( "Выбери за сколько минут мне нужно напоминать тебе о первой паре", reply_markup=first_lesson_notify() ) - self.set_state(user, "wait_for_first_notify") - elif text == "Назад": - self.set_state(user, "ready") - bot.send_message( - user.chat_id, - text, + self.set_state("wait_for_first_notify") + elif self.message_text == "Назад": + self.set_state("ready") + self.send_message( + self.message_text, reply_markup=main_keyboard() ) else: - bot.send_message(user.chat_id, "Используй кнопки!", reply_markup=notify_type()) + self.send_message("Используй кнопки!", reply_markup=notify_type()) - def handle_state_wait_for_first_notify(self, message: Message, user: User): - text = message.text - if text == "30 минут": + def handle_state_wait_for_first_notify(self): + if self.message_text == "30 минут": time_notify = 30 - elif text == "1 час": + elif self.message_text == "1 час": time_notify = 60 - elif text == "4 часа": + elif self.message_text == "4 часа": time_notify = 4 * 60 - elif text == "12 часов": + elif self.message_text == "12 часов": time_notify = 12 * 60 - elif text == "Не уведомлять": + elif self.message_text == "Не уведомлять": time_notify = None else: - bot.send_message(user.chat_id, "Используй кнопки!", reply_markup=first_lesson_notify()) + self.send_message("Используй кнопки!", reply_markup=first_lesson_notify()) return - mongo.users_collection.update_one({"chat_id": user.chat_id}, {"$set": {"first_lesson_notify": time_notify}}) - self.set_state(user, "ready") - bot.send_message(user.chat_id, "Запомнил!", reply_markup=main_keyboard()) + mongo.users_collection.update_one({"chat_id": self.user['chat_id']}, {"$set": {"first_lesson_notify": time_notify}}) + self.set_state("ready") + self.send_message("Запомнил!", reply_markup=main_keyboard()) - def handle_state_wait_for_daily_notify_type(self, message: Message, user: User): - text = message.text - if text == "Текущий день": - bot.send_message( - user.chat_id, + def handle_state_wait_for_daily_notify_type(self): + if self.message_text == "Текущий день": + self.send_message( "Каждый день (кроме воскресенья) я буду присылать тебе расписание на текущий день. Пришли мне в формате чч:мм время, в которое ты хочешь получать расписание. Например, 09:30", reply_markup=no_daily_notify() ) mongo.users_collection.update_one( - {"chat_id": user.chat_id}, + {"chat_id": self.user['chat_id']}, {"$set": {"daily_notify_today": True, "state": "wait_for_daily_notify"}} ) - elif text == "Следующий день": - bot.send_message( - user.chat_id, + elif self.message_text == "Следующий день": + self.send_message( "Каждый день (кроме субботы) я буду присылать тебе расписание на следующий день. Пришли мне в формате чч:мм время, в которое ты хочешь получать расписание. Например, 20:30", reply_markup=no_daily_notify() ) mongo.users_collection.update_one( - {"chat_id": user.chat_id}, + {"chat_id": self.user['chat_id']}, {"$set": {"daily_notify_today": False, "state": "wait_for_daily_notify"}} ) - elif text == "Не уведомлять": - bot.send_message( - user.chat_id, + elif self.message_text == "Не уведомлять": + self.send_message( "Принято! Я не буду уведомлять тебя.", reply_markup=main_keyboard() ) mongo.users_collection.update_one( - {"chat_id": user.chat_id}, + {"chat_id": self.user['chat_id']}, {"$set": {"next_daily_notify_time": None, "daily_notify_time": None, "state": "ready"}} ) - elif text == "Назад": - bot.send_message( - user.chat_id, + elif self.message_text == "Назад": + self.send_message( "Возвращаюсь!", reply_markup=main_keyboard() ) mongo.users_collection.update_one( - {"chat_id": user.chat_id}, + {"chat_id": self.user['chat_id']}, {"$set": {"next_daily_notify_time": None, "daily_notify_time": None, "state": "ready"}} ) else: - bot.send_message( - user.chat_id, + self.send_message( "Ты прислал мне что-то неправильное, используй кнопки. Как ты хочешь чтобы я тебя уведомлял?", reply_markup=daily_notify_type() ) - def handle_state_wait_for_notify(self, message: Message, user: User): - text = message.text - if text == "Не уведомлять": - user.notify_minutes = None - elif text == "5 минут": - user.notify_minutes = 5 - elif text == "10 минут": - user.notify_minutes = 10 - elif text == "15 минут": - user.notify_minutes = 15 - elif text == "20 минут": - user.notify_minutes = 20 + def handle_state_wait_for_notify(self): + if self.message_text == "Не уведомлять": + self.user['notify_minutes'] = None + elif self.message_text == "5 минут": + self.user['notify_minutes'] = 5 + elif self.message_text == "10 минут": + self.user['notify_minutes'] = 10 + elif self.message_text == "15 минут": + self.user['notify_minutes'] = 15 + elif self.message_text == "20 минут": + self.user['notify_minutes'] = 20 else: - bot.send_message( - user.chat_id, + self.send_message( "Я не понимаю такой команды, используй кнопки.", reply_markup=notify_keyboard() ) return - mongo.users_collection.update_one({"chat_id": user.chat_id}, {"$set": {"notify_minutes": user.notify_minutes}}) - if user.notify_minutes is not None: - text = f"Принято! Буду уведомлять тебя за {text}." + mongo.users_collection.update_one({"chat_id": self.user['chat_id']}, {"$set": {"notify_minutes": self.user['notify_minutes']}}) + if self.user['notify_minutes'] is not None: + text = f"Принято! Буду уведомлять тебя за {self.message_text}." else: text = f"Принято! Я не буду уведомлять тебя." - bot.send_message(user.chat_id, text, reply_markup=main_keyboard()) - self.set_state(user, "ready") + self.send_message(text, reply_markup=main_keyboard()) + self.set_state("ready") def _validate_time(self, line: str) -> bool: if len(line) != 5: @@ -362,53 +277,44 @@ class Answer(BaseAnswer): return False return True - def handle_state_wait_for_daily_notify(self, message: Message, user: User): - text = message.text - if text == "Не уведомлять": - bot.send_message( - user.chat_id, + def handle_state_wait_for_daily_notify(self): + if self.message_text == "Не уведомлять": + self.send_message( "Принято! Я не буду присылать тебе ежедневные уведомления.", reply_markup=main_keyboard() ) mongo.users_collection.update_one( - {"chat_id": user.chat_id}, + {"chat_id": self.user['chat_id']}, {"$set": {"daily_notify_time": None, "next_daily_notify_time": None, "state": "ready"}} ) return - if not self._validate_time(text): - bot.send_message( - user.chat_id, + if not self._validate_time(self.message_text): + self.send_message( "Ты прислал мне что-то неправильное. Пришли мне в формате чч:мм время, в которое ты хочешь получать расписание. Например, 09:30", reply_markup=no_daily_notify() ) return - user.daily_notify_time = text - next_time = get_next_daily_notify_time(user) - bot.send_message( - user.chat_id, - f"Принято! Буду уведомлять тебя каждый день в {text}.", + self.user['daily_notify_time'] = self.message_text + next_time = get_next_daily_notify_time(self.user) + self.send_message( + f"Принято! Буду уведомлять тебя каждый день в {self.message_text}.", reply_markup=main_keyboard() ) mongo.users_collection.update_one( - {"chat_id": user.chat_id}, + {"chat_id": self.user['chat_id']}, {"$set": { - "daily_notify_time": text, + "daily_notify_time": self.message_text, "next_daily_notify_time": next_time, "state": "ready" }} ) - def handle_state_reset(self, message: Message, user: User): - if message.text == "Да": - mongo.users_collection.delete_one({"chat_id": user.chat_id}) - bot.send_message(user.chat_id, "Настройки сброшены, ждем твоего возвращения", reply_markup=again_keyboard()) - elif message.text == "Нет": - bot.send_message(user.chat_id, "Возращаюсь к прежнему режиму", reply_markup=main_keyboard()) - self.set_state(user, "ready") + def handle_state_reset(self): + if self.message_text == "Да": + mongo.users_collection.delete_one({"email": self.user['email']}) + self.send_message("Настройки сброшены, ждем твоего возвращения", reply_markup=again_keyboard()) + elif self.message_text == "Нет": + self.send_message("Возращаюсь к прежнему режиму", reply_markup=main_keyboard()) + self.set_state("ready") else: - bot.send_message(user.chat_id, - "Я не понимаю, используй кнопки", - reply_markup=yes_no_keyboard()) - - -answer = Answer() + self.send_message("Я не понимаю, используй кнопки", reply_markup=yes_no_keyboard()) diff --git a/helpers/keyboards.py b/helpers/keyboards.py index 07ce6a4..b748157 100644 --- a/helpers/keyboards.py +++ b/helpers/keyboards.py @@ -24,14 +24,6 @@ def campus_keyboard(): return kb -def student_or_teacher_keyboard(): - kb = telebot.types.ReplyKeyboardMarkup(True, False) - kb.row("Преподаватель 👨‍🏫") - kb.row("Студент 👨‍🎓") - kb.row("Начать заново 🔄") - return kb - - def notify_keyboard(): kb = telebot.types.ReplyKeyboardMarkup(True, False) kb.row("Не уведомлять") @@ -55,14 +47,6 @@ def again_keyboard(): return kb -def groups_keyboard(data): - kb = telebot.types.ReplyKeyboardMarkup(True, False) - for entity in data: - kb.row(entity['description']) - kb.row("Начать заново 🔄") - return kb - - def no_daily_notify(): kb = telebot.types.ReplyKeyboardMarkup(True, False) kb.row("Не уведомлять") diff --git a/helpers/models.py b/helpers/models.py deleted file mode 100644 index 9ff82cd..0000000 --- a/helpers/models.py +++ /dev/null @@ -1,42 +0,0 @@ -import datetime -from dataclasses import dataclass -from typing import Optional - -import marshmallow_dataclass -from marshmallow import EXCLUDE - - -@dataclass -class User: - chat_id: int - name: Optional[str] = None - group: Optional[str] = None - hse_id: Optional[int] = None - state: str = "new" - notify_minutes: Optional[int] = 10 - daily_notify_time: Optional[str] = None - next_daily_notify_time: Optional[datetime.datetime] = None - is_teacher: bool = False - campus: str = "Москва" - daily_notify_today: bool = True - first_lesson_notify: Optional[float] = None - yandex_id: Optional[str] = None - - class Meta: - unknown = EXCLUDE - - -@dataclass -class Lesson: - hse_id: int - - -USchema = marshmallow_dataclass.class_schema(User) - - -class UserSchema(USchema): - - def load(self, entity, *args, **kwargs): - if entity.get('next_daily_notify_time', None) is not None: - entity['next_daily_notify_time'] = entity['next_daily_notify_time'].strftime("%Y-%m-%dT%H:%M:00.000+00:00") - return super().load(entity, *args, **kwargs) diff --git a/helpers/mongo.py b/helpers/mongo.py index d2dd169..8389afc 100644 --- a/helpers/mongo.py +++ b/helpers/mongo.py @@ -5,7 +5,6 @@ import pymongo import settings from helpers import now -from helpers.models import UserSchema, User class Mongo: @@ -26,21 +25,19 @@ class Mongo: ("discipline", 1), ("auditorium", 1), ("begin", 1), - ("hse_user_id", 1), + ("user_email", 1), ("link", 1) ]) self.lessons_collection.create_index([ - ("hse_user_id", 1), + ("user_email", 1), ("begin", 1) ]) self.lessons_collection.create_index([ - ("hse_user_id", 1), + ("user_email", 1), ("begin", 1), ("notified", 1) ]) - def get_user(self, user_id: int) -> User: - return UserSchema().loads(self.users_collection.find_one({"id": user_id})) def __getitem__(self, item): return self.database.get_collection(item) @@ -53,32 +50,32 @@ class Mongo: def lessons_collection(self): return self["lessons"] - def _get_lessons_on_date(self, user: User, date: datetime.datetime): + def _get_lessons_on_date(self, user: dict, date: datetime.datetime): date = datetime.datetime(year=date.year, month=date.month, day=date.day) next_date = date + datetime.timedelta(days=1) lessons = [] for lesson in self.lessons_collection.find({ - "hse_user_id": user.hse_id, + "user_email": user['email'], "begin": {"$gte": date, "$lte": next_date}} ): lessons.append(lesson) lessons.sort(key=lambda les: les["begin"]) return lessons - def get_today_lessons(self, user: User): + def get_today_lessons(self, user: dict): return self._get_lessons_on_date(user, now(user)) - def get_tomorrow_lessons(self, user: User): + def get_tomorrow_lessons(self, user: dict): return self._get_lessons_on_date(user, now(user) + datetime.timedelta(days=1)) - def get_week_lessons(self, user: User): + def get_week_lessons(self, user: dict): date = now(user) date = datetime.datetime(year=date.year, month=date.month, day=date.day) weekday = date.weekday() next_date = date + datetime.timedelta(days=(6 - weekday)) lessons = [] for lesson in self.lessons_collection.find({ - "hse_user_id": user.hse_id, + "user_email": user['email'], "begin": {"$gte": date, "$lte": next_date}} ): lessons.append(lesson) diff --git a/helpers/ruz.py b/helpers/ruz.py index 059fb1a..93d26b8 100644 --- a/helpers/ruz.py +++ b/helpers/ruz.py @@ -4,47 +4,43 @@ import logging from requests import get import settings -from helpers import User fields = [ 'discipline', 'building', 'auditorium', - 'date', - 'beginLesson', - 'endLesson', - 'lecturer', - 'url1' + 'date_start', + 'date_end', + 'lecturer_profiles', + 'stream_links' ] class RUZ: - def find_person(self, user: User) -> dict | None: - if user.is_teacher: - person_type = "person" - else: - person_type = "student" - search_str = settings.RUZ_API + f"search?term={user.name}&type={person_type}" - try: - data = get(search_str) - except: - return None - if data.status_code == 200: - data = data.json() - for index, value in enumerate(data): - data[index]['description'] = value['description'].capitalize() - return data - return None + def reformat_data(self, col): + for data in col: + date_start = datetime.datetime.strptime(data['date_start'], "%Y-%m-%dT%H:%M:%SZ") + date_end = datetime.datetime.strptime(data['date_end'], "%Y-%m-%dT%H:%M:%SZ") + data['date'] = date_start.strftime("%Y.%m.%d") + data['beginLesson'] = date_start.strftime("%H:%M") + data['endLesson'] = date_end.strftime("%H:%M") + try: + data['lecturer'] = data['lecturer_profiles'][0]["full_name"] + except: + data['lecturer'] = 'Неизвестный преподаватель' + try: + data['url1'] = data['stream_links'][0]['link'] + except: + data['url1'] = None + del data['date_start'] + del data['date_end'] + del data['lecturer_profiles'] + del data['stream_links'] - def get_schedule(self, user: User, begin_date: datetime.datetime, end_date: datetime.datetime): + def get_schedule(self, user: dict, begin_date: datetime.datetime): start_date_str = begin_date.strftime("%Y.%m.%d") - end_date_str = end_date.strftime("%Y.%m.%d") - if user.is_teacher: - person_type = "person" - else: - person_type = "student" - search_str = settings.RUZ_API + f"schedule/{person_type}/{user.hse_id}?start={start_date_str}&finish={end_date_str}&lng=1" + search_str = settings.RUZ_API + f"v3/ruz/lessons?start={start_date_str}&offset=30&email={user['email']}" try: data = get(search_str) except: @@ -59,11 +55,12 @@ class RUZ: return None formatted_data = [ { - field: element[field] + field: element.get(field) for field in fields } for element in data ] + self.reformat_data(formatted_data) return formatted_data def _name_weekday(self, weekday: int) -> str: diff --git a/migrate.py b/migrate.py new file mode 100644 index 0000000..ca61dc2 --- /dev/null +++ b/migrate.py @@ -0,0 +1,19 @@ +import settings +from daemons.bot import bot +from helpers.keyboards import campus_keyboard +from helpers.mongo import mongo +from requests import get + + +for user in mongo.users_collection.find({'$or': [{'hse_id': None}, {'hse_id': {'$exists': False}}], "state": {"$ne": "wait_for_campus"}}): + try: + bot.send_message( + user['chat_id'], + "Время закончить регистрацию! Выбери свой кампус обучения.", + reply_markup=campus_keyboard() + ) + except Exception as e: + print(e) + continue + print('sent to', user['chat_id']) + mongo.users_collection.update_one({"_id": user['_id']}, {"$set": {"state": "wait_for_campus"}}) diff --git a/settings.py b/settings.py index 5d045df..1716cb3 100644 --- a/settings.py +++ b/settings.py @@ -7,7 +7,7 @@ MONGO_PASSWORD = os.getenv("MONGO_PASSWORD", "password") MONGO_HOST = os.getenv("MONGO_HOST", "localhost") DEBUG = os.getenv("DEBUG", "true") == "true" -RUZ_API = "https://ruz.hse.ru/api/" +RUZ_API = "https://api.hseapp.ru/" MOSCOW_TIMEZONE = zoneinfo.ZoneInfo("Europe/Moscow") logging_config = {