initial
This commit is contained in:
commit
b7ef588b34
56
.deploy/deploy-dev.yaml
Normal file
56
.deploy/deploy-dev.yaml
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
bot:
|
||||||
|
image: mathwave/sprint-repo:ruz-bot
|
||||||
|
networks:
|
||||||
|
- b-jokes-net
|
||||||
|
environment:
|
||||||
|
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
||||||
|
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
||||||
|
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
|
||||||
|
command: bot
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
|
||||||
|
fetch:
|
||||||
|
image: mathwave/sprint-repo:ruz-bot
|
||||||
|
environment:
|
||||||
|
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
||||||
|
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
||||||
|
command: fetch
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
|
||||||
|
notify:
|
||||||
|
image: mathwave/sprint-repo:ruz-bot
|
||||||
|
environment:
|
||||||
|
MONGO_HOST: "mongo.develop.sprinthub.ru"
|
||||||
|
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
|
||||||
|
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
|
||||||
|
command: notify
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
|
||||||
|
networks:
|
||||||
|
b-jokes-net:
|
||||||
|
driver: overlay
|
||||||
|
common-infra-nginx:
|
||||||
|
external: true
|
51
.deploy/deploy-prod.yaml
Normal file
51
.deploy/deploy-prod.yaml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
version: "3.4"
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
bot:
|
||||||
|
image: mathwave/sprint-repo:ruz-bot
|
||||||
|
environment:
|
||||||
|
MONGO_HOST: "mongo.sprinthub.ru"
|
||||||
|
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
||||||
|
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
|
||||||
|
DEBUG: false
|
||||||
|
command: bot
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
|
||||||
|
fetch:
|
||||||
|
image: mathwave/sprint-repo:ruz-bot
|
||||||
|
environment:
|
||||||
|
MONGO_HOST: "mongo.sprinthub.ru"
|
||||||
|
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
||||||
|
DEBUG: false
|
||||||
|
command: fetch
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
|
||||||
|
notify:
|
||||||
|
image: mathwave/sprint-repo:ruz-bot
|
||||||
|
environment:
|
||||||
|
MONGO_HOST: "mongo.sprinthub.ru"
|
||||||
|
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
|
||||||
|
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
|
||||||
|
DEBUG: false
|
||||||
|
command: notify
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
119
.gitignore
vendored
Normal file
119
.gitignore
vendored
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# Django #
|
||||||
|
*.log
|
||||||
|
*.pot
|
||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
db.sqlite3
|
||||||
|
media
|
||||||
|
data
|
||||||
|
*/__pycache__
|
||||||
|
|
||||||
|
# Backup files #
|
||||||
|
*.bak
|
||||||
|
|
||||||
|
# If you are using PyCharm #
|
||||||
|
.idea
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.xml
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
*.iws /out/
|
||||||
|
|
||||||
|
# Python #
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python build/
|
||||||
|
develop-eggs/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
.pytest_cache/
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
.hypothesis/
|
||||||
|
postgres-data
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# celery
|
||||||
|
celerybeat-schedule.*
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
venv/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
# Sublime Text #
|
||||||
|
*.tmlanguage.cache
|
||||||
|
*.tmPreferences.cache
|
||||||
|
*.stTheme.cache
|
||||||
|
*.sublime-workspace
|
||||||
|
*.sublime-project
|
||||||
|
|
||||||
|
# sftp configuration file
|
||||||
|
sftp-config.json
|
||||||
|
|
||||||
|
# Package control specific files Package
|
||||||
|
Control.last-run
|
||||||
|
Control.ca-list
|
||||||
|
Control.ca-bundle
|
||||||
|
Control.system-ca-bundle
|
||||||
|
GitHub.sublime-settings
|
||||||
|
|
||||||
|
# Visual Studio Code #
|
||||||
|
.vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history
|
43
.gitlab-ci.yml
Normal file
43
.gitlab-ci.yml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- deploy-dev
|
||||||
|
- deploy-prod
|
||||||
|
|
||||||
|
build:
|
||||||
|
stage: build
|
||||||
|
tags:
|
||||||
|
- dev
|
||||||
|
before_script:
|
||||||
|
- docker login -u mathwave -p $DOCKERHUB_PASSWORD
|
||||||
|
script:
|
||||||
|
- docker build -t mathwave/sprint-repo:ruz-bot .
|
||||||
|
- docker push mathwave/sprint-repo:ruz-bot
|
||||||
|
|
||||||
|
.deploy:
|
||||||
|
before_script:
|
||||||
|
- docker login -u mathwave -p $DOCKERHUB_PASSWORD
|
||||||
|
|
||||||
|
deploy-dev:
|
||||||
|
extends:
|
||||||
|
- .deploy
|
||||||
|
stage: deploy-dev
|
||||||
|
tags:
|
||||||
|
- dev
|
||||||
|
rules:
|
||||||
|
- if: '$CI_COMMIT_BRANCH == "master"'
|
||||||
|
when: on_success
|
||||||
|
- when: manual
|
||||||
|
script:
|
||||||
|
- docker stack deploy --with-registry-auth -c ./.deploy/deploy-dev.yaml ruz-bot
|
||||||
|
|
||||||
|
deploy-prod:
|
||||||
|
extends:
|
||||||
|
- .deploy
|
||||||
|
stage: deploy-prod
|
||||||
|
tags:
|
||||||
|
- prod
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
when: manual
|
||||||
|
script:
|
||||||
|
- docker stack deploy --with-registry-auth -c ./.deploy/deploy-prod.yaml ruz-bot
|
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM python:3.10
|
||||||
|
RUN mkdir /usr/src/app
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY requirements.txt requirements.txt
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
COPY . .
|
||||||
|
ENTRYPOINT ["python", "entrypoint.py"]
|
0
daemons/__init__.py
Normal file
0
daemons/__init__.py
Normal file
13
daemons/bot.py
Normal file
13
daemons/bot.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import telebot
|
||||||
|
from telebot.types import Message
|
||||||
|
|
||||||
|
|
||||||
|
bot = telebot.TeleBot(os.getenv("TELEGRAM_TOKEN"))
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler()
|
||||||
|
def do_action(message: Message):
|
||||||
|
from helpers.answer import answer
|
||||||
|
answer.process(message)
|
83
daemons/fetch.py
Normal file
83
daemons/fetch.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import datetime
|
||||||
|
import zoneinfo
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from helpers.mongo import mongo
|
||||||
|
from helpers.ruz import ruz
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_schedule_for_user(user_hse_id: int):
|
||||||
|
zone = zoneinfo.ZoneInfo("Europe/Moscow")
|
||||||
|
today = datetime.datetime.now(zone)
|
||||||
|
next_day = today + datetime.timedelta(days=7)
|
||||||
|
schedule = ruz.get_schedule(user_hse_id, today, next_day)
|
||||||
|
if schedule is None:
|
||||||
|
return False
|
||||||
|
saved_ids = []
|
||||||
|
for element in schedule:
|
||||||
|
year, month, day = element['date'].split('.')
|
||||||
|
begin_hour, begin_minute = element['beginLesson'].split(':')
|
||||||
|
end_hour, end_minute = element['endLesson'].split(':')
|
||||||
|
lesson = mongo.lessons_collection.find_one({
|
||||||
|
"discipline": element['discipline'],
|
||||||
|
"auditorium": element['auditorium'],
|
||||||
|
"hse_user_id": user_hse_id,
|
||||||
|
"begin": datetime.datetime(
|
||||||
|
year=int(year),
|
||||||
|
month=int(month),
|
||||||
|
day=int(day),
|
||||||
|
hour=int(begin_hour),
|
||||||
|
minute=int(begin_minute)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
if lesson is None:
|
||||||
|
result = mongo.lessons_collection.insert_one({
|
||||||
|
"discipline": element['discipline'],
|
||||||
|
"auditorium": element['auditorium'],
|
||||||
|
"hse_user_id": user_hse_id,
|
||||||
|
"begin": datetime.datetime(
|
||||||
|
year=int(year),
|
||||||
|
month=int(month),
|
||||||
|
day=int(day),
|
||||||
|
hour=int(begin_hour),
|
||||||
|
minute=int(begin_minute)
|
||||||
|
),
|
||||||
|
"end": datetime.datetime(
|
||||||
|
year=int(year),
|
||||||
|
month=int(month),
|
||||||
|
day=int(day),
|
||||||
|
hour=int(end_hour),
|
||||||
|
minute=int(end_minute)
|
||||||
|
),
|
||||||
|
"building": element['building'],
|
||||||
|
"lecturer": element['lecturer'],
|
||||||
|
"notified": False
|
||||||
|
})
|
||||||
|
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}})
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def process():
|
||||||
|
for user in mongo.users_collection.find({}):
|
||||||
|
fetch_schedule_for_user(user['hse_id'])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_old():
|
||||||
|
zone = zoneinfo.ZoneInfo("Europe/Moscow")
|
||||||
|
today = datetime.datetime.now(zone)
|
||||||
|
mongo.lessons_collection.delete_many({"end": {"$lte": today - datetime.timedelta(days=1)}})
|
||||||
|
|
||||||
|
|
||||||
|
def fetch():
|
||||||
|
while True:
|
||||||
|
print("fetch start")
|
||||||
|
begin = datetime.datetime.now()
|
||||||
|
process()
|
||||||
|
end = datetime.datetime.now()
|
||||||
|
print('fetch finished')
|
||||||
|
print("time elapsed", (end - begin).total_seconds())
|
||||||
|
delete_old()
|
||||||
|
sleep(60 * 60)
|
38
daemons/notify.py
Normal file
38
daemons/notify.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import datetime
|
||||||
|
import zoneinfo
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from daemons.bot import bot
|
||||||
|
from helpers.mongo import mongo
|
||||||
|
|
||||||
|
|
||||||
|
def process():
|
||||||
|
for user in mongo.users_collection.find({"notify_minutes": {"$ne": None}}):
|
||||||
|
zone = zoneinfo.ZoneInfo("Europe/Moscow")
|
||||||
|
now = datetime.datetime.now(zone)
|
||||||
|
for lesson in mongo.lessons_collection.find({
|
||||||
|
"hse_user_id": user["hse_id"],
|
||||||
|
"begin": {"$lte": now + datetime.timedelta(minutes=5)},
|
||||||
|
"notified": False
|
||||||
|
}):
|
||||||
|
ans = ""
|
||||||
|
ans += "Аудитория: " + lesson["building"] + ", " + lesson["auditorium"] + "\n"
|
||||||
|
ans += "Начало: " + lesson["begin"].strftime("%H:%M") + "\n"
|
||||||
|
ans += "Конец: " + lesson["end"].strftime("%H:%M") + "\n"
|
||||||
|
ans += "Преподаватель: " + lesson["lecturer"] + "\n"
|
||||||
|
bot.send_message(
|
||||||
|
user["chat_id"],
|
||||||
|
"Уведомляю о занятиях!\n" + ans
|
||||||
|
)
|
||||||
|
mongo.lessons_collection.update_one({"_id": lesson['_id']}, {"$set": {"notified": True}})
|
||||||
|
|
||||||
|
|
||||||
|
def notify():
|
||||||
|
while True:
|
||||||
|
print("notify start")
|
||||||
|
begin = datetime.datetime.now()
|
||||||
|
process()
|
||||||
|
end = datetime.datetime.now()
|
||||||
|
print('notify finished')
|
||||||
|
print("time elapsed", (end - begin).total_seconds())
|
||||||
|
sleep(60 * 2)
|
19
entrypoint.py
Normal file
19
entrypoint.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from daemons.bot import bot
|
||||||
|
from daemons.fetch import fetch
|
||||||
|
from daemons.notify import notify
|
||||||
|
|
||||||
|
arg = sys.argv[-1]
|
||||||
|
|
||||||
|
if arg == "bot":
|
||||||
|
print("bot is starting")
|
||||||
|
bot.polling()
|
||||||
|
elif arg == "fetch":
|
||||||
|
print("fetch is starting")
|
||||||
|
fetch()
|
||||||
|
elif arg == "notify":
|
||||||
|
print("notify is starting")
|
||||||
|
notify()
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown param {arg}")
|
0
helpers/__init__.py
Normal file
0
helpers/__init__.py
Normal file
172
helpers/answer.py
Normal file
172
helpers/answer.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
import telebot
|
||||||
|
from telebot.types import Message
|
||||||
|
|
||||||
|
from daemons.bot import bot
|
||||||
|
from daemons.fetch import fetch_schedule_for_user
|
||||||
|
from helpers.models import UserSchema, User
|
||||||
|
from helpers.mongo import mongo
|
||||||
|
from helpers.ruz import ruz
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAnswer:
|
||||||
|
def process(self, message: Message):
|
||||||
|
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)
|
||||||
|
|
||||||
|
def set_state(self, user: User, state: str):
|
||||||
|
user.state = state
|
||||||
|
mongo.users_collection.update_one({"chat_id": user.chat_id}, {"$set": {"state": state}})
|
||||||
|
|
||||||
|
|
||||||
|
class Answer(BaseAnswer):
|
||||||
|
|
||||||
|
def handle_state_new(self, message: Message, user: User):
|
||||||
|
bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Привет! Я буду помогать тебе выживать в вышке!\nДля начала пришли мне свое ФИО.",
|
||||||
|
)
|
||||||
|
self.set_state(user, "wait_for_name")
|
||||||
|
|
||||||
|
def handle_state_wait_for_name(self, message: Message, user: User):
|
||||||
|
kb = telebot.types.ReplyKeyboardMarkup(True, True)
|
||||||
|
data = ruz.find_person(message.text)
|
||||||
|
if data is None:
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
"В РУЗе какая-то поломка, попробуй еще раз позже."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if len(data) == 0:
|
||||||
|
bot.send_message(user.chat_id, "К сожалению, в РУЗе не нашлось такого студента, попробуй еще раз.")
|
||||||
|
return
|
||||||
|
for entity in data:
|
||||||
|
kb.row(entity['description'])
|
||||||
|
user.name = message.text
|
||||||
|
mongo.users_collection.update_one(
|
||||||
|
{"chat_id": user.chat_id},
|
||||||
|
{"$set": {"name": user.name}})
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
"Отлично! Теперь выбери из списка свою группу.",
|
||||||
|
reply_markup=kb
|
||||||
|
)
|
||||||
|
self.set_state(user, "wait_for_group")
|
||||||
|
|
||||||
|
def handle_state_wait_for_group(self, message: Message, user: User):
|
||||||
|
group = message.text
|
||||||
|
data = ruz.find_person(user.name)
|
||||||
|
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
|
||||||
|
mongo.users_collection.update_one({"chat_id": user.chat_id}, {"$set": {
|
||||||
|
"hse_id": user.hse_id,
|
||||||
|
"group": group,
|
||||||
|
"name": user.name
|
||||||
|
}})
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
"Я нашел тебя в базе РУЗ. Я буду подсказывать тебе расписание, а также уведомлять о предстоящих парах.",
|
||||||
|
)
|
||||||
|
success = fetch_schedule_for_user(user.hse_id)
|
||||||
|
if success:
|
||||||
|
kb = telebot.types.ReplyKeyboardMarkup(True, True)
|
||||||
|
kb.row("Пары сегодня")
|
||||||
|
kb.row("Уведомления")
|
||||||
|
lessons = mongo.get_today_lessons(user)
|
||||||
|
if len(lessons) == 0:
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
"Сегодня у тебя нет пар.",
|
||||||
|
reply_markup=kb
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
ruz.schedule_builder(lessons),
|
||||||
|
reply_markup=kb
|
||||||
|
)
|
||||||
|
self.set_state(user, "ready")
|
||||||
|
|
||||||
|
def handle_state_ready(self, message: Message, user: User):
|
||||||
|
kb = telebot.types.ReplyKeyboardMarkup(True, True)
|
||||||
|
kb.row("Пары сегодня")
|
||||||
|
kb.row("Уведомления")
|
||||||
|
if message.text == "Пары сегодня":
|
||||||
|
lessons = mongo.get_today_lessons(user)
|
||||||
|
if len(lessons) == 0:
|
||||||
|
text = "Сегодня у тебя нет пар."
|
||||||
|
else:
|
||||||
|
text = ruz.schedule_builder(lessons)
|
||||||
|
elif message.text == "Уведомления":
|
||||||
|
kb = telebot.types.ReplyKeyboardMarkup(True, True)
|
||||||
|
kb.row("Не уведомлять")
|
||||||
|
kb.row("5 минут")
|
||||||
|
kb.row("10 минут")
|
||||||
|
kb.row("15 минут")
|
||||||
|
kb.row("20 минут")
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
"Выбери когда мне нужно тебе написать о предстоящей паре",
|
||||||
|
reply_markup=kb
|
||||||
|
)
|
||||||
|
self.set_state(user, "wait_for_notify")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
text = "Я не понимаю такой команды, используй кнопки."
|
||||||
|
bot.send_message(
|
||||||
|
user.chat_id,
|
||||||
|
text,
|
||||||
|
reply_markup=kb
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
else:
|
||||||
|
kb = telebot.types.ReplyKeyboardMarkup(True, True)
|
||||||
|
kb.row("Не уведомлять")
|
||||||
|
kb.row("5 минут")
|
||||||
|
kb.row("10 минут")
|
||||||
|
kb.row("15 минут")
|
||||||
|
kb.row("20 минут")
|
||||||
|
bot.send_message(user.chat_id, "Я не понимаю такой команды, используй кнопки.", reply_markup=kb)
|
||||||
|
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}."
|
||||||
|
else:
|
||||||
|
text = f"Принято! Я не уведомлять тебя."
|
||||||
|
kb = telebot.types.ReplyKeyboardMarkup(True, True)
|
||||||
|
kb.row("Пары сегодня")
|
||||||
|
kb.row("Уведомления")
|
||||||
|
bot.send_message(user.chat_id, text, reply_markup=kb)
|
||||||
|
self.set_state(user, "ready")
|
||||||
|
|
||||||
|
|
||||||
|
answer = Answer()
|
27
helpers/models.py
Normal file
27
helpers/models.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Lesson:
|
||||||
|
hse_id: int
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
UserSchema = marshmallow_dataclass.class_schema(User)
|
66
helpers/mongo.py
Normal file
66
helpers/mongo.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import datetime
|
||||||
|
import zoneinfo
|
||||||
|
from functools import cached_property
|
||||||
|
|
||||||
|
import pymongo
|
||||||
|
|
||||||
|
import settings
|
||||||
|
from helpers.models import UserSchema, User
|
||||||
|
|
||||||
|
|
||||||
|
class Mongo:
|
||||||
|
def __init__(self):
|
||||||
|
url = f"mongodb://{settings.MONGO_USER}:{settings.MONGO_PASSWORD}@{settings.MONGO_HOST}:27017/"
|
||||||
|
self.client = pymongo.MongoClient(url)
|
||||||
|
self.database = self.client.get_database("ruz-bot")
|
||||||
|
self.users_collection.create_index([
|
||||||
|
("chat_id", 1)
|
||||||
|
])
|
||||||
|
self.users_collection.create_index([
|
||||||
|
("notify_minutes", 1)
|
||||||
|
])
|
||||||
|
self.lessons_collection.create_index([
|
||||||
|
("discipline", 1),
|
||||||
|
("auditorium", 1),
|
||||||
|
("begin", 1),
|
||||||
|
("hse_user_id", 1)
|
||||||
|
])
|
||||||
|
self.lessons_collection.create_index([
|
||||||
|
("hse_user_id", 1),
|
||||||
|
("begin", 1)
|
||||||
|
])
|
||||||
|
self.lessons_collection.create_index([
|
||||||
|
("hse_user_id", 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)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def users_collection(self):
|
||||||
|
return self["users"]
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def lessons_collection(self):
|
||||||
|
return self["lessons"]
|
||||||
|
|
||||||
|
def get_today_lessons(self, user: User):
|
||||||
|
zone = zoneinfo.ZoneInfo("Europe/Moscow")
|
||||||
|
today = datetime.datetime.now(zone)
|
||||||
|
tomorrow = today + datetime.timedelta(days=1)
|
||||||
|
tomorrow = datetime.datetime(year=tomorrow.year, month=tomorrow.month, day=tomorrow.day)
|
||||||
|
lessons = []
|
||||||
|
for lesson in self.lessons_collection.find({
|
||||||
|
"hse_user_id": user.hse_id,
|
||||||
|
"begin": {"$gte": today, "$lte": tomorrow}}
|
||||||
|
):
|
||||||
|
lessons.append(lesson)
|
||||||
|
return lessons
|
||||||
|
|
||||||
|
|
||||||
|
mongo = Mongo()
|
62
helpers/ruz.py
Normal file
62
helpers/ruz.py
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from requests import get
|
||||||
|
|
||||||
|
import settings
|
||||||
|
|
||||||
|
|
||||||
|
fields = [
|
||||||
|
'discipline',
|
||||||
|
'building',
|
||||||
|
'auditorium',
|
||||||
|
'date',
|
||||||
|
'beginLesson',
|
||||||
|
'endLesson',
|
||||||
|
'lecturer'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class RUZ:
|
||||||
|
|
||||||
|
def find_person(self, name: str) -> dict | None:
|
||||||
|
search_str = settings.RUZ_API + f"search?term={name}&type=student"
|
||||||
|
try:
|
||||||
|
data = get(search_str)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
if data.status_code == 200:
|
||||||
|
return data.json()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_schedule(self, hse_id: int, begin_date: datetime.datetime, end_date: datetime.datetime):
|
||||||
|
start_date_str = begin_date.strftime("%Y.%m.%d")
|
||||||
|
end_date_str = end_date.strftime("%Y.%m.%d")
|
||||||
|
search_str = settings.RUZ_API + f"schedule/student/{hse_id}?start={start_date_str}&finish={end_date_str}&lng=1"
|
||||||
|
try:
|
||||||
|
data = get(search_str)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
if data.status_code != 200:
|
||||||
|
return None
|
||||||
|
data = data.json()
|
||||||
|
formatted_data = [
|
||||||
|
{
|
||||||
|
field: element[field]
|
||||||
|
for field in fields
|
||||||
|
}
|
||||||
|
for element in data
|
||||||
|
]
|
||||||
|
return formatted_data
|
||||||
|
|
||||||
|
def schedule_builder(self, lessons: list[dict]) -> str:
|
||||||
|
ans = ""
|
||||||
|
for lesson in lessons:
|
||||||
|
ans += "Аудитория: " + lesson["building"] + ", " + lesson["auditorium"] + "\n"
|
||||||
|
ans += "Начало: " + lesson["begin"].strftime("%H:%M") + "\n"
|
||||||
|
ans += "Конец: " + lesson["end"].strftime("%H:%M") + "\n"
|
||||||
|
ans += "Преподаватель: " + lesson["lecturer"] + "\n"
|
||||||
|
ans += "_______________\n"
|
||||||
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
ruz = RUZ()
|
14
requirements.txt
Normal file
14
requirements.txt
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
certifi==2022.9.24
|
||||||
|
charset-normalizer==2.1.1
|
||||||
|
idna==3.4
|
||||||
|
marshmallow==3.18.0
|
||||||
|
marshmallow-dataclass==8.5.9
|
||||||
|
mypy-extensions==0.4.3
|
||||||
|
packaging==21.3
|
||||||
|
pymongo==4.2.0
|
||||||
|
pyparsing==3.0.9
|
||||||
|
pyTelegramBotAPI==4.1.1
|
||||||
|
requests==2.28.1
|
||||||
|
typing-inspect==0.8.0
|
||||||
|
typing_extensions==4.4.0
|
||||||
|
urllib3==1.26.12
|
9
settings.py
Normal file
9
settings.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
MONGO_USER = os.getenv("MONGO_USER", "mongo")
|
||||||
|
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/"
|
Loading…
Reference in New Issue
Block a user