This commit is contained in:
Andrey Sazonov 2025-10-02 21:11:22 +03:00
parent ee58d3a7a4
commit 9937d4e250
9 changed files with 53 additions and 68 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
BOT_TOKEN=xxxxxxxxxxxxxxxxxxxxx
WEBHOOK_URL=https://ваш-домен.ru

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.env
__pycache__/
*.pyc
logs/

View File

@ -8,14 +8,11 @@ ENV PYTHONFAULTHANDLER=1 \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_DEFAULT_TIMEOUT=100
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && \
pip install --no-cache-dir --disable-pip-version-check -r requirements.txt

View File

@ -32,10 +32,10 @@ Telegram-бот, который автоматически парсит и от
### 1. Клонируйте репозиторий
### 2. Настройте конфигурацию
Отредактируйте файл `config.py`:
Скопируйте `.env.example` в `.env` и отредактируйте.
```python
token = 'ВАШ_TELEGRAM_BOT_TOKEN'
URL_APP = 'https://ваш-домен.ru'
BOT_TOKEN=xxxxxxxxxxxxxxxxxxxxx
WEBHOOK_URL=https://ваш-домен.ru
```
### 3. Соберите и запустите через Docker
@ -48,25 +48,12 @@ docker run -d \
mrsk_bot
```
- Или через docker compose
> Убедитесь, что ваш сервер доступен по `URL_APP` и настроен reverse proxy (например, Nginx) на порт 5000.
---
## 📁 Структура проекта
```
.
├── bot.py # Основной файл бота
├── main.py # Парсер данных с сайта МРСК
├── config.py # Конфигурация (токен, URL, API-эндпоинты)
├── requirements.txt # Зависимости Python
├── Dockerfile # Для сборки образа
├── logs/ # Директория для логов (бот.log, parser.log, users.log)
└── README.md
```
---
## Обратная связь
Если у вас есть вопросы, предложения или вы нашли баг — пишите: [@pikusQQ](https://t.me/pikusQQ)

28
bot.py
View File

@ -1,13 +1,18 @@
import asyncio
import logging
import os
from pathlib import Path
import pytz
from datetime import datetime
from pathlib import Path
from aiogram import Bot, Dispatcher, executor, types
from aiogram.dispatcher.filters import Text
import config
from dotenv import load_dotenv
from main import start_parser
load_dotenv()
logging.basicConfig(
level=logging.INFO,
filename="logs/bot.log",
@ -16,11 +21,19 @@ logging.basicConfig(
)
logger = logging.getLogger(__name__)
BOT_TOKEN = os.getenv("BOT_TOKEN")
WEBHOOK_URL = os.getenv("WEBHOOK_URL")
if not BOT_TOKEN:
raise ValueError("❌ Переменная BOT_TOKEN не задана в .env")
if not WEBHOOK_URL:
raise ValueError("❌ Переменная WEBHOOK_URL не задана в .env")
TZ = pytz.timezone('Europe/Moscow')
USER_LOG = Path("logs/users.log")
UPDATE_INTERVAL = 300 # 5 минут в секундах
UPDATE_INTERVAL = 300 # 5 минут
bot = Bot(token=config.token)
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher(bot)
async def periodic_update():
@ -40,7 +53,6 @@ async def cmd_start(message: types.Message):
keyboard.add(*buttons)
await message.answer('Выберите тип отключений:', reply_markup=keyboard)
# Утилита для отправки файла
async def send_file_content(message: types.Message, file_path: Path, empty_msg: str, log_msg: str):
await message.answer("Обновляю...")
@ -101,12 +113,10 @@ async def info(message: types.Message):
async def fallback(message: types.Message):
await message.answer("Нажмите /start для начала работы.")
# Webhook handlers
async def on_startup(dp):
await bot.set_webhook(config.URL_APP)
# Запуск фоновой задачи после старта
await bot.set_webhook(WEBHOOK_URL)
asyncio.create_task(periodic_update())
logger.info("Webhook установлен. Фоновое обновление запущено.")
logger.info(f"Webhook установлен на {WEBHOOK_URL}. Фоновое обновление запущено.")
async def on_shutdown(dp):
await bot.delete_webhook()

View File

@ -1,8 +0,0 @@
main_url = 'https://www.mrsk-1.ru/customers/customer-service/power-outage/ajax.php?request='
districtCode = '0ECC8970-2702-4C14-9E4D-956606EFFEE3'
localityCode = '02E9C019-AB4D-4FA0-928E-D6C0A41DC256'
url_vnereglament = f'{main_url}3&regionCode=31&districtCode={districtCode}&locality=Белгород&localityCode={localityCode}'
url_avar = f'{main_url}4&regionCode=31&districtCode={districtCode}&locality=Белгород&localityCode={localityCode}&Street=-'
url_plan = f'{main_url}2&regionCode=31&districtCode={districtCode}&locality=Белгород&localityCode={localityCode}'
token = ''
URL_APP = ''

View File

@ -1,17 +1,14 @@
version: '3.8'
services:
mrsk-bot:
power-outage-bot:
build: .
container_name: mrsk-bot
container_name: power-outage-bot
restart: unless-stopped
ports:
- "5002:5000"
- "5000:5000"
volumes:
- ./logs:/app/logs
- ./.env:/app/.env:ro
environment:
- PYTHONUNBUFFERED=1
networks:
- bot-network
networks:
bot-network:
driver: bridge

29
main.py
View File

@ -1,3 +1,5 @@
# main.py
import json
import logging
import requests
@ -5,8 +7,6 @@ from pathlib import Path
from urllib3.exceptions import InsecureRequestWarning
import urllib3
import config
urllib3.disable_warnings(InsecureRequestWarning)
logging.basicConfig(
@ -15,30 +15,28 @@ logging.basicConfig(
format="%(asctime)s - %(module)s - %(levelname)s - %(funcName)s: %(lineno)d - %(message)s",
datefmt='%H:%M:%S',
)
logger = logging.getLogger(__name__)
MAIN_URL = 'https://www.mrsk-1.ru/customers/customer-service/power-outage/ajax.php?request='
DISTRICT_CODE = '0ECC8970-2702-4C14-9E4D-956606EFFEE3'
LOCALITY_CODE = '02E9C019-AB4D-4FA0-928E-D6C0A41DC256'
URL_PLAN = f'{MAIN_URL}2&regionCode=31&districtCode={DISTRICT_CODE}&locality=Белгород&localityCode={LOCALITY_CODE}'
URL_VNEREGLAMENT = f'{MAIN_URL}3&regionCode=31&districtCode={DISTRICT_CODE}&locality=Белгород&localityCode={LOCALITY_CODE}'
URL_AVAR = f'{MAIN_URL}4&regionCode=31&districtCode={DISTRICT_CODE}&locality=Белгород&localityCode={LOCALITY_CODE}&Street=-'
PLAN_FILE = Path("plan.txt")
VN_FILE = Path("vnereglament.txt")
AVAR_FILE = Path("avar.txt")
def clean_street(street: str) -> str:
"""Очищает и форматирует строку с адресами."""
street = street.replace('Белгород г; ', '').replace('\n\n', '\n').strip()
lines = [line.strip() for line in street.split('\n') if line.strip()]
return '\n'.join(lines)
def format_time(dt_str: str) -> str:
"""Преобразует '2025-10-02T09:00:00''02.10.2025 09:00'."""
try:
return dt_str.replace('T', ' ')
except:
return dt_str
def parse_and_save(data, file_path: Path, mode: str):
"""
mode: 'plan', 'vnereglament', 'avar'
"""
if not data or data == 0:
file_path.write_text("[Нет отключений]", encoding='utf-8')
return
@ -61,7 +59,7 @@ def parse_and_save(data, file_path: Path, mode: str):
def get_plan():
try:
r = requests.get(url=config.url_plan, verify=False, timeout=10)
r = requests.get(url=URL_PLAN, verify=False, timeout=10)
r.raise_for_status()
data = r.json()
parse_and_save(data, PLAN_FILE, 'plan')
@ -72,7 +70,7 @@ def get_plan():
def get_vnereglament():
try:
r = requests.get(url=config.url_vnereglament, verify=False, timeout=10)
r = requests.get(url=URL_VNEREGLAMENT, verify=False, timeout=10)
r.raise_for_status()
data = r.json()
parse_and_save(data, VN_FILE, 'vnereglament')
@ -83,7 +81,7 @@ def get_vnereglament():
def get_avar():
try:
r = requests.get(url=config.url_avar, verify=False, timeout=10)
r = requests.get(url=URL_AVAR, verify=False, timeout=10)
r.raise_for_status()
data = r.json()
parse_and_save(data, AVAR_FILE, 'avar')
@ -97,6 +95,3 @@ def start_parser():
get_vnereglament()
get_avar()
logger.info("Парсер отработал")
if __name__ == '__main__':
start_parser()

View File

@ -14,3 +14,4 @@ pytz==2024.1
requests==2.32.3
urllib3==2.2.1
yarl==1.9.4
python-dotenv==1.0.1