1
0
Fork 0
mirror of https://github.com/EDeev/school_menu.git synced 2026-10-08 04:59:35 +03:00

Compare commits

..

5 commits

Author SHA1 Message Date
6bb94afe64 README на русском и английском 2026-10-02 23:40:56 +00:00
59a97485e8 CI: синтаксис, неопределённые имена и проверка разбора ячеек 2026-10-02 23:40:56 +00:00
6cccfd202d Токен, ID бота и технической группы — из окружения
В base.py стояли замаскированные значения (ID_BOT = **********), из-за которых модуль
не импортировался; теперь BOT_TOKEN, BOT_ID и TECH_GROUP_ID берутся из переменных.
2026-10-02 23:40:56 +00:00
ad20d4adea Разбор граммовки и калорийности без eval
Значения ячеек Excel-меню с сайта школы вычислялись через eval — любой текст в файле
выполнился бы как код. Теперь arithmetic() разбирает только числа и + - * /.
2026-10-02 23:40:56 +00:00
ac866d7df1 Переводы строк приведены к LF 2026-10-02 23:38:04 +00:00
7 changed files with 605 additions and 625 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
* text=auto eol=lf
*.db binary

29
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: pip install ruff==0.14.0 openpyxl==3.1.2 requests==2.31.0
- name: Синтаксис и неопределённые имена
run: ruff check --select E9,F821,F811 . && python -m py_compile code/*.py
- name: Разбор ячеек меню без eval
working-directory: code
run: |
python -c "
from pars import arithmetic
assert arithmetic('150+30') == 180 and arithmetic('12.5') == 12.5
try:
arithmetic('__import__(\"os\")')
raise SystemExit('eval-подобный ввод не отвергнут')
except ValueError:
pass"

41
README.en.md Normal file
View file

@ -0,0 +1,41 @@
# School Menu Bot
[Русский](README.md) · **English**
[![CI](https://github.com/EDeev/school_menu/actions/workflows/ci.yml/badge.svg)](https://github.com/EDeev/school_menu/actions/workflows/ci.yml)
A school cafeteria Telegram bot: every morning it posts the menu from the school website, takes breakfast,
lunch and snack orders by class and compiles a summary for the cafeteria.
**Status:** school project (2021–2022), completed, archived
**Stack:** Python 3.10 · aiogram 2 · SQLite · openpyxl (menu from Excel) · pymorphy2
- Today's and tomorrow's menu (`/today`, `/tomorrow`) and a 06:00 broadcast
- Ordering with buttons, cancelling and changing an order
- Class registration by code, separate groups for students and the cafeteria
- Order summary by class with correct Russian word forms
## Running
```bash
pip install -r requirements.txt
cd code && BOT_TOKEN=token BOT_ID=bot_id TECH_GROUP_ID=group_id python bot.py
```
Portion weights and calories from Excel are parsed without `eval`: only numbers and `+ - * /` are allowed.
## License
School project (2021–2022). The code is open for study; there is no separate license.
## Author
**Egor Deev** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
---
<div align="center">
<sub>⭐ If you find this project useful, give it a star on GitHub!</sub>
<p><sub>Made with ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
</div>

153
README.md
View file

@ -1,154 +1,41 @@
# 🍽️ School Menu Bot # School Menu Bot
Автоматизированная система управления школьным питанием с интеграцией Telegram Bot API для сбора и обработки заказов. **Русский** · [English](README.en.md)
## 🎯 Описание проекта [![CI](https://github.com/EDeev/school_menu/actions/workflows/ci.yml/badge.svg)](https://github.com/EDeev/school_menu/actions/workflows/ci.yml)
School Menu Bot представляет собой многофункциональное решение для организации процесса заказа школьного питания. Система обеспечивает автоматический парсинг меню с официального сайта школы, предоставляет интерактивный интерфейс для заказов через Telegram и ведет детальную статистику по группам и пользователям. Telegram-бот школьной столовой: каждое утро присылает меню с сайта школы, принимает заказы завтраков,
обедов и полдников по классам и собирает сводку для столовой.
## ⭐ Ключевые возможности **Статус:** школьный проект (2021–2022), завершён, в архиве
### Пользовательский функционал **Стек:** Python 3.10 · aiogram 2 · SQLite · openpyxl (меню из Excel) · pymorphy2
- **Просмотр меню**: Получение актуального меню на текущий и следующий день
- **Интерактивные заказы**: Система выбора питания через inline-клавиатуры
- **Гибкое планирование**: Возможность заказа комбинаций (завтрак, обед, полдник)
- **Управление заказами**: Отмена и изменение активных заказов
### Административные функции - Меню на сегодня и завтра (`/today`, `/tomorrow`) и рассылка в 06:00
- **Групповая регистрация**: Система верификации через уникальные коды - Заказ питания кнопками, отмена и изменение заказа
- **Аналитика заказов**: Детальная статистика по группам и пользователям - Регистрация класса по коду, отдельные группы для учеников и для столовой
- **Автоматические уведомления**: Ежедневная рассылка меню в 06:00 - Сводка заказов по классам с правильным склонением слов
- **Мультигрупповая поддержка**: Раздельные каналы для учеников и администрации
### Техническая архитектура ## Запуск
- **Парсинг данных**: Автоматическое извлечение меню из Excel-файлов
- **Многоуровневая БД**: Система баз данных для пользователей, групп и заказов
- **Асинхронная обработка**: Неблокирующее выполнение операций
- **Морфологический анализ**: Корректное склонение слов в статистике
## 🛠️ Технологический стек
### Backend Framework
- **Python 3.8+** - Основной язык разработки
- **Aiogram** - Асинхронный фреймворк для Telegram Bot API
- **Asyncio** - Асинхронное программирование
### Data Processing
- **OpenPyXL** - Обработка Excel-файлов с меню
- **Requests** - HTTP-запросы к внешним ресурсам
- **PyMorphy2** - Морфологический анализ русского языка
### Database Management
- **SQLite** - Легковесная встроенная СУБД
- **Многоуровневая архитектура БД**:
- `base.db` - Основные данные групп и пользователей
- `users.db` - Пользовательские связки
- `groups.db` - Групповые данные и статистика
- `now.db` - Текущие активные заказы
## 📁 Структура проекта
```
school_menu/
├── code/
│ ├── bot.py # Основная логика бота
│ ├── pars.py # Парсер школьного меню
│ ├── sql.py # ORM для работы с БД
│ └── base.py # Конфигурация и токены
├── db/
│ ├── base.db # Основная база данных
│ ├── users.db # Пользовательские данные
│ ├── groups.db # Групповая статистика
│ └── now.db # Текущие заказы (пересоздается ежедневно)
├── requirements.txt # Зависимости проекта
└── README.md # Документация
```
## 🚀 Установка и запуск
### Предварительные требования
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
cd code && BOT_TOKEN=токен BOT_ID=id_бота TECH_GROUP_ID=id_группы python bot.py
``` ```
### Конфигурация Граммовка и калорийность из Excel разбираются без `eval`: допускаются только числа и `+ - * /`.
1. Создайте нового бота через [@BotFather](https://t.me/BotFather)
2. Настройте `code/base.py`:
```python
TOKEN = "ваш_токен_от_BotFather"
ID_BOT = ваш_id_бота
TEX_GROUP = "-id_технической_группы"
```
### Запуск системы ## Лицензия
```bash
cd code/
python bot.py
```
## 💡 Алгоритм работы Школьный проект (2021–2022). Код открыт для изучения, отдельной лицензии нет.
### Процесс регистрации групп ## Автор
1. Администратор вызывает `/add_group` в личных сообщениях
2. Система генерирует уникальные коды для основной и технической групп
3. Коды вводятся в соответствующих чатах для верификации
4. Создается связка групп с инициализацией статистики
### Система заказов **Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
1. Парсинг актуального меню с `school01.ru/food/`
2. Пользователь выбирает комбинацию питания через `/eat`
3. Заказ фиксируется в основной группе и дублируется в техническую
4. Обновление статистики по группам и пользователям
### Автоматизация процессов
- **06:00 ежедневно**: Рассылка меню на следующий день
- **Учет выходных**: Автоматический пропуск воскресений
- **Сброс заказов**: Ежедневная очистка активных заказов
## 📊 Команды бота
| Команда | Описание | Доступность |
|---------|----------|-------------|
| `/start` | Инициализация бота | Личные сообщения |
| `/help` | Справочная информация | Универсально |
| `/today` | Меню на сегодня | Универсально |
| `/tomorrow` | Меню на завтра | Универсально |
| `/eat` | Интерфейс заказа | Только группы |
| `/stat` | Статистика группы | Только группы |
| `/add_group` | Регистрация групп | Личные сообщения |
| `/del_group` | Удаление связки | Личные сообщения |
## 🎯 Особенности реализации
### Безопасность данных
- Уникальная система кодов для верификации
- Раздельное хранение пользовательских и групповых данных
- Автоматическая очистка временных данных
### Производительность
- Асинхронная архитектура для обработки множественных запросов
- Оптимизированные SQL-запросы с контекстными менеджерами
- Кэширование статистических данных
### Масштабируемость
- Модульная архитектура классов для работы с БД
- Поддержка неограниченного количества групп
- Гибкая система расширения функционала
## 📋 Лицензия
Проект распространяется под лицензией MIT.
## 👥 Автор
**Деев Егор Викторович** - Backend Developer
- GitHub: [@EDeev](https://github.com/EDeev)
- Email: egor@deev.space
- Telegram: [@Egor_Deev](https://t.me/Egor_Deev)
--- ---
<div align="center"> <div align="center">
<sub>Разработано для автоматизации процессов школьного питания с упором на удобство использования и надежность системы.</sub> <sub>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p> <p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
</div> </div>

View file

@ -1,4 +1,8 @@
TOKEN = "token from BotFather" import os
ID_BOT = ********** # Токен бота от @BotFather — из переменной окружения BOT_TOKEN
TEX_GROUP = "-*********" TOKEN = os.environ.get("BOT_TOKEN", "")
# Числовой ID бота и ID технической группы для служебных сообщений — тоже из окружения
ID_BOT = int(os.environ.get("BOT_ID", "0"))
TEX_GROUP = os.environ.get("TECH_GROUP_ID", "")

View file

@ -1,436 +1,436 @@
import logging, asyncio, random, pymorphy2 import logging, asyncio, random, pymorphy2
import base, pars, sql, os import base, pars, sql, os
import datetime as dt import datetime as dt
from aiogram import Bot, Dispatcher, executor, types from aiogram import Bot, Dispatcher, executor, types
# log level # log level
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
morph = pymorphy2.MorphAnalyzer() morph = pymorphy2.MorphAnalyzer()
# bot init # bot init
bot = Bot(token=base.TOKEN) bot = Bot(token=base.TOKEN)
dp = Dispatcher(bot) dp = Dispatcher(bot)
# create now.db # create now.db
file = open('../db/now.db', 'w+') file = open('../db/now.db', 'w+')
file.close() file.close()
# db init # db init
db = sql.Base('../db/base.db') db = sql.Base('../db/base.db')
du = sql.User('../db/users.db') du = sql.User('../db/users.db')
dg = sql.Group('../db/groups.db') dg = sql.Group('../db/groups.db')
dn = sql.Now('../db/now.db') dn = sql.Now('../db/now.db')
@dp.message_handler(commands="start") @dp.message_handler(commands="start")
async def start(message: types.Message): async def start(message: types.Message):
if 0 < int(message.chat.id): if 0 < int(message.chat.id):
await message.answer("Ghdbtn", types.ParseMode.MARKDOWN) await message.answer("Ghdbtn", types.ParseMode.MARKDOWN)
# ОБНОВЛЕНИЕ АЙДИ ГРУППЫ # ОБНОВЛЕНИЕ АЙДИ ГРУППЫ
@dp.message_handler(content_types=['migrate_to_chat_id', 'migrate_from_chat_id']) @dp.message_handler(content_types=['migrate_to_chat_id', 'migrate_from_chat_id'])
async def chat_reload(message: types.Message): async def chat_reload(message: types.Message):
if du.group_exists(message.migrate_from_chat_id): if du.group_exists(message.migrate_from_chat_id):
du.update_group_id(message.migrate_from_chat_id, message.migrate_to_chat_id) du.update_group_id(message.migrate_from_chat_id, message.migrate_to_chat_id)
# КОМАНДЫ # КОМАНДЫ
@dp.message_handler(commands="help") @dp.message_handler(commands="help")
async def hlp(message: types.Message): async def hlp(message: types.Message):
if 0 < int(message.chat.id): if 0 < int(message.chat.id):
await message.answer("*Инструкция использования:*\n\n" await message.answer("*Инструкция использования:*\n\n"
"*/eat* - с помощью этой команды вы можете заказать питание\n" "*/eat* - с помощью этой команды вы можете заказать питание\n"
"*/today* - команда выдающая меню на сегодня\n" "*/today* - команда выдающая меню на сегодня\n"
"*/tomorrow* - команда выдающая меню на завтра\n\n" "*/tomorrow* - команда выдающая меню на завтра\n\n"
"*/add_group* - команда выдающая меню на завтра\n" "*/add_group* - команда выдающая меню на завтра\n"
"*/del_group* - команда выдающая меню на завтра\n", types.ParseMode.MARKDOWN) "*/del_group* - команда выдающая меню на завтра\n", types.ParseMode.MARKDOWN)
else: else:
await message.answer("*Инструкция использования:*\n\n" await message.answer("*Инструкция использования:*\n\n"
"*/eat* - с помощью этой команды вы можете заказать питание\n" "*/eat* - с помощью этой команды вы можете заказать питание\n"
"*/today* - команда выдающая меню на сегодня\n" "*/today* - команда выдающая меню на сегодня\n"
"*/tomorrow* - команда выдающая меню на завтра\n", types.ParseMode.MARKDOWN) "*/tomorrow* - команда выдающая меню на завтра\n", types.ParseMode.MARKDOWN)
@dp.message_handler(commands="stat") @dp.message_handler(commands="stat")
async def stat(message: types.Message): async def stat(message: types.Message):
if 0 > int(message.chat.id): if 0 > int(message.chat.id):
group_id = message.chat.id group_id = message.chat.id
if du.group_exists(group_id): if du.group_exists(group_id):
group_id = du.get_group_id(group_id) group_id = du.get_group_id(group_id)
if db.main_group_exists(group_id): if db.main_group_exists(group_id):
overall = db.stat_group(group_id) overall = db.stat_group(group_id)
all_users = dg.stat_all_users(group_id) all_users = dg.stat_all_users(group_id)
z = [[user[1], user[0]] for user in all_users if user[1] != 0] z = [[user[1], user[0]] for user in all_users if user[1] != 0]
o = [[user[2], user[0]] for user in all_users if user[2] != 0] o = [[user[2], user[0]] for user in all_users if user[2] != 0]
p = [[user[3], user[0]] for user in all_users if user[3] != 0] p = [[user[3], user[0]] for user in all_users if user[3] != 0]
txt = f"<i><b>Всего было сделано заказов - {sum(overall)}</b></i>\n" txt = f"<i><b>Всего было сделано заказов - {sum(overall)}</b></i>\n"
line = morph.parse('заказ')[0] line = morph.parse('заказ')[0]
lst = [[f"\n\t<b>* Завтраков - {str(overall[0])}</b>\n", z], [f"\n\t<b>* Обедов - {str(overall[1])}</b>\n", o], lst = [[f"\n\t<b>* Завтраков - {str(overall[0])}</b>\n", z], [f"\n\t<b>* Обедов - {str(overall[1])}</b>\n", o],
[f"\n\t<b>* Полдников - {str(overall[2])}</b>\n", p]] [f"\n\t<b>* Полдников - {str(overall[2])}</b>\n", p]]
for i in lst: for i in lst:
txt = txt + i[0] txt = txt + i[0]
if i[1]: if i[1]:
for n, j in enumerate(i[1][:3]): for n, j in enumerate(i[1][:3]):
txt = txt + "\t\t\t<b>" + str(n + 1) + ". " + j[1] + "</b> " + str(j[0]) + " " + line.make_agree_with_number(j[0]).word + "\n" txt = txt + "\t\t\t<b>" + str(n + 1) + ". " + j[1] + "</b> " + str(j[0]) + " " + line.make_agree_with_number(j[0]).word + "\n"
await message.answer(txt, types.ParseMode.HTML) await message.answer(txt, types.ParseMode.HTML)
else: else:
await message.answer("Ваша группа не зарегистрирована!") await message.answer("Ваша группа не зарегистрирована!")
else: else:
await message.answer("Ваша группа не зарегистрирована!") await message.answer("Ваша группа не зарегистрирована!")
@dp.message_handler(commands="eat") @dp.message_handler(commands="eat")
async def eat(message: types.Message): async def eat(message: types.Message):
if 0 > int(message.chat.id): if 0 > int(message.chat.id):
keyboard1 = {"inline_keyboard": keyboard1 = {"inline_keyboard":
[[{"text": "Завтрак", "callback_data": "z"}, {"text": "Обед", "callback_data": "o"}, [[{"text": "Завтрак", "callback_data": "z"}, {"text": "Обед", "callback_data": "o"},
{"text": "Полдник", "callback_data": "p"}], [{"text": "Завтрак / Обед", "callback_data": "zo"}], {"text": "Полдник", "callback_data": "p"}], [{"text": "Завтрак / Обед", "callback_data": "zo"}],
[{"text": "Завтрак / Полдник", "callback_data": "zp"}], [{"text": "Завтрак / Полдник", "callback_data": "zp"}],
[{"text": "Обед / Полдник", "callback_data": "op"}], [{"text": "Обед / Полдник", "callback_data": "op"}],
[{"text": "Завтрак / Обед / Полдник", "callback_data": "zop"}]]} [{"text": "Завтрак / Обед / Полдник", "callback_data": "zop"}]]}
await bot.send_message(chat_id=message.chat.id, await bot.send_message(chat_id=message.chat.id,
text='*Это меню выбора притания!* Всё, что вам надо сделать, это лишь выбрать ' text='*Это меню выбора притания!* Всё, что вам надо сделать, это лишь выбрать '
'необходимый заказ.\n\nEсли после нажатия бот написал, что вы выбрали, то всё ' 'необходимый заказ.\n\nEсли после нажатия бот написал, что вы выбрали, то всё '
'прошло успешно и вы можете не беспокоиться!', parse_mode=types.ParseMode.MARKDOWN, 'прошло успешно и вы можете не беспокоиться!', parse_mode=types.ParseMode.MARKDOWN,
reply_markup=keyboard1) reply_markup=keyboard1)
keyboard2 = {"inline_keyboard": [[{"text": "Отменить заказ", "callback_data": "cancel"}]]} keyboard2 = {"inline_keyboard": [[{"text": "Отменить заказ", "callback_data": "cancel"}]]}
await bot.send_message(chat_id=message.chat.id, await bot.send_message(chat_id=message.chat.id,
text='Если вы хотите сменить заказ, то отмените действующий заказ по кнопке ниже и ' text='Если вы хотите сменить заказ, то отмените действующий заказ по кнопке ниже и '
'закажите заново!', parse_mode=types.ParseMode.MARKDOWN, 'закажите заново!', parse_mode=types.ParseMode.MARKDOWN,
reply_markup=keyboard2) reply_markup=keyboard2)
else: else:
await message.answer("Эта команда доступна к использованию *только в чатах*", types.ParseMode.MARKDOWN) await message.answer("Эта команда доступна к использованию *только в чатах*", types.ParseMode.MARKDOWN)
@dp.message_handler(commands="del_group") @dp.message_handler(commands="del_group")
async def delg(message: types.Message): async def delg(message: types.Message):
if 0 < int(message.chat.id): if 0 < int(message.chat.id):
user_id = message.from_user.id user_id = message.from_user.id
if du.user_exists(user_id): if du.user_exists(user_id):
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
if db.user_main_exists(user_id): if db.user_main_exists(user_id):
keyboard = {"inline_keyboard": [[{"text": "Подтвердить удаление", "callback_data": "del"}]]} keyboard = {"inline_keyboard": [[{"text": "Подтвердить удаление", "callback_data": "del"}]]}
await bot.send_message(chat_id=message.chat.id, await bot.send_message(chat_id=message.chat.id,
text='Вы точно хотите удалить статистику и связку групп?', text='Вы точно хотите удалить статистику и связку групп?',
parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard) parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard)
else: else:
await message.answer("У вас нет связки групп для их удаления!") await message.answer("У вас нет связки групп для их удаления!")
else: else:
await message.answer("У вас нет связки групп для их удаления!") await message.answer("У вас нет связки групп для их удаления!")
else: else:
await message.answer("Эта команда доступна к использованию *только в личном чате*", types.ParseMode.MARKDOWN) await message.answer("Эта команда доступна к использованию *только в личном чате*", types.ParseMode.MARKDOWN)
@dp.message_handler(commands="add_group") @dp.message_handler(commands="add_group")
async def addg(message: types.Message): async def addg(message: types.Message):
if 0 < int(message.chat.id): if 0 < int(message.chat.id):
user_id = message.from_user.id user_id = message.from_user.id
flag = False flag = False
if not du.user_exists(user_id): if not du.user_exists(user_id):
du.add_user(user_id) du.add_user(user_id)
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
all_cods = db.all_cods() all_cods = db.all_cods()
cod1 = random.randint(1000, 9999) cod1 = random.randint(1000, 9999)
while cod1 in all_cods[0]: while cod1 in all_cods[0]:
cod1 = random.randint(1000, 9999) cod1 = random.randint(1000, 9999)
cod2 = random.randint(1000, 9999) cod2 = random.randint(1000, 9999)
while cod2 in all_cods[1]: while cod2 in all_cods[1]:
cod2 = random.randint(1000, 9999) cod2 = random.randint(1000, 9999)
db.add_user_tranzit(user_id, cod1, cod2) db.add_user_tranzit(user_id, cod1, cod2)
await message.answer(f"Введите этот код в группе с учениками - *{str(cod1)}*, а этот в личной для " await message.answer(f"Введите этот код в группе с учениками - *{str(cod1)}*, а этот в личной для "
f"фиксации заказа - *{str(cod2)}*", types.ParseMode.MARKDOWN) f"фиксации заказа - *{str(cod2)}*", types.ParseMode.MARKDOWN)
flag = True flag = True
else: else:
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
if db.user_tranzit_exists(user_id): if db.user_tranzit_exists(user_id):
cods = db.get_cods(user_id) cods = db.get_cods(user_id)
await message.answer(f"Введите этот код в основной группе с учениками - *{str(cods[0])}*, а этот в " await message.answer(f"Введите этот код в основной группе с учениками - *{str(cods[0])}*, а этот в "
f"личной для фиксации заказа - *{str(cods[1])}*", types.ParseMode.MARKDOWN) f"личной для фиксации заказа - *{str(cods[1])}*", types.ParseMode.MARKDOWN)
flag = True flag = True
else: else:
if not db.user_main_exists(user_id): if not db.user_main_exists(user_id):
all_cods = db.all_cods() all_cods = db.all_cods()
cod1 = random.randint(1000, 9999) cod1 = random.randint(1000, 9999)
while cod1 in all_cods[0]: while cod1 in all_cods[0]:
cod1 = random.randint(1000, 9999) cod1 = random.randint(1000, 9999)
cod2 = random.randint(1000, 9999) cod2 = random.randint(1000, 9999)
while cod2 in all_cods[1]: while cod2 in all_cods[1]:
cod2 = random.randint(1000, 9999) cod2 = random.randint(1000, 9999)
db.add_user_tranzit(user_id, cod1, cod2) db.add_user_tranzit(user_id, cod1, cod2)
await message.answer(f"Введите этот код в группе с учениками - *{str(cod1)}*, а этот в личной для " await message.answer(f"Введите этот код в группе с учениками - *{str(cod1)}*, а этот в личной для "
f"фиксации заказа - *{str(cod2)}*", types.ParseMode.MARKDOWN) f"фиксации заказа - *{str(cod2)}*", types.ParseMode.MARKDOWN)
flag = True flag = True
else: else:
await message.answer("У вас уже есть связка группы, вам надо её удалить, прежде чем верифицировать " await message.answer("У вас уже есть связка группы, вам надо её удалить, прежде чем верифицировать "
"новую!", types.ParseMode.MARKDOWN) "новую!", types.ParseMode.MARKDOWN)
if flag: if flag:
keyboard = {"inline_keyboard": [[{"text": "Отменить верификацию", "callback_data": "cnl"}]]} keyboard = {"inline_keyboard": [[{"text": "Отменить верификацию", "callback_data": "cnl"}]]}
await bot.send_message(chat_id=message.chat.id, await bot.send_message(chat_id=message.chat.id,
text='*Вы можете отменить заявку* если не хотите связывать группы для сбора ' text='*Вы можете отменить заявку* если не хотите связывать группы для сбора '
'заказов питания!', parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard) 'заказов питания!', parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard)
else: else:
await message.answer("Эта команда доступна к использованию *только в личном чате*", types.ParseMode.MARKDOWN) await message.answer("Эта команда доступна к использованию *только в личном чате*", types.ParseMode.MARKDOWN)
@dp.message_handler(commands=["today", "tomorrow"]) @dp.message_handler(commands=["today", "tomorrow"])
async def todtom(message: types.Message): async def todtom(message: types.Message):
food = pars.eating(0 if "today" in message.text else 1) food = pars.eating(0 if "today" in message.text else 1)
if food[0]: if food[0]:
food = food[1] food = food[1]
if "today" in message.text: if "today" in message.text:
et = f"<b>Питание за {'.'.join(reversed(str(dt.date.today()).split('-')))}</b>\n\n" et = f"<b>Питание за {'.'.join(reversed(str(dt.date.today()).split('-')))}</b>\n\n"
else: else:
et = f"<b>Питание за {'.'.join(reversed(str(dt.date.today() + dt.timedelta(days=1)).split('-')))}</b>\n\n" et = f"<b>Питание за {'.'.join(reversed(str(dt.date.today() + dt.timedelta(days=1)).split('-')))}</b>\n\n"
for j in food: for j in food:
et = et + "<i><b>" + str(j) + ":</b></i>\n" et = et + "<i><b>" + str(j) + ":</b></i>\n"
for i in food[j]: for i in food[j]:
et = et + "<b>* " + str(i[0]) + "</b> <i>[ " et = et + "<b>* " + str(i[0]) + "</b> <i>[ "
et = et + str(i[1][0]) + " г. " + str(i[1][1]) + " ккал. ]</i>" + "\n" et = et + str(i[1][0]) + " г. " + str(i[1][1]) + " ккал. ]</i>" + "\n"
et = et + "\n\n" et = et + "\n\n"
await message.answer(et, types.ParseMode.HTML) await message.answer(et, types.ParseMode.HTML)
else: else:
if food[1]: if food[1]:
await message.answer(f"<b>Ошибка в системе бота!</b>\n\nОна будет в скором времени исправлена...", await message.answer(f"<b>Ошибка в системе бота!</b>\n\nОна будет в скором времени исправлена...",
types.ParseMode.HTML) types.ParseMode.HTML)
await bot.send_message(chat_id=base.TEX_GROUP, text=f"<b>[ {str(dt.datetime.now())[:-10]} ]</b> " await bot.send_message(chat_id=base.TEX_GROUP, text=f"<b>[ {str(dt.datetime.now())[:-10]} ]</b> "
f"<b><i>=></i></b> <i>{food[3]}</i> (ошибка в системе)", f"<b><i>=></i></b> <i>{food[3]}</i> (ошибка в системе)",
parse_mode=types.ParseMode.HTML) parse_mode=types.ParseMode.HTML)
else: else:
if food[2] != 6: if food[2] != 6:
await message.answer(f"<b>Нет данных по питанию!</b>\n\nКак только данные появятся вы сможете " await message.answer(f"<b>Нет данных по питанию!</b>\n\nКак только данные появятся вы сможете "
f"их получить этой же командой!", types.ParseMode.HTML) f"их получить этой же командой!", types.ParseMode.HTML)
await bot.send_message(chat_id=base.TEX_GROUP, text=f"<b>[ {str(dt.datetime.now())[:-10]} ]</b> " await bot.send_message(chat_id=base.TEX_GROUP, text=f"<b>[ {str(dt.datetime.now())[:-10]} ]</b> "
f"<b><i>=></i></b> <i>{food[3]}</i> (нет данных по " f"<b><i>=></i></b> <i>{food[3]}</i> (нет данных по "
f"питанию)", parse_mode=types.ParseMode.HTML) f"питанию)", parse_mode=types.ParseMode.HTML)
else: else:
await message.answer(f"<b>Вы вызывете команду в воскресенье!</b>\n\nНа что вы надеетесь?", await message.answer(f"<b>Вы вызывете команду в воскресенье!</b>\n\nНа что вы надеетесь?",
types.ParseMode.HTML) types.ParseMode.HTML)
# ИЛАЙН КЛАВИАТУРА # ИЛАЙН КЛАВИАТУРА
@dp.callback_query_handler(text=["z", "o", "p", "zo", "zp", "op", "zop"]) @dp.callback_query_handler(text=["z", "o", "p", "zo", "zp", "op", "zop"])
async def var(call: types.CallbackQuery): async def var(call: types.CallbackQuery):
et = {"z": "завтрак", "o": "обед", "p": "полдник", "zo": "завтрак / обед", "zp": "завтрак / полдник", et = {"z": "завтрак", "o": "обед", "p": "полдник", "zo": "завтрак / обед", "zp": "завтрак / полдник",
"op": "обед / полдник", "zop": "завтрак / обед / полдник"} "op": "обед / полдник", "zop": "завтрак / обед / полдник"}
group_id = call.message.chat.id group_id = call.message.chat.id
if du.group_exists(group_id): if du.group_exists(group_id):
group_id = du.get_group_id(group_id) group_id = du.get_group_id(group_id)
if db.main_group_exists(group_id): if db.main_group_exists(group_id):
user_id = call.from_user.id user_id = call.from_user.id
if du.user_exists(user_id): if du.user_exists(user_id):
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
else: else:
du.add_user(user_id) du.add_user(user_id)
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
if db.check_group_inproc(group_id): if db.check_group_inproc(group_id):
if not dn.check_user(group_id, user_id): if not dn.check_user(group_id, user_id):
flag = True flag = True
else: else:
await call.message.answer(f"{call.from_user.first_name}, вы уже сделали заказ! Отмените его с " await call.message.answer(f"{call.from_user.first_name}, вы уже сделали заказ! Отмените его с "
f"помощью кнопки отмены!") f"помощью кнопки отмены!")
flag = False flag = False
else: else:
dn.created_group(group_id) dn.created_group(group_id)
db.add_group(group_id) db.add_group(group_id)
flag = True flag = True
if flag: if flag:
for i in str(call.data): for i in str(call.data):
db.upd_stat_group(group_id, i, 1) db.upd_stat_group(group_id, i, 1)
dg.upd_stat_user(group_id, user_id, i, 1) dg.upd_stat_user(group_id, user_id, i, 1)
ids = db.get_spec_group(group_id) ids = db.get_spec_group(group_id)
msg1 = await bot.send_message(chat_id=str(du.get_first_group_id(ids)), msg1 = await bot.send_message(chat_id=str(du.get_first_group_id(ids)),
text=f'[{call.from_user.first_name}](tg://user?id={call.from_user.id}) >> ' text=f'[{call.from_user.first_name}](tg://user?id={call.from_user.id}) >> '
f'*{et[call.data].title()}*', parse_mode=types.ParseMode.MARKDOWN) f'*{et[call.data].title()}*', parse_mode=types.ParseMode.MARKDOWN)
msg2 = await call.message.answer(text=f'{call.from_user.first_name}, всё принято, вы заказили - ' msg2 = await call.message.answer(text=f'{call.from_user.first_name}, всё принято, вы заказили - '
f'*{"и".join(et[call.data].split("/"))}*', f'*{"и".join(et[call.data].split("/"))}*',
parse_mode=types.ParseMode.MARKDOWN) parse_mode=types.ParseMode.MARKDOWN)
dn.add_user(group_id, user_id, msg2.message_id, msg1.message_id, str(call.data)) dn.add_user(group_id, user_id, msg2.message_id, msg1.message_id, str(call.data))
else: else:
await call.message.answer('Вы должны через личные сообщения зарегистрировать этот чат у бота вместе с ' await call.message.answer('Вы должны через личные сообщения зарегистрировать этот чат у бота вместе с '
'дополнительным для отправки списка!') 'дополнительным для отправки списка!')
else: else:
await call.message.answer('Вы должны через личные сообщения зарегистрировать этот чат у бота вместе с ' await call.message.answer('Вы должны через личные сообщения зарегистрировать этот чат у бота вместе с '
'дополнительным для отправки списка!') 'дополнительным для отправки списка!')
@dp.callback_query_handler(text=["cnl"]) @dp.callback_query_handler(text=["cnl"])
async def cnl(call: types.CallbackQuery): async def cnl(call: types.CallbackQuery):
user_id = du.get_user_id(call.from_user.id) user_id = du.get_user_id(call.from_user.id)
if db.user_tranzit_exists(user_id): if db.user_tranzit_exists(user_id):
db.delete_tranzit(user_id) db.delete_tranzit(user_id)
await call.message.answer('<b>Заявка удалена!</b> Если ещё захотите связать группы, то зановой вызовите ' await call.message.answer('<b>Заявка удалена!</b> Если ещё захотите связать группы, то зановой вызовите '
'команду <b>/add_group</b>', types.ParseMode.HTML) 'команду <b>/add_group</b>', types.ParseMode.HTML)
else: else:
await call.message.answer('У вас нет оставленных заявок!') await call.message.answer('У вас нет оставленных заявок!')
@dp.callback_query_handler(text=["del"]) @dp.callback_query_handler(text=["del"])
async def ver_del(call: types.CallbackQuery): async def ver_del(call: types.CallbackQuery):
user_id = du.get_user_id(call.from_user.id) user_id = du.get_user_id(call.from_user.id)
if db.user_main_exists(user_id): if db.user_main_exists(user_id):
ids = db.get_main_group(user_id) ids = db.get_main_group(user_id)
db.delete_main_group(user_id) db.delete_main_group(user_id)
dg.delete_group(str(ids)) dg.delete_group(str(ids))
db.del_group(str(ids)) db.del_group(str(ids))
await call.message.answer('Всё успешно удалено!') await call.message.answer('Всё успешно удалено!')
else: else:
await call.message.answer("У вас нет связки групп для их удаления!") await call.message.answer("У вас нет связки групп для их удаления!")
@dp.callback_query_handler(text=["cancel"]) @dp.callback_query_handler(text=["cancel"])
async def cancel(call: types.CallbackQuery): async def cancel(call: types.CallbackQuery):
group_id = call.message.chat.id group_id = call.message.chat.id
if du.group_exists(group_id): if du.group_exists(group_id):
main_group_id = group_id main_group_id = group_id
group_id = du.get_group_id(group_id) group_id = du.get_group_id(group_id)
if db.main_group_exists(group_id): if db.main_group_exists(group_id):
user_id = call.from_user.id user_id = call.from_user.id
if du.user_exists(user_id): if du.user_exists(user_id):
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
else: else:
du.add_user(user_id) du.add_user(user_id)
user_id = du.get_user_id(user_id) user_id = du.get_user_id(user_id)
if db.check_group_inproc(group_id): if db.check_group_inproc(group_id):
if dn.check_user(group_id, user_id): if dn.check_user(group_id, user_id):
ord_id = dn.get_ord_id(group_id, user_id) ord_id = dn.get_ord_id(group_id, user_id)
for i in ord_id: for i in ord_id:
db.upd_stat_group(group_id, i, -1) db.upd_stat_group(group_id, i, -1)
dg.upd_stat_user(group_id, user_id, i, -1) dg.upd_stat_user(group_id, user_id, i, -1)
ids = db.get_spec_group(group_id) ids = db.get_spec_group(group_id)
set_group_id = str(du.get_first_group_id(ids)) set_group_id = str(du.get_first_group_id(ids))
main, sett = dn.get_message(group_id, user_id) main, sett = dn.get_message(group_id, user_id)
await bot.delete_message(chat_id=main_group_id, message_id=main) await bot.delete_message(chat_id=main_group_id, message_id=main)
await bot.delete_message(chat_id=set_group_id, message_id=sett) await bot.delete_message(chat_id=set_group_id, message_id=sett)
dn.del_user(group_id, user_id) dn.del_user(group_id, user_id)
await call.message.answer(f"{call.from_user.first_name}, ваш заказ отменён!") await call.message.answer(f"{call.from_user.first_name}, ваш заказ отменён!")
else: else:
await call.message.answer(f"{call.from_user.first_name}, у вас нет заказа!") await call.message.answer(f"{call.from_user.first_name}, у вас нет заказа!")
else: else:
await call.message.answer(f"{call.from_user.first_name}, у вас нет заказа!") await call.message.answer(f"{call.from_user.first_name}, у вас нет заказа!")
else: else:
await call.message.answer('Вы ещё не можете использовать бота, завершите процедуру регистрации!') await call.message.answer('Вы ещё не можете использовать бота, завершите процедуру регистрации!')
else: else:
await call.message.answer('Вы должны через личные сообщения зарегистрировать этот чат у бота вместе с ' await call.message.answer('Вы должны через личные сообщения зарегистрировать этот чат у бота вместе с '
'дополнительным для отправки списка!') 'дополнительным для отправки списка!')
# ТРЕКЕР НОВЫХ ПОЛЬЗОВАТЕЛЕЙ # ТРЕКЕР НОВЫХ ПОЛЬЗОВАТЕЛЕЙ
@dp.message_handler(content_types=["text", "audio", "document", "photo", "sticker", "video", @dp.message_handler(content_types=["text", "audio", "document", "photo", "sticker", "video",
"video_note", "voice", "location", "contact"]) "video_note", "voice", "location", "contact"])
async def check(message: types.Message): async def check(message: types.Message):
if 0 > int(message.chat.id): if 0 > int(message.chat.id):
if du.user_exists(message.from_user.id): if du.user_exists(message.from_user.id):
user_id = du.get_user_id(message.from_user.id) user_id = du.get_user_id(message.from_user.id)
if db.user_tranzit_exists(user_id) and len(message.text) == 4: if db.user_tranzit_exists(user_id) and len(message.text) == 4:
cods = db.get_cods(user_id) cods = db.get_cods(user_id)
flag = False flag = False
if not du.group_exists(message.chat.id): if not du.group_exists(message.chat.id):
if message.text == str(cods[0]): if message.text == str(cods[0]):
du.add_group(message.chat.id) du.add_group(message.chat.id)
group_id = du.get_group_id(message.chat.id) group_id = du.get_group_id(message.chat.id)
db.add_main_group(user_id, group_id) db.add_main_group(user_id, group_id)
flag = True flag = True
elif message.text == str(cods[1]): elif message.text == str(cods[1]):
du.add_group(message.chat.id) du.add_group(message.chat.id)
group_id = du.get_group_id(message.chat.id) group_id = du.get_group_id(message.chat.id)
db.add_set_group(user_id, group_id) db.add_set_group(user_id, group_id)
flag = True flag = True
else: else:
group_id = du.get_group_id(message.chat.id) group_id = du.get_group_id(message.chat.id)
if not db.main_group_exists(group_id): if not db.main_group_exists(group_id):
if message.text == str(cods[0]): if message.text == str(cods[0]):
db.add_main_group(user_id, group_id) db.add_main_group(user_id, group_id)
flag = True flag = True
elif message.text == str(cods[1]): elif message.text == str(cods[1]):
db.add_set_group(user_id, group_id) db.add_set_group(user_id, group_id)
flag = True flag = True
if flag and db.user_tranzit(user_id): if flag and db.user_tranzit(user_id):
ids = db.get_groups_ids(user_id) ids = db.get_groups_ids(user_id)
db.add_active_groups(user_id, ids[0], ids[1]) db.add_active_groups(user_id, ids[0], ids[1])
db.delete_tranzit(user_id) db.delete_tranzit(user_id)
dg.created_group(ids[0]) dg.created_group(ids[0])
dg.add_user(ids[0], user_id, message.from_user.first_name) dg.add_user(ids[0], user_id, message.from_user.first_name)
else: else:
if du.group_exists(message.chat.id): if du.group_exists(message.chat.id):
group_id = du.get_group_id(message.chat.id) group_id = du.get_group_id(message.chat.id)
if db.main_group_exists(group_id): if db.main_group_exists(group_id):
dg.update_name(user_id, message.from_user.first_name, group_id) dg.update_name(user_id, message.from_user.first_name, group_id)
else: else:
if du.group_exists(message.chat.id): if du.group_exists(message.chat.id):
group_id = du.get_group_id(message.chat.id) group_id = du.get_group_id(message.chat.id)
if db.main_group_exists(group_id): if db.main_group_exists(group_id):
du.add_user(message.from_user.id) du.add_user(message.from_user.id)
user_id = du.get_user_id(message.from_user.id) user_id = du.get_user_id(message.from_user.id)
dg.add_user(group_id, user_id, message.from_user.first_name) dg.add_user(group_id, user_id, message.from_user.first_name)
# ОТДЕЛЕНИЕ ДНЯ В ГРУППЕ # ОТДЕЛЕНИЕ ДНЯ В ГРУППЕ
async def time(wait_for): async def time(wait_for):
while True: while True:
await asyncio.sleep(wait_for) await asyncio.sleep(wait_for)
hour = dt.datetime.now().hour hour = dt.datetime.now().hour
date = dt.datetime.now().day date = dt.datetime.now().day
wkd = dt.datetime.today().weekday() wkd = dt.datetime.today().weekday()
data = db.get_day() data = db.get_day()
if date != data and hour == 6: if date != data and hour == 6:
for ids in db.get_spec_groups(): for ids in db.get_spec_groups():
group = du.get_first_group_id(ids[0]) group = du.get_first_group_id(ids[0])
if wkd == 5: if wkd == 5:
dat = ".".join(reversed(str(dt.date.today() + dt.timedelta(days=2)).split("-"))) dat = ".".join(reversed(str(dt.date.today() + dt.timedelta(days=2)).split("-")))
await bot.send_message(chat_id=group, text=f'<b>Питание на {dat}</b>', await bot.send_message(chat_id=group, text=f'<b>Питание на {dat}</b>',
parse_mode=types.ParseMode.HTML) parse_mode=types.ParseMode.HTML)
elif wkd != 6: elif wkd != 6:
dat = ".".join(reversed(str(dt.date.today() + dt.timedelta(days=1)).split("-"))) dat = ".".join(reversed(str(dt.date.today() + dt.timedelta(days=1)).split("-")))
await bot.send_message(chat_id=group, text=f'<b>Питание на {dat}</b>', await bot.send_message(chat_id=group, text=f'<b>Питание на {dat}</b>',
parse_mode=types.ParseMode.HTML) parse_mode=types.ParseMode.HTML)
db.update_day(date) db.update_day(date)
db.reset_proc() db.reset_proc()
os.remove('../db/now.db') os.remove('../db/now.db')
fle = open('../db/now.db', 'w+') fle = open('../db/now.db', 'w+')
fle.close() fle.close()
if __name__ == '__main__': if __name__ == '__main__':
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.create_task(time(600)) # ПРОВЕРКА КАЖДЫЕ 10 МИНУТ loop.create_task(time(600)) # ПРОВЕРКА КАЖДЫЕ 10 МИНУТ
executor.start_polling(dp, skip_updates=True) executor.start_polling(dp, skip_updates=True)

