Compare commits

...

43 Commits

Author SHA1 Message Date
Egor Matveev
8799b3107b fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 22s
Deploy Dev / Push (pull_request) Successful in 13s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 9s
Deploy Prod / Deploy prod (pull_request) Successful in 17s
2025-09-15 00:33:18 +03:00
Egor Matveev
7a2950e480 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 6s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
2025-09-15 00:28:09 +03:00
Egor Matveev
7448b4e0c6 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 20s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
2025-09-15 00:19:20 +03:00
5e2c725bc2 Update helpers/answer.py
All checks were successful
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 15s
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 10s
Deploy Prod / Deploy prod (pull_request) Successful in 15s
2025-09-14 10:13:29 +03:00
595dfd0f6d Update .deploy/deploy-prod.yaml
All checks were successful
Deploy Dev / Build (pull_request) Successful in 52s
Deploy Dev / Push (pull_request) Successful in 28s
Deploy Dev / Deploy dev (pull_request) Successful in 15s
2025-09-12 21:20:29 +03:00
0990f684da Update .deploy/deploy-dev.yaml 2025-09-12 21:20:03 +03:00
Egor Matveev
5fa5789559 fix
All checks were successful
Deploy Prod / Build (pull_request) Successful in 4s
Deploy Prod / Push (pull_request) Successful in 9s
Deploy Prod / Deploy prod (pull_request) Successful in 14s
2025-06-15 23:30:50 +03:00
Egor Matveev
7fcbb316e6 fix
Some checks failed
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 20s
Deploy Prod / Push (pull_request) Has been cancelled
Deploy Prod / Deploy prod (pull_request) Has been cancelled
Deploy Prod / Build (pull_request) Has been cancelled
2025-06-15 23:26:18 +03:00
Egor Matveev
e9ecb7d94f fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 10s
Deploy Dev / Deploy dev (pull_request) Successful in 23s
2025-06-15 15:02:35 +03:00
Egor Matveev
dbe5df157b fix
Some checks failed
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Failing after 7s
2025-06-15 15:01:22 +03:00
Egor Matveev
f4af02ec1d fix
Some checks failed
Deploy Dev / Push (pull_request) Successful in 25s
Deploy Dev / Build (pull_request) Successful in 48s
Deploy Dev / Deploy dev (pull_request) Failing after 6s
2025-06-15 14:56:49 +03:00
156e1407ed fix
All checks were successful
Deploy Prod / Build (pull_request) Successful in 5s
Deploy Prod / Push (pull_request) Successful in 8s
Deploy Prod / Deploy prod (pull_request) Successful in 14s
2024-11-27 19:11:14 +03:00
18aabe497b fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 7s
Deploy Dev / Deploy dev (pull_request) Successful in 14s
Deploy Prod / Build (pull_request) Successful in 4s
Deploy Prod / Push (pull_request) Successful in 8s
Deploy Prod / Deploy prod (pull_request) Successful in 20s
2024-11-27 16:37:59 +03:00
144e5d4335 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 7s
Deploy Dev / Deploy dev (pull_request) Successful in 15s
2024-11-27 16:35:19 +03:00
48ab0d1797 Merge branch 'master' of https://gitea.sprinthub.ru/self/ruz-bot
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 14s
2024-11-27 16:30:16 +03:00
ddfd5596d9 add 2024-11-27 16:29:37 +03:00
f8417edc94 Merge pull request 'queues' (#19) from queues into master
Reviewed-on: #19
2024-11-27 02:46:58 +03:00
292bacc963 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 7s
Deploy Dev / Deploy dev (pull_request) Successful in 16s
Deploy Prod / Build (pull_request) Successful in 4s
Deploy Prod / Push (pull_request) Successful in 8s
Deploy Prod / Deploy prod (pull_request) Successful in 17s
2024-11-27 02:44:10 +03:00
e76cfc7994 fix
All checks were successful
Deploy Prod / Build (pull_request) Successful in 5s
Deploy Prod / Push (pull_request) Successful in 10s
Deploy Prod / Deploy prod (pull_request) Successful in 24s
2024-11-26 19:59:08 +03:00
c146fc7f1e fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 19s
Deploy Prod / Build (pull_request) Successful in 4s
Deploy Prod / Push (pull_request) Successful in 8s
Deploy Prod / Deploy prod (pull_request) Successful in 19s
2024-11-25 00:00:26 +03:00
2425ee2988 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
2024-11-24 23:55:46 +03:00
bc4f74e20f add 2024-11-24 23:54:28 +03:00
4e5d483208 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 20s
2024-11-23 22:31:36 +03:00
1d00a27b15 fix
Some checks failed
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Failing after 4s
2024-11-23 22:26:35 +03:00
c3886433ac fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 7s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
2024-11-22 01:52:20 +03:00
ac1c8745dc fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
2024-11-22 01:47:46 +03:00
17c9b1b693 fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 4s
Deploy Dev / Push (pull_request) Successful in 8s
Deploy Dev / Deploy dev (pull_request) Successful in 17s
2024-11-17 23:07:37 +03:00
e286a3078e fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 10s
Deploy Dev / Deploy dev (pull_request) Successful in 19s
2024-11-17 23:02:07 +03:00
64e957af2f fix
All checks were successful
Deploy Dev / Build (pull_request) Successful in 8s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 22s
2024-11-17 22:58:04 +03:00
272233fc67 trial
All checks were successful
Deploy Dev / Build (pull_request) Successful in 5s
Deploy Dev / Push (pull_request) Successful in 9s
Deploy Dev / Deploy dev (pull_request) Successful in 21s
2024-11-17 22:52:50 +03:00
b8f0624665 deploy
All checks were successful
Deploy Prod / Build (pull_request) Successful in 6s
Deploy Prod / Push (pull_request) Successful in 8s
Deploy Prod / Deploy prod (pull_request) Successful in 16s
2024-10-11 07:16:48 +03:00
389ead9018 deploy
All checks were successful
Deploy Dev / Build (pull_request) Successful in 2m33s
Deploy Dev / Push (pull_request) Successful in 42s
Deploy Dev / Deploy dev (pull_request) Successful in 14s
Deploy Prod / Build (pull_request) Successful in 4s
Deploy Prod / Push (pull_request) Successful in 8s
Deploy Prod / Deploy prod (pull_request) Successful in 16s
2024-10-11 07:04:56 +03:00
Administrator
d40dd2c81d Merge branch 'master' of http://gitlab.sprinthub.ru/self/ruz-bot 2024-10-03 00:36:04 +03:00
Administrator
39e02f3759 fix 2024-10-03 00:35:39 +03:00
c1ffaca96b Update deploy-prod.yaml 2024-03-24 14:35:03 +00:00
Administrator
0e07ebdc61 bot enabled exp 2024-02-18 00:48:29 +03:00
Administrator
86a3b891f1 daemon true 2024-02-17 01:33:20 +03:00
Administrator
f63ce9ee90 no daemon 2024-02-17 01:26:36 +03:00
Administrator
2ebba22478 thread 2024-02-17 01:24:45 +03:00
Administrator
7ce43cf4b6 kill thread 2024-02-17 01:16:51 +03:00
Administrator
50d9d1c953 Merge branch 'master' of http://gitlab.sprinthub.ru/self/ruz-bot 2024-02-17 01:10:20 +03:00
Administrator
ea6f5e2ca4 fix staff 2024-02-17 01:09:30 +03:00
70d2f4ce52 alice 2024-02-12 22:01:56 +03:00
20 changed files with 423 additions and 234 deletions

View File

@@ -3,15 +3,16 @@ version: "3.4"
services:
bot:
worker:
image: mathwave/sprint-repo:ruz-bot
environment:
MONGO_HOST: "mongo.develop.sprinthub.ru"
STAGE: "development"
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
command: bot
networks:
- queues-development
- configurator
- mongo-development
command: worker
deploy:
mode: replicated
restart_policy:
@@ -23,11 +24,11 @@ services:
fetch:
image: mathwave/sprint-repo:ruz-bot
environment:
MONGO_HOST: "mongo.develop.sprinthub.ru"
STAGE: "development"
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
networks:
- queues-development
- mongo-development
command: fetch
deploy:
mode: replicated
@@ -40,11 +41,11 @@ services:
notify:
image: mathwave/sprint-repo:ruz-bot
environment:
MONGO_HOST: "mongo.develop.sprinthub.ru"
STAGE: "development"
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
networks:
- queues-development
- mongo-development
command: notify
deploy:
mode: replicated
@@ -57,13 +58,12 @@ services:
ruz-bot-nginx:
image: mathwave/sprint-repo:ruz-bot
networks:
- common-infra-nginx
- common-infra-nginx-development
- queues-development
- mongo-development
environment:
MONGO_HOST: "mongo.develop.sprinthub.ru"
STAGE: "development"
MONGO_PASSWORD: $MONGO_PASSWORD_DEV
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_DEV
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
command: api
deploy:
mode: replicated
@@ -74,5 +74,11 @@ services:
order: start-first
networks:
common-infra-nginx:
common-infra-nginx-development:
external: true
queues-development:
external: true
configurator:
external: true
mongo-development:
external: true

View File

@@ -3,24 +3,20 @@ version: "3.4"
services:
bot:
worker:
image: mathwave/sprint-repo:ruz-bot
environment:
MONGO_HOST: "mongo.sprinthub.ru"
STAGE: "production"
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
DEBUG: "false"
command: bot
networks:
- queues
- configurator
- mongo
command: worker
deploy:
mode: replicated
restart_policy:
condition: any
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config:
parallelism: 1
order: start-first
@@ -28,21 +24,18 @@ services:
fetch:
image: mathwave/sprint-repo:ruz-bot
environment:
MONGO_HOST: "mongo.sprinthub.ru"
STAGE: "production"
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
DEBUG: "false"
networks:
- queues
- configurator
- mongo
command: fetch
deploy:
mode: replicated
restart_policy:
condition: any
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config:
parallelism: 1
order: start-first
@@ -50,21 +43,18 @@ services:
notify:
image: mathwave/sprint-repo:ruz-bot
environment:
MONGO_HOST: "mongo.sprinthub.ru"
STAGE: "production"
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
DEBUG: "false"
networks:
- queues
- configurator
- mongo
command: notify
deploy:
mode: replicated
restart_policy:
condition: any
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config:
parallelism: 1
order: start-first
@@ -73,22 +63,17 @@ services:
image: mathwave/sprint-repo:ruz-bot
networks:
- common-infra-nginx
- configurator
- mongo
environment:
MONGO_HOST: "mongo.sprinthub.ru"
STAGE: "production"
MONGO_PASSWORD: $MONGO_PASSWORD_PROD
TELEGRAM_TOKEN: $TELEGRAM_TOKEN_PROD
PLATFORM_SECURITY_TOKEN: $PLATFORM_SECURITY_TOKEN
DEBUG: "false"
command: api
deploy:
mode: replicated
restart_policy:
condition: any
placement:
constraints:
# - node.role == worker
- node.labels.zone == ru
update_config:
parallelism: 1
order: start-first
@@ -96,3 +81,9 @@ services:
networks:
common-infra-nginx:
external: true
queues:
external: true
configurator:
external: true
mongo:
external: true

View File

@@ -0,0 +1,44 @@
name: Deploy Dev
on:
pull_request:
branches:
- dev
types: [closed]
jobs:
build:
name: Build
runs-on: [ dev ]
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: dev
- name: build
run: docker build -t mathwave/sprint-repo:ruz-bot .
push:
name: Push
runs-on: [ dev ]
needs: build
steps:
- name: push
run: docker push mathwave/sprint-repo:ruz-bot
deploy-dev:
name: Deploy dev
runs-on: [prod]
needs: push
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: dev
- name: deploy
env:
TELEGRAM_TOKEN_DEV: ${{ secrets.TELEGRAM_TOKEN_DEV }}
MONGO_PASSWORD_DEV: ${{ secrets.MONGO_PASSWORD_DEV }}
run: docker stack deploy --with-registry-auth -c ./.deploy/deploy-dev.yaml ruz-bot-development

View File

@@ -0,0 +1,45 @@
name: Deploy Prod
on:
pull_request:
branches:
- prod
types: [closed]
jobs:
build:
name: Build
runs-on: [ dev ]
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: prod
- name: build
run: docker build -t mathwave/sprint-repo:ruz-bot .
push:
name: Push
runs-on: [ dev ]
needs: build
steps:
- name: push
run: docker push mathwave/sprint-repo:ruz-bot
deploy-prod:
name: Deploy prod
runs-on: [prod]
needs: push
steps:
- name: login
run: docker login -u mathwave -p ${{ secrets.DOCKERHUB_PASSWORD }}
- name: checkout
uses: actions/checkout@v4
with:
ref: prod
- name: deploy
env:
TELEGRAM_TOKEN_PROD: ${{ secrets.TELEGRAM_TOKEN_PROD }}
MONGO_PASSWORD_PROD: ${{ secrets.MONGO_PASSWORD_PROD }}
PLATFORM_SECURITY_TOKEN: ${{ secrets.PLATFORM_SECURITY_TOKEN }}
run: docker stack deploy --with-registry-auth -c ./.deploy/deploy-prod.yaml ruz-bot

View File

@@ -7,6 +7,7 @@ RUN apt-get install -y locales locales-all
ENV LANGUAGE ru_RU.UTF-8
ENV LANG ru_RU.UTF-8
ENV LC_ALL ru_RU.UTF-8
ENV PYTHONUNBUFFERED=1
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .

View File

@@ -3,73 +3,75 @@ from flask import Flask, request
import settings
from helpers.alice import Processor
from helpers.mongo import mongo
from daemons import base
def api():
app = Flask(__name__)
class Daemon(base.Daemon):
def execute(self):
app = Flask(__name__)
@app.route('/stats/json', methods=['GET'])
def stats_json():
all_users = mongo.users_collection.count_documents({})
teachers = mongo.users_collection.count_documents({"is_teacher": True})
return {
"Всего пользователей": all_users,
"Пользователей прошедших регистрацию": mongo.users_collection.count_documents({'email': {'$ne': None}}),
"Преподавателей": teachers,
"Отписались от уведомлений": mongo.users_collection.count_documents({'notify_minutes': None}),
"Отправлено уведомлений за сегодня": mongo.lessons_collection.count_documents({'notified': True}),
"Проиндексировано занятий из РУЗа": mongo.lessons_collection.count_documents({}),
"Пользователей из Москвы": mongo.users_collection.count_documents(
{'campus': 'Москва'}) + mongo.users_collection.count_documents({'campus': {'$exists': False}}),
"Пользователей из Москвы (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Москва', 'email': {'$ne': None}}) + mongo.users_collection.count_documents(
{'campus': {'$exists': False}, 'email': {'$ne': None}}),
"Пользователей из Перми": mongo.users_collection.count_documents({'campus': 'Пермь'}),
"Пользователей из Перми (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Пермь', 'email': {'$ne': None}}),
"Пользователей из Нижнего Новгорода": mongo.users_collection.count_documents({'campus': 'Нижний Новгород'}),
"Пользователей из Нижнего Новгорода (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Нижний Новгород', 'email': {'$ne': None}}),
"Пользователей из Санкт-Петербурга": mongo.users_collection.count_documents({'campus': 'Санкт-Петербург'}),
"Пользователей из Санкт-Петербурга (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Санкт-Петербург', 'email': {'$ne': None}})
}
@app.route('/stats', methods=['GET'])
def stats():
all_users = mongo.users_collection.count_documents({})
teachers = mongo.users_collection.count_documents({"is_teacher": True})
text = f"Всего пользователей: {all_users}<br>" \
f"Пользователей прошедших регистрацию: {mongo.users_collection.count_documents({'email': {'$ne': None}})}<br>" \
f"Студентов: {all_users - teachers}<br>" \
f"Преподавателей: {teachers}<br>" \
f"Отписались от уведомлений: {mongo.users_collection.count_documents({'notify_minutes': None})}<br>" \
f"Отправлено уведомлений за сегодня: {mongo.lessons_collection.count_documents({'notified': True})}<br>" \
f"Проиндексировано занятий из РУЗа: {mongo.lessons_collection.count_documents({})}<br>" \
f"<br>" \
f"<br>" \
f"Пользователей из Москвы: {mongo.users_collection.count_documents({'campus': 'Москва'}) + mongo.users_collection.count_documents({'campus': {'$exists': False}})}<br>" \
f"Пользователей из Москвы (регистрация): {mongo.users_collection.count_documents({'campus': 'Москва', 'email': {'$ne': None}}) + mongo.users_collection.count_documents({'campus': {'$exists': False}, 'email': {'$ne': None}})}<br>" \
f"Пользователей из Перми: {mongo.users_collection.count_documents({'campus': 'Пермь'})}<br>" \
f"Пользователей из Перми (регистрация): {mongo.users_collection.count_documents({'campus': 'Пермь', 'email': {'$ne': None}})}<br>" \
f"Пользователей из Нижнего Новгорода: {mongo.users_collection.count_documents({'campus': 'Нижний Новгород'})}<br>" \
f"Пользователей из Нижнего Новгорода (регистрация): {mongo.users_collection.count_documents({'campus': 'Нижний Новгород', 'email': {'$ne': None}})}<br>" \
f"Пользователей из Санкт-Петербурга: {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург'})}<br>" \
f"Пользователей из Санкт-Петербурга (регистрация): {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург', 'email': {'$ne': None}})}<br>"
return text
@app.route('/alice', methods=['POST'])
def alice():
req = request.json
processor = Processor(req)
response = {
"version": req['version'],
"session": req['session'],
"response": {
"end_session": False
@app.route('/stats/json', methods=['GET'])
def stats_json():
all_users = mongo.users_collection.count_documents({})
teachers = mongo.users_collection.count_documents({"is_teacher": True})
return {
"Всего пользователей": all_users,
"Пользователей прошедших регистрацию": mongo.users_collection.count_documents({'email': {'$ne': None}}),
"Преподавателей": teachers,
"Отписались от уведомлений": mongo.users_collection.count_documents({'notify_minutes': None}),
"Отправлено уведомлений за сегодня": mongo.lessons_collection.count_documents({'notified': True}),
"Проиндексировано занятий из РУЗа": mongo.lessons_collection.count_documents({}),
"Пользователей из Москвы": mongo.users_collection.count_documents(
{'campus': 'Москва'}) + mongo.users_collection.count_documents({'campus': {'$exists': False}}),
"Пользователей из Москвы (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Москва', 'email': {'$ne': None}}) + mongo.users_collection.count_documents(
{'campus': {'$exists': False}, 'email': {'$ne': None}}),
"Пользователей из Перми": mongo.users_collection.count_documents({'campus': 'Пермь'}),
"Пользователей из Перми (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Пермь', 'email': {'$ne': None}}),
"Пользователей из Нижнего Новгорода": mongo.users_collection.count_documents({'campus': 'Нижний Новгород'}),
"Пользователей из Нижнего Новгорода (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Нижний Новгород', 'email': {'$ne': None}}),
"Пользователей из Санкт-Петербурга": mongo.users_collection.count_documents({'campus': 'Санкт-Петербург'}),
"Пользователей из Санкт-Петербурга (регистрация)": mongo.users_collection.count_documents(
{'campus': 'Санкт-Петербург', 'email': {'$ne': None}})
}
}
response['response'].update(processor.process())
return response
app.run(host="0.0.0.0", port=1238, debug=settings.DEBUG)
@app.route('/stats', methods=['GET'])
def stats():
all_users = mongo.users_collection.count_documents({})
teachers = mongo.users_collection.count_documents({"is_teacher": True})
text = f"Всего пользователей: {all_users}<br>" \
f"Пользователей прошедших регистрацию: {mongo.users_collection.count_documents({'email': {'$ne': None}})}<br>" \
f"Студентов: {all_users - teachers}<br>" \
f"Преподавателей: {teachers}<br>" \
f"Отписались от уведомлений: {mongo.users_collection.count_documents({'notify_minutes': None})}<br>" \
f"Отправлено уведомлений за сегодня: {mongo.lessons_collection.count_documents({'notified': True})}<br>" \
f"Проиндексировано занятий из РУЗа: {mongo.lessons_collection.count_documents({})}<br>" \
f"<br>" \
f"<br>" \
f"Пользователей из Москвы: {mongo.users_collection.count_documents({'campus': 'Москва'}) + mongo.users_collection.count_documents({'campus': {'$exists': False}})}<br>" \
f"Пользователей из Москвы (регистрация): {mongo.users_collection.count_documents({'campus': 'Москва', 'email': {'$ne': None}}) + mongo.users_collection.count_documents({'campus': {'$exists': False}, 'email': {'$ne': None}})}<br>" \
f"Пользователей из Перми: {mongo.users_collection.count_documents({'campus': 'Пермь'})}<br>" \
f"Пользователей из Перми (регистрация): {mongo.users_collection.count_documents({'campus': 'Пермь', 'email': {'$ne': None}})}<br>" \
f"Пользователей из Нижнего Новгорода: {mongo.users_collection.count_documents({'campus': 'Нижний Новгород'})}<br>" \
f"Пользователей из Нижнего Новгорода (регистрация): {mongo.users_collection.count_documents({'campus': 'Нижний Новгород', 'email': {'$ne': None}})}<br>" \
f"Пользователей из Санкт-Петербурга: {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург'})}<br>" \
f"Пользователей из Санкт-Петербурга (регистрация): {mongo.users_collection.count_documents({'campus': 'Санкт-Петербург', 'email': {'$ne': None}})}<br>"
return text
@app.route('/alice', methods=['POST'])
def alice():
req = request.json
processor = Processor(req)
response = {
"version": req['version'],
"session": req['session'],
"response": {
"end_session": False
}
}
response['response'].update(processor.process())
return response
app.run(host="0.0.0.0", port=1238, debug=settings.DEBUG)

