mirror of
https://github.com/EDeev/npr_meteobot.git
synced 2026-10-08 04:59:33 +03:00
Compare commits
No commits in common. "69830f156f0f33e350c284cd63455490372462a4" and "6c60731a2cb0bdb886e4662b94232a0dec7671d7" have entirely different histories.
69830f156f
...
6c60731a2c
12 changed files with 476 additions and 481 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
* text=auto eol=lf
|
|
||||||
*.db binary
|
|
||||||
21
.github/workflows/ci.yml
vendored
21
.github/workflows/ci.yml
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
||||||
- name: Ruff (синтаксис и ошибки)
|
|
||||||
run: ruff check --select E9,F .
|
|
||||||
- name: База создаётся из schema.sql
|
|
||||||
run: |
|
|
||||||
python -c "import sql; db = sql.SQLighter('/tmp/t.db'); db.add_user('1'); assert db.user_exists('1')"
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -1,5 +0,0 @@
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
.venv/
|
|
||||||
.env
|
|
||||||
*.db
|
|
||||||
41
README.en.md
41
README.en.md
|
|
@ -1,41 +0,0 @@
|
||||||
# NPR MeteoBot
|
|
||||||
|
|
||||||
[Русский](README.md) · **English**
|
|
||||||
|
|
||||||
[](https://github.com/EDeev/npr_meteobot/actions/workflows/ci.yml)
|
|
||||||
|
|
||||||
A Telegram bot for schoolchildren in the Norilsk industrial area: it sends class cancellations due to frost
|
|
||||||
and wind ("aktirovka") for the user's shift and district and shows the weather forecast.
|
|
||||||
|
|
||||||
**Status:** school project (2022), completed, archived
|
|
||||||
|
|
||||||
**Stack:** Python 3.10 · aiogram 2 · SQLite · Selenium + BeautifulSoup (city administration site, Gismeteo) · pymorphy2
|
|
||||||
|
|
||||||
- Subscription to cancellations by shift (1 or 2) and district: Norilsk, Talnakh, Oganer, Kayerkan
|
|
||||||
- Regular checks of the administration site and broadcasts to subscribers
|
|
||||||
- Weather forecast for the day and up to 10 days ahead (Gismeteo data)
|
|
||||||
|
|
||||||
## Running
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pip install -r requirements.txt
|
|
||||||
BOT_TOKEN=token_from_BotFather python bot.py
|
|
||||||
```
|
|
||||||
|
|
||||||
The `sub.db` database is created on first run from `schema.sql`. Cancellations need Chrome: the
|
|
||||||
administration site is read with Selenium.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
School project (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>
|
|
||||||
140
README.md
140
README.md
|
|
@ -1,41 +1,139 @@
|
||||||
# NPR MeteoBot
|
# NPR MeteoBot
|
||||||
|
|
||||||
**Русский** · [English](README.en.md)
|
Автоматизированная система мониторинга метеорологических условий и уведомлений об актировках учебных занятий для Норильского промышленного района (НПР).
|
||||||
|
|
||||||
[](https://github.com/EDeev/npr_meteobot/actions/workflows/ci.yml)
|
## 📋 Описание проекта
|
||||||
|
|
||||||
Telegram-бот для школьников Норильского промышленного района: присылает актировки (отмену занятий
|
NPR MeteoBot представляет собой интеллектуальную систему на базе Telegram-бота, предназначенную для:
|
||||||
из-за мороза и ветра) для своей смены и района и показывает прогноз погоды.
|
- Мониторинга погодных условий в четырех районах НПР
|
||||||
|
- Автоматического отслеживания актировок учебных занятий
|
||||||
|
- Персонализированных уведомлений пользователей
|
||||||
|
- Предоставления детализированных метеорологических данных
|
||||||
|
|
||||||
**Статус:** школьный проект (2022), завершён, в архиве
|
### Основные возможности:
|
||||||
|
- **Персонализация**: Настройка по смене обучения и району проживания
|
||||||
|
- **Автоматизация**: Регулярный мониторинг официальных источников
|
||||||
|
- **Уведомления**: Система подписок с гибкими настройками
|
||||||
|
- **Актуальность**: Интеграция с Gismeteo и официальным сайтом города
|
||||||
|
|
||||||
**Стек:** Python 3.10 · aiogram 2 · SQLite · Selenium + BeautifulSoup (сайт администрации, Gismeteo) · pymorphy2
|
## 🏗️ Архитектура системы
|
||||||
|
|
||||||
- Подписка на актировки по смене (1 или 2) и району: Норильск, Талнах, Оганер, Кайеркан
|
### Компоненты:
|
||||||
- Регулярная проверка сайта администрации и рассылка подписчикам
|
- `bot.py` - Основной модуль Telegram-бота с обработкой команд
|
||||||
- Прогноз погоды на день и на 10 дней вперёд (данные Gismeteo)
|
- `sql.py` - Модуль работы с базой данных SQLite
|
||||||
|
- `info.py` - Парсинг данных с внешних источников
|
||||||
|
- `config.py` - Конфигурационные параметры системы
|
||||||
|
|
||||||
## Запуск
|
### Структура базы данных:
|
||||||
|
- **users**: Профили пользователей с настройками
|
||||||
|
- **weather**: Кэш метеорологических данных
|
||||||
|
- **parts**: Данные об актировках по сменам
|
||||||
|
- **data**: Системная информация
|
||||||
|
|
||||||
```bash
|
## 🛠️ Технологический стек
|
||||||
pip install -r requirements.txt
|
|
||||||
BOT_TOKEN=токен_от_BotFather python bot.py
|
### Backend:
|
||||||
|
- **Python 3.7+** - Основной язык разработки
|
||||||
|
- **aiogram 2.x** - Фреймворк для Telegram Bot API
|
||||||
|
- **SQLite** - Локальная база данных
|
||||||
|
- **asyncio** - Асинхронное программирование
|
||||||
|
|
||||||
|
### Парсинг данных:
|
||||||
|
- **Selenium WebDriver** - Автоматизация браузера
|
||||||
|
- **BeautifulSoup4** - HTML-парсинг
|
||||||
|
- **requests** - HTTP-запросы
|
||||||
|
|
||||||
|
### Обработка данных:
|
||||||
|
- **pymorphy2** - Морфологический анализ русского языка
|
||||||
|
|
||||||
|
## 🚀 Установка и настройка
|
||||||
|
|
||||||
|
### Предварительные требования:
|
||||||
|
```python
|
||||||
|
# Установка ChromeDriver для Selenium
|
||||||
|
# Ubuntu/Debian:
|
||||||
|
sudo apt-get install chromium-chromedriver
|
||||||
|
|
||||||
|
# Windows: загрузить с https://chromedriver.chromium.org/
|
||||||
```
|
```
|
||||||
|
|
||||||
База `sub.db` создаётся при первом запуске из `schema.sql`. Для актировок нужен Chrome: сайт
|
### Установка зависимостей:
|
||||||
администрации читается через Selenium.
|
```python
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
## Лицензия
|
### Конфигурация:
|
||||||
|
1. Получите токен бота в [@BotFather](https://t.me/BotFather)
|
||||||
|
2. Обновите `API_TOKEN` в `config.py`
|
||||||
|
3. Создайте структуру базы данных SQLite
|
||||||
|
|
||||||
Школьный проект (2022). Код открыт для изучения, отдельной лицензии нет.
|
### Запуск:
|
||||||
|
```python
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
## Автор
|
## 📊 Функциональные возможности
|
||||||
|
|
||||||
**Деев Егор Викторович** — [GitHub](https://github.com/EDeev) · [Telegram](https://t.me/DeevEgor) · [egor@deev.space](mailto:egor@deev.space)
|
### Команды бота:
|
||||||
|
- `/start` - Инициализация и настройка профиля
|
||||||
|
- `/town` - Изменение города и смены обучения
|
||||||
|
- `/acta` - Просмотр текущих актировок
|
||||||
|
- `/weather` - Детализированный прогноз погоды
|
||||||
|
- `/storm` - Штормовые предупреждения
|
||||||
|
- `/letter` - Управление подпиской на уведомления
|
||||||
|
- `/notice` - Настройка типа уведомлений
|
||||||
|
|
||||||
|
### Автоматические процессы:
|
||||||
|
- **timer()** - Мониторинг актировок (каждую минуту)
|
||||||
|
- **wind()** - Обновление погодных данных (каждые 20 минут)
|
||||||
|
- **clear()** - Очистка устаревших данных (каждые 5 минут)
|
||||||
|
|
||||||
|
## 🎯 Целевые районы
|
||||||
|
|
||||||
|
Система обслуживает четыре района НПР:
|
||||||
|
- **Центральный район** (Норильск)
|
||||||
|
- **Район Талнах**
|
||||||
|
- **Район Оганер**
|
||||||
|
- **Район Кайеркан**
|
||||||
|
|
||||||
|
## 🔧 Конфигурационные параметры
|
||||||
|
|
||||||
|
### Временные интервалы мониторинга:
|
||||||
|
- Прогноз погоды: 1, 4, 7, 10, 13, 16, 19, 22 часа
|
||||||
|
- Проверка актировок: ежеминутно
|
||||||
|
- Обновление погоды: каждые 20 минут
|
||||||
|
|
||||||
|
### Источники данных:
|
||||||
|
- **Актировки**: [norilsk-city.ru/meteo](https://www.norilsk-city.ru/meteo/)
|
||||||
|
- **Погода**: [gismeteo.ru](https://www.gismeteo.ru/)
|
||||||
|
|
||||||
|
## 📈 Масштабируемость
|
||||||
|
|
||||||
|
Система спроектирована для:
|
||||||
|
- Обработки 17,000+ активных пользователей
|
||||||
|
- Асинхронной обработки запросов
|
||||||
|
- Отказоустойчивой работы с внешними API
|
||||||
|
|
||||||
|
## ⚠️ Ограничения
|
||||||
|
|
||||||
|
- Зависимость от доступности внешних источников данных
|
||||||
|
- Требует настройки ChromeDriver для корректного парсинга
|
||||||
|
- Локальная SQLite база данных (не подходит для кластерного развертывания)
|
||||||
|
|
||||||
|
## 📝 Лицензия
|
||||||
|
|
||||||
|
Проект разработан для образовательных и некоммерческих целей.
|
||||||
|
|
||||||
|
## 👥 Автор
|
||||||
|
|
||||||
|
**Деев Егор Викторович** - 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>⭐ Если проект оказался полезным, поставьте звёздочку на GitHub!</sub>
|
<sub>Разработано с использованием современных технологий асинхронного программирования и автоматизации веб-парсинга.</sub>
|
||||||
<p><sub>Сделано с ❤️ — <a href="https://deev.space">deev.space</a></sub></p>
|
<p><sub>Создано с ❤️ от вашего дорогого - deev.space ©</sub></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
2
bot.py
2
bot.py
|
|
@ -157,7 +157,7 @@ async def act(message: types.Message):
|
||||||
|
|
||||||
|
|
||||||
@dp.message_handler(commands=['weather'])
|
@dp.message_handler(commands=['weather'])
|
||||||
async def weather_command(message: types.Message):
|
async def storm(message: types.Message):
|
||||||
text = message.text.split()
|
text = message.text.split()
|
||||||
try:
|
try:
|
||||||
day = int(text[1])
|
day = int(text[1])
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import os
|
API_TOKEN = 'token from BotFather' # ОСНОВНОЙ
|
||||||
|
# API_TOKEN = 'token from BotFather' # ТЕСТЫ
|
||||||
# Токен бота от @BotFather — из переменной окружения BOT_TOKEN
|
|
||||||
API_TOKEN = os.environ.get('BOT_TOKEN', '')
|
|
||||||
|
|
||||||
CITY = {'Центральный район': [1, 'Норильск'], 'район Талнах': [2, 'Талнах'],
|
CITY = {'Центральный район': [1, 'Норильск'], 'район Талнах': [2, 'Талнах'],
|
||||||
'ж/о Оганер': [3, 'Оганер'], 'район Кайеркан': [4, 'Кайеркан']}
|
'ж/о Оганер': [3, 'Оганер'], 'район Кайеркан': [4, 'Кайеркан']}
|
||||||
|
|
|
||||||
4
info.py
4
info.py
|
|
@ -22,7 +22,7 @@ def act():
|
||||||
acta[acta.index(lst)] = [config.CITY[lst[0]], int(lst[1].split("-")[1].split()[0])]
|
acta[acta.index(lst)] = [config.CITY[lst[0]], int(lst[1].split("-")[1].split()[0])]
|
||||||
|
|
||||||
return sorted(acta), count
|
return sorted(acta), count
|
||||||
except Exception:
|
except Exception as e:
|
||||||
return [], ""
|
return [], ""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ def weather(moment):
|
||||||
dire = soup.find_all("div", class_="direction")
|
dire = soup.find_all("div", class_="direction")
|
||||||
prec = soup.find_all("div", class_="item-unit")
|
prec = soup.find_all("div", class_="item-unit")
|
||||||
|
|
||||||
weather = {}
|
k, s, weather = 0, 0, {}
|
||||||
|
|
||||||
for i in range(8):
|
for i in range(8):
|
||||||
par = wind[i].get_text().split()[0].split('-')
|
par = wind[i].get_text().split()[0].split('-')
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
aiogram==2.25.2
|
|
||||||
pymorphy2==0.9.1
|
|
||||||
pymorphy2-dicts-ru==2.4.417127.4579844
|
|
||||||
selenium==4.15.2
|
|
||||||
beautifulsoup4==4.12.3
|
|
||||||
requests==2.31.0
|
|
||||||
15
schema.sql
15
schema.sql
|
|
@ -1,15 +0,0 @@
|
||||||
-- Схема базы бота и служебные строки, которые он ожидает (подписчиков нет)
|
|
||||||
CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, id_user VARCHAR (255) NOT NULL,
|
|
||||||
rating INTEGER (4) NOT NULL DEFAULT (0), status BOOLEAN NOT NULL DEFAULT (False),
|
|
||||||
send BOOLEAN NOT NULL DEFAULT (True), part INTEGER (1) NOT NULL, city INTEGER (1) NOT NULL);
|
|
||||||
CREATE TABLE IF NOT EXISTS parts (part INTEGER PRIMARY KEY AUTOINCREMENT, nor VARCHAR (3), tal VARCHAR (3),
|
|
||||||
oga VARCHAR (3), kae VARCHAR (3));
|
|
||||||
CREATE TABLE IF NOT EXISTS weather (id STRING, "1" VARCHAR, "4" VARCHAR, "7" VARCHAR, "10" VARCHAR,
|
|
||||||
"13" VARCHAR, "16" VARCHAR, "19" VARCHAR, "22" VARCHAR);
|
|
||||||
CREATE TABLE IF NOT EXISTS data (id INTEGER PRIMARY KEY AUTOINCREMENT, date DATE NOT NULL, part INTEGER (2) NOT NULL,
|
|
||||||
weath STRING);
|
|
||||||
|
|
||||||
INSERT INTO parts (part, nor, tal, oga, kae) VALUES (1, '11', '11', '11', '11'), (2, '11', '11', '11', '11');
|
|
||||||
INSERT INTO weather (id) VALUES (' '), ('tomorrow'), ('3-day'), ('4-day'), ('5-day'), ('6-day'), ('7-day'),
|
|
||||||
('8-day'), ('9-day'), ('10-day');
|
|
||||||
INSERT INTO data (id, date, part) VALUES (1, '2022-02-24', 1);
|
|
||||||
11
sql.py
11
sql.py
|
|
@ -1,22 +1,11 @@
|
||||||
import os
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
SCHEMA = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'schema.sql')
|
|
||||||
|
|
||||||
|
|
||||||
class SQLighter:
|
class SQLighter:
|
||||||
def __init__(self, database):
|
def __init__(self, database):
|
||||||
"""Подключаемся к БД и сохраняем курсор соединения"""
|
"""Подключаемся к БД и сохраняем курсор соединения"""
|
||||||
self.connection = sqlite3.connect(database)
|
self.connection = sqlite3.connect(database)
|
||||||
self.cursor = self.connection.cursor()
|
self.cursor = self.connection.cursor()
|
||||||
self._create_if_empty()
|
|
||||||
|
|
||||||
def _create_if_empty(self):
|
|
||||||
"""Новая база: создаём таблицы и служебные строки из schema.sql"""
|
|
||||||
exists = self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'users'").fetchone()
|
|
||||||
if not exists:
|
|
||||||
with open(SCHEMA, encoding='utf-8') as f:
|
|
||||||
self.connection.executescript(f.read())
|
|
||||||
|
|
||||||
def user_exists(self, id_user):
|
def user_exists(self, id_user):
|
||||||
"""Проверяем, есть ли уже пользователь в базе"""
|
"""Проверяем, есть ли уже пользователь в базе"""
|
||||||
|
|
|
||||||
BIN
sub.db
Normal file
BIN
sub.db
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue