update
This commit is contained in:
parent
ee58d3a7a4
commit
9937d4e250
2
.env.example
Normal file
2
.env.example
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
BOT_TOKEN=xxxxxxxxxxxxxxxxxxxxx
|
||||
WEBHOOK_URL=https://ваш-домен.ru
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.env
|
||||
__pycache__/
|
||||
*.pyc
|
||||
logs/
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
23
README.md
23
README.md
|
|
@ -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
28
bot.py
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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®ionCode=31&districtCode={districtCode}&locality=Белгород&localityCode={localityCode}'
|
||||
url_avar = f'{main_url}4®ionCode=31&districtCode={districtCode}&locality=Белгород&localityCode={localityCode}&Street=-'
|
||||
url_plan = f'{main_url}2®ionCode=31&districtCode={districtCode}&locality=Белгород&localityCode={localityCode}'
|
||||
token = ''
|
||||
URL_APP = ''
|
||||
|
|
@ -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
29
main.py
|
|
@ -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®ionCode=31&districtCode={DISTRICT_CODE}&locality=Белгород&localityCode={LOCALITY_CODE}'
|
||||
URL_VNEREGLAMENT = f'{MAIN_URL}3®ionCode=31&districtCode={DISTRICT_CODE}&locality=Белгород&localityCode={LOCALITY_CODE}'
|
||||
URL_AVAR = f'{MAIN_URL}4®ionCode=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()
|
||||
|
|
@ -14,3 +14,4 @@ pytz==2024.1
|
|||
requests==2.32.3
|
||||
urllib3==2.2.1
|
||||
yarl==1.9.4
|
||||
python-dotenv==1.0.1
|
||||
Loading…
Reference in New Issue
Block a user