3
daemons/base.py Normal file
View File

@@ -0,0 +1,3 @@
class Daemon:
def execute(self):
raise NotImplementedError

View File

@@ -1,24 +0,0 @@
import os
import telebot
from telebot.types import Message
import settings
from helpers.mongo import mongo
from helpers.sprint_platform import platform
bot = telebot.TeleBot(os.getenv("TELEGRAM_TOKEN"))
@bot.message_handler(commands=['start'])
def on_start(message: Message):
mongo.users_collection.delete_many({"chat_id": message.chat.id})
do_action(message)
@bot.message_handler()
def do_action(message: Message):
if settings.STAGE == 'development' and not platform.is_staff(telegram_id=message.chat.id):
return
from helpers.answer import Answer
Answer(message).process()

View File

@@ -6,6 +6,8 @@ from helpers import now, campus_timdelta
from helpers.mongo import mongo
from helpers.ruz import ruz
from daemons import base
def fetch_schedule_for_user(user: dict):
today = now(user)
@@ -75,16 +77,17 @@ def delete_old():
mongo.lessons_collection.delete_many({"end": {"$lte": datetime.datetime.now() - datetime.timedelta(days=1)}})
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()
class Daemon(base.Daemon):
def execute(self):
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()

View File

@@ -2,12 +2,12 @@ import datetime
import logging
from time import sleep
from telebot.apihelper import ApiTelegramException
from daemons.bot import bot
from helpers import now
from helpers.mongo import mongo
from helpers.ruz import ruz
from daemons import base
from utils import queues
def process():
@@ -25,13 +25,7 @@ def process():
ans += f"🧑‍🏫 {(lesson['lecturer'] or 'Неизвестно')}\n"
if lesson.get('link', None):
ans += f"🔗 {lesson['link']}"
try:
bot.send_message(
user["chat_id"],
f"Через {user['notify_minutes']} минут у тебя занятие!\n" + ans
)
except ApiTelegramException:
pass
queues.set_task('botalka_mailbox', {'project': 'ruz-bot', 'name': 'telegram-bot', 'body': {'text': f"Через {user['notify_minutes']} минут у тебя занятие!\n" + ans, 'chat_id': user["chat_id"], 'parse_mode': 'Markdown'}}, 1)
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}}):
@@ -46,11 +40,7 @@ def process():
else:
text = ruz.schedule_builder(lessons)
try:
bot.send_message(
user["chat_id"],
f"Уведомляю о занятиях! Твое расписание на {'сегодня' if user.get('daily_notify_today', True) else 'завтра'}:\n" + text,
parse_mode='Markdown'
)
queues.set_task('botalka_mailbox', {'project': 'ruz-bot', 'name': 'telegram-bot', 'body': {'text': f"Уведомляю о занятиях! Твое расписание на {'сегодня' if user.get('daily_notify_today', True) else 'завтра'}:\n" + text, 'chat_id': user["chat_id"], 'parse_mode': 'Markdown'}}, 1)
except:
pass
mongo.users_collection.update_one(
@@ -72,34 +62,29 @@ def process():
ans += f"🧑‍🏫 {(lesson['lecturer'] or 'Неизвестно')}\n"
if lesson.get('link', None):
ans += f"🔗 {lesson['link']}"
try:
mess = "Пары начутся через "
if user['first_lesson_notify'] == 30:
mess += "30 минут"
elif user['first_lesson_notify'] == 60:
mess += "1 час"
elif user['first_lesson_notify'] == 4 * 60:
mess += "4 часа"
else:
mess += "12 часов"
mess += "!\n\nТвоя первая пара:\n\n" + ans
bot.send_message(
user["chat_id"],
mess
)
except ApiTelegramException:
pass
mess = "Пары начутся через "
if user['first_lesson_notify'] == 30:
mess += "30 минут"
elif user['first_lesson_notify'] == 60:
mess += "1 час"
elif user['first_lesson_notify'] == 4 * 60:
mess += "4 часа"
else:
mess += "12 часов"
mess += "!\n\nТвоя первая пара:\n\n" + ans
queues.set_task('botalka_mailbox', {'project': 'ruz-bot', 'name': 'telegram-bot', 'body': {'text': mess, 'chat_id': user["chat_id"], 'parse_mode': 'Markdown'}}, 1)
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))}, "user_email": user["email"]}, {"$set": {"notified_today": True}})
break
def notify():
while True:
logging.info("notify start")
begin = datetime.datetime.now()
process()
end = datetime.datetime.now()
logging.info('notify finished')
logging.info("time elapsed %s", (end - begin).total_seconds())
sleep(63 - end.second)
class Daemon(base.Daemon):
def execute(self):
while True:
logging.info("notify start")
begin = datetime.datetime.now()
process()
end = datetime.datetime.now()
logging.info('notify finished')
logging.info("time elapsed %s", (end - begin).total_seconds())
sleep(63 - end.second)

19
daemons/worker.py Normal file
View File

@@ -0,0 +1,19 @@
from daemons import base
from utils import queues
import json
from telebot.types import Message
class Daemon(base.Daemon, queues.TasksHandlerMixin):
@property
def queue_name(self):
return 'ruz_bot_worker'
def execute(self):
self.poll()
def process(self, payload):
message: Message = Message.de_json(json.dumps(payload))
from helpers.answer import Answer
Answer(message).process()

View File

@@ -2,10 +2,6 @@ import logging.config
import sys
import settings
from daemons.api import api
from daemons.bot import bot
from daemons.fetch import fetch
from daemons.notify import notify
import locale
@@ -13,17 +9,25 @@ logging.config.dictConfig(settings.logging_config)
locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8')
arg = sys.argv[-1]
if arg == "bot":
logging.info("bot is starting")
bot.polling()
if arg == "poll":
logging.info("poll is starting")
from daemons.poll import Daemon
elif arg == 'worker':
logging.info("worker is starting")
from daemons.worker import Daemon
elif arg == 'mailbox':
logging.info("mailbox is starting")
from daemons.mailbox import Daemon
elif arg == "fetch":
logging.info("fetch is starting")
fetch()
from daemons.fetch import Daemon
elif arg == "notify":
logging.info("notify is starting")
notify()
from daemons.notify import Daemon
elif arg == "api":
logging.info("api is starting")
api()
from daemons.api import Daemon
else:
raise ValueError(f"Unknown param {arg}")
Daemon().execute()