View file

@ -1,52 +1,69 @@
import requests as req import ast
import datetime as dt import operator
import openpyxl, os, re import requests as req
import datetime as dt
import openpyxl, os, re
def eating(day):
if day == 0: _OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}
wkd = dt.datetime.today().weekday()
else:
wkd = (dt.date.today() + dt.timedelta(days=1)).weekday() def arithmetic(text):
"""Значение ячейки вида «150» или «150+30»: только числа и + - * /, без eval."""
if wkd != 6: def calc(node):
flag = False if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
try: return node.value
if day == 0: if isinstance(node, ast.BinOp) and type(node.op) in _OPS:
eat = f"https://www.school01.ru/food/{str(dt.date.today())}-sm.xlsx" return _OPS[type(node.op)](calc(node.left), calc(node.right))
else: if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
eat = f"https://www.school01.ru/food/{str(dt.date.today() + dt.timedelta(days=1))}-sm.xlsx" return -calc(node.operand)
raise ValueError(f"недопустимое значение в ячейке: {text!r}")
file, xls = open("school_food.xlsx", "wb"), req.get(eat) return calc(ast.parse(text, mode="eval").body)
file.write(xls.content)
file.close()
def eating(day):
excel_file = openpyxl.load_workbook('school_food.xlsx') if day == 0:
employees_sheet = excel_file[excel_file.sheetnames[0]] wkd = dt.datetime.today().weekday()
else:
food = {employees_sheet["A4"].value: [], employees_sheet["A12"].value: []} wkd = (dt.date.today() + dt.timedelta(days=1)).weekday()
food_info = [[employees_sheet["A4"].value, 4, 8], [employees_sheet["A12"].value, 12, 9]]
if wkd != 6:
flag = True flag = False
try:
if employees_sheet["A22"].value is not None: if day == 0:
food[employees_sheet["A22"].value] = [] eat = f"https://www.school01.ru/food/{str(dt.date.today())}-sm.xlsx"
food_info.append([employees_sheet["A22"].value, 21, 2]) else:
eat = f"https://www.school01.ru/food/{str(dt.date.today() + dt.timedelta(days=1))}-sm.xlsx"
for j in food_info:
for i in range(j[2]): file, xls = open("school_food.xlsx", "wb"), req.get(eat)
if employees_sheet[f"D{j[1] + i}"].value is None: file.write(xls.content)
continue file.close()
gramm = str(int(eval(str(employees_sheet[f"E{j[1] + i}"].value).strip()))) excel_file = openpyxl.load_workbook('school_food.xlsx')
kkal = str(eval("".join(str(employees_sheet[f"J{j[1] + i}"].value).split()).replace(",", ".", 1))) employees_sheet = excel_file[excel_file.sheetnames[0]]
food[j[0]].append([employees_sheet[f"D{j[1] + i}"].value, [gramm, kkal]]) food = {employees_sheet["A4"].value: [], employees_sheet["A12"].value: []}
food_info = [[employees_sheet["A4"].value, 4, 8], [employees_sheet["A12"].value, 12, 9]]
os.remove("school_food.xlsx")
flag = True
return [True, food]
except Exception as e: if employees_sheet["A22"].value is not None:
return [False, flag, wkd, repr(e)] food[employees_sheet["A22"].value] = []
else: food_info.append([employees_sheet["A22"].value, 21, 2])
return [False, False, wkd]
for j in food_info:
for i in range(j[2]):
if employees_sheet[f"D{j[1] + i}"].value is None:
continue
gramm = str(int(arithmetic(str(employees_sheet[f"E{j[1] + i}"].value).strip())))
kkal = str(arithmetic("".join(str(employees_sheet[f"J{j[1] + i}"].value).split()).replace(",", ".", 1)))
food[j[0]].append([employees_sheet[f"D{j[1] + i}"].value, [gramm, kkal]])
os.remove("school_food.xlsx")
return [True, food]
except Exception as e:
return [False, flag, wkd, repr(e)]
else:
return [False, False, wkd]