diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9670794 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +BOT_TOKEN=xxxxxxxxxxxxxxxxxxxxx +WEBHOOK_URL=https://ваш-домен.ru \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..67d6207 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +__pycache__/ +*.pyc +logs/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b281290..eae11c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index 8ba424f..fe53eb0 100644 --- a/README.md +++ b/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) diff --git a/bot.py b/bot.py index 610cb1d..7959c86 100644 --- a/bot.py +++ b/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() diff --git a/config.py b/config.py deleted file mode 100644 index c339d38..0000000 --- a/config.py +++ /dev/null @@ -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 = '' diff --git a/docker-compose.yml b/docker-compose.yml index cd3c74a..233736e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 \ No newline at end of file + - PYTHONUNBUFFERED=1 \ No newline at end of file diff --git a/main.py b/main.py index 8ed56c8..6df44f4 100644 --- a/main.py +++ b/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 + return dt_str.replace('T', ' ') 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') @@ -96,7 +94,4 @@ def start_parser(): get_plan() get_vnereglament() get_avar() - logger.info("Парсер отработал") - -if __name__ == '__main__': - start_parser() \ No newline at end of file + logger.info("Парсер отработал") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 349d982..a4825f1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,3 +14,4 @@ pytz==2024.1 requests==2.32.3 urllib3==2.2.1 yarl==1.9.4 +python-dotenv==1.0.1 \ No newline at end of file