View File

@@ -1,9 +1,9 @@
import logging
from typing import Optional
from daemons.bot import bot
from helpers import now
from helpers.mongo import mongo
from utils import queues
def try_parse(message: str) -> Optional[int]:
@@ -84,7 +84,7 @@ class Processor:
}
else:
mongo.users_collection.update_one({"yandex_code": code}, {"$set": {"yandex_id": self.user_id, "yandex_code": None}})
bot.send_message(user['chat_id'], "Алиса успешно подключена!")
queues.set_task('ruz_bot_mailbox', {'text': "Алиса успешно подключена!", 'chat_id': user["chat_id"]}, 1)
lesson = self.get_lesson_for_user(user['chat_id'])
if lesson is None:
return {
@@ -94,4 +94,4 @@ class Processor:
return {
"text": f'Отлично, теперь я могу подсказывать тебе расписание. Твое ближайшее занятие в {lesson["begin"].strftime("%A %d %B %H:%M")}: {lesson["discipline"].replace("(рус)", "").replace("(анг)", "")}',
"end_session": True
}
}

View File

@@ -1,9 +1,9 @@
import datetime
import logging
from random import choice
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, no_daily_notify, \
@@ -11,6 +11,16 @@ from helpers.keyboards import main_keyboard, notify_keyboard, yes_no_keyboard, a
from helpers.mongo import mongo
from helpers.sprint_platform import platform
from helpers.ruz import ruz
from utils import queues
class User:
def __init__(self, telegram_id):
self.telegram_id = telegram_id
@property
def platform_staff(self):
return platform.is_staff(telegram_id=self.telegram_id)
class Answer:
@@ -20,9 +30,10 @@ class Answer:
message_text: str
def __init__(self, message: Message):
self.message = message
self.message = message
self.message_text = message.text or message.caption or ""
if self.message_text.startswith('/start'):
mongo.users_collection.delete_many({"chat_id": message.chat.id})
user = mongo.users_collection.find_one({"chat_id": message.chat.id})
if user is None:
user = {
@@ -43,6 +54,7 @@ class Answer:
self.user = user
def process(self):
user = User(self.user['chat_id'])
getattr(
self,
"handle_state_" + self.user['state'],
@@ -55,7 +67,10 @@ class Answer:
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)
body = {'text': text, 'chat_id': self.user['chat_id'], 'parse_mode': 'Markdown'}
if reply_markup:
body['reply_markup'] = reply_markup.to_json()
queues.set_task('botalka_mailbox', {'project': 'ruz-bot', 'name': 'telegram-bot', 'body': body}, 5)
def set_state(self, state: str):
self.user['state'] = state
@@ -145,6 +160,21 @@ class Answer:
self.set_state("reset")
return
elif self.message_text == "Подключение Алисы":
alice_exp = platform.get_experiment('alice')
telegram_id = self.user['chat_id']
try:
user = User(telegram_id)
alice_exp_enabled = alice_exp['enabled'] and eval(alice_exp['condition'])
except Exception as exc:
logging.info(exc)
alice_exp_enabled = False
if not alice_exp_enabled:
self.send_message(
'Эта функция еще не работает!',
reply_markup=main_keyboard(self.user['chat_id']),
parse_mode='Markdown'
)
return
if self.user.get('yandex_id', None) is None:
words = platform.get_config('words')
while True:

View File

@@ -13,12 +13,7 @@ def main_keyboard(telegram_id):
kb.row("Ежедневные уведомления")
alice_exp = platform.get_experiment('alice')
try:
class User:
def __init__(self, telegram_id):
self.telegram_id = telegram_id
@property
def platform_staff(self):
return platform.is_staff(telegram_id=self.telegram_id)
from helpers.answer import User
user = User(telegram_id)
alice_exp_enabled = alice_exp['enabled'] and eval(alice_exp['condition'])
except Exception as exc:

View File

@@ -9,7 +9,7 @@ from helpers import now
class Mongo:
def __init__(self):
url = f"mongodb://{settings.MONGO_USER}:{settings.MONGO_PASSWORD}@{settings.MONGO_HOST}:27017/"
url = f"mongodb://{settings.MONGO_USER}:{settings.MONGO_PASSWORD}@mongo:27017/"
self.client = pymongo.MongoClient(url)
self.database = self.client.get_database("ruz-bot")
self.users_collection.create_index([

View File

@@ -12,7 +12,8 @@ fields = [
'date_start',
'date_end',
'lecturer_profiles',
'stream_links'
'stream_links',
'type',
]
@@ -39,7 +40,7 @@ class RUZ:
del data['stream_links']
def get_schedule(self, user: dict, begin_date: datetime.datetime):
start_date_str = begin_date.strftime("%Y.%m.%d")
start_date_str = begin_date.strftime("%Y-%m-%d")
search_str = settings.RUZ_API + f"v3/ruz/lessons?start={start_date_str}&offset=30&email={user['email']}"
try:
data = get(search_str)

View File

@@ -15,13 +15,11 @@ class PlatformClient:
self.stage = stage
self.configs = configs
self.experiments = experiments
self.endpoint = 'https://platform.sprinthub.ru/'
self.configs_url = urllib.parse.urljoin(self.endpoint, 'configs/get')
self.experiments_url = urllib.parse.urljoin(self.endpoint, 'experiments/get')
self.staff_url = urllib.parse.urljoin(self.endpoint, 'is_staff')
self.fetch_url = urllib.parse.urljoin(self.endpoint, 'fetch')
self.endpoint = 'http://configurator/'
self.fetch_url = urllib.parse.urljoin(self.endpoint, '/api/v1/fetch')
self.config_storage = {}
self.experiment_storage = {}
self.staff_storage = {}
self.poll_data()
if need_poll:
self.poll_data_in_thread()
@@ -32,7 +30,7 @@ class PlatformClient:
sleep(30)
self.fetch()
Thread(target=inner).start()
Thread(target=inner, daemon=True).start()
def poll_data(self):
self.fetch(with_exception=True)
@@ -43,7 +41,6 @@ class PlatformClient:
try:
response = get(
url,
headers={'X-Security-Token': self.platform_security_token},
params=params
)
if response.status_code == 200:
@@ -63,6 +60,10 @@ class PlatformClient:
local_platform = json.loads(open('local_platform.json', 'r').read())
self.config_storage = local_platform['configs']
self.experiment_storage = local_platform['experiments']
self.staff_storage = {
key: set(value)
for key, value in local_platform['platform_staff'].items()
}
return
response_data = self.request_with_retries(self.fetch_url, {
'project': self.app_name,
@@ -70,6 +71,10 @@ class PlatformClient:
}, with_exception)
self.config_storage = response_data['configs']
self.experiment_storage = response_data['experiments']
self.staff_storage = {
key: set(value)
for key, value in response_data['platform_staff'].items()
}
def fetch_configs(self, with_exception=False):
if self.stage == 'local':
@@ -98,18 +103,10 @@ class PlatformClient:
self.experiment_storage[experiment] = response_data
def is_staff(self, **kwargs):
if self.stage == 'local':
local_platform = json.loads(open('local_platform.json', 'r').read())
local_staff = local_platform['staff']
for element in local_staff:
for key, value in kwargs.items():
if element[key] == value:
return True
return False
response_data = self.request_with_retries(self.staff_url, kwargs)
if response_data is None:
return False
return response_data['is_staff']
for key, value in kwargs.items():
if value in self.staff_storage[key]:
return True
return False
def get_config(self, name):
return self.config_storage[name]

0
utils/__init__.py Normal file
View File

87
utils/queues.py Normal file
View File

@@ -0,0 +1,87 @@
from concurrent.futures import ThreadPoolExecutor
import datetime
import os
import traceback
import zoneinfo
import requests
import time
stage = os.getenv("STAGE", 'local')
if stage == 'local':
QUEUES_URL = 'http://localhost:1239'
else:
QUEUES_URL = 'http://queues:1239'
class QueuesException(Exception):
...
class TasksHandlerMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.executor = ThreadPoolExecutor(max_workers=1)
def _send_metric(self, start: datetime.datetime, end: datetime.datetime, success: bool):
def send():
requests.post(f'{QUEUES_URL}/api/v1/metric', json={
'service': 'ruz-bot',
'queue': self.queue_name,
'success': success,
'timestamp': start.strftime("%Y-%m-%dT%H:%M:%S") + "Z",
"success": success,
"execution_time_ms": (end - start).microseconds // 1000,
"environment": stage,
})
self.executor.submit(send)
def poll(self):
while True:
try:
response = requests.get(f'{QUEUES_URL}/api/v1/take', headers={'queue': self.queue_name}).json()
except requests.JSONDecodeError:
print('Unable to decode json')
time.sleep(3)
continue
task = response.get('task')
if not task:
time.sleep(0.2)
continue
start = datetime.datetime.now(zoneinfo.ZoneInfo("Europe/Moscow"))
try:
print(f'process task with id {task["id"]}, attempt {task["attempt"]}')
self.process(task['payload'])
success = True
except Exception as exc:
print(f'Error processing message id={task["id"]}, payload={task["payload"]}, exc={exc}')
traceback.print_exc()
success = False
end = datetime.datetime.now(zoneinfo.ZoneInfo("Europe/Moscow"))
if success:
try:
resp = requests.post(f'{QUEUES_URL}/api/v1/finish', json={'id': task['id']})
if resp.status_code != 202:
raise QueuesException
print(f'finish task with id {task["id"]}')
except:
print(f'Failed to finish task id={task["id"]}')
self._send_metric(start, end, success)
@property
def queue_name(self):
raise NotImplemented
def process(self, payload):
raise NotImplemented
def set_task(queue_name: str, payload: dict, seconds_to_execute: int, delay: int|None = None):
resp = requests.post(f'{QUEUES_URL}/api/v1/put', headers={'queue': queue_name}, json={
'payload': payload,
'seconds_to_execute': seconds_to_execute,
'delay': delay,
})
if resp.status_code != 202:
raise QueuesException