Add main keyboard and enhance schedule formatting in DTEK notification bot
FOR HISTORY
This commit is contained in:
+272
-39
@@ -5,7 +5,8 @@ from bs4 import BeautifulSoup
|
||||
from datetime import datetime, timedelta
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.filters import Command
|
||||
from aiogram.types import Message
|
||||
from aiogram.types import Message, ReplyKeyboardMarkup, KeyboardButton
|
||||
from aiogram.utils.keyboard import ReplyKeyboardBuilder
|
||||
import logging
|
||||
|
||||
# Настройки
|
||||
@@ -127,10 +128,24 @@ def get_voe_html(city_id, street_id, house_id):
|
||||
return insert_html
|
||||
|
||||
|
||||
def get_main_keyboard():
|
||||
"""Создает главную клавиатуру с кнопками"""
|
||||
builder = ReplyKeyboardBuilder()
|
||||
builder.row(
|
||||
KeyboardButton(text="📊 Графік"),
|
||||
KeyboardButton(text="🔄 Перевірити")
|
||||
)
|
||||
builder.row(
|
||||
KeyboardButton(text="📅 Сьогодні"),
|
||||
KeyboardButton(text="📅 Завтра")
|
||||
)
|
||||
return builder.as_markup(resize_keyboard=True)
|
||||
|
||||
|
||||
def format_schedule_message(schedule, days_to_show=2):
|
||||
"""Форматирует расписание для отправки в Telegram"""
|
||||
message = f"⚡️ <b>Графік відключень світла</b>\n"
|
||||
message += f"🕐 Оновлено: {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
"""Форматирует расписание для отправки в Telegram (компактная версия)"""
|
||||
message = f"⚡️ <b>Графік відключень</b>\n"
|
||||
message += f"🕐 {datetime.now().strftime('%d.%m.%Y %H:%M')}\n\n"
|
||||
|
||||
start_date = datetime.now()
|
||||
|
||||
@@ -143,45 +158,135 @@ def format_schedule_message(schedule, days_to_show=2):
|
||||
day_name = "Сьогодні" if day == 0 else "Завтра"
|
||||
|
||||
message += f"📅 <b>{day_name} ({date_str})</b>\n"
|
||||
message += "━━━━━━━━━━━━━━━━━━━━\n"
|
||||
|
||||
# Группируем последовательные отключения
|
||||
disconnections = []
|
||||
i = 0
|
||||
while i < len(day_blocks):
|
||||
block = day_blocks[i]
|
||||
first = block["first_half"]
|
||||
second = block["second_half"]
|
||||
|
||||
# Проверяем статус отключения
|
||||
first_off = first["status"] == "off"
|
||||
second_off = second["status"] == "off"
|
||||
|
||||
if first_off or second_off:
|
||||
start_time = f"{block['hour']:02d}:00" if first_off else f"{block['hour']:02d}:30"
|
||||
|
||||
# Ищем конец периода отключения
|
||||
j = i
|
||||
while j < len(day_blocks):
|
||||
b = day_blocks[j]
|
||||
f = b["first_half"]
|
||||
s = b["second_half"]
|
||||
|
||||
# Если обе половины не отключены, прерываем
|
||||
if f["status"] != "off" and s["status"] != "off":
|
||||
break
|
||||
|
||||
j += 1
|
||||
|
||||
# Определяем конечное время
|
||||
if j > 0:
|
||||
prev_block = day_blocks[j-1]
|
||||
if prev_block["second_half"]["status"] == "off":
|
||||
end_time = f"{prev_block['hour']+1:02d}:00"
|
||||
else:
|
||||
end_time = f"{prev_block['hour']:02d}:30"
|
||||
else:
|
||||
end_time = f"{block['hour']+1:02d}:00"
|
||||
|
||||
# Получаем информацию о черге и подтверждении
|
||||
queue = first["queue"] or second["queue"]
|
||||
confirmed = first["confirmed"] if first_off else second["confirmed"]
|
||||
|
||||
icon = "🔴" if confirmed is True else "🟠" if confirmed is False else "🔴"
|
||||
|
||||
disconnections.append({
|
||||
"time": f"{start_time}-{end_time}",
|
||||
"icon": icon,
|
||||
"queue": queue
|
||||
})
|
||||
|
||||
i = j if j > i else i + 1
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# Выводим сгруппированные отключения
|
||||
if disconnections:
|
||||
for d in disconnections:
|
||||
queue_info = f" Ч{d['queue']}" if d['queue'] else ""
|
||||
message += f"{d['icon']} {d['time']}{queue_info}\n"
|
||||
else:
|
||||
message += "🟢 Відключень немає\n"
|
||||
|
||||
message += "\n"
|
||||
|
||||
message += "<i>🔴 підтверджено • 🟠 можливо</i>"
|
||||
|
||||
return message
|
||||
|
||||
|
||||
def format_single_day_schedule(schedule, day_num):
|
||||
"""Форматирует расписание для одного дня (еще более компактно)"""
|
||||
start_date = datetime.now()
|
||||
date_str = (start_date + timedelta(days=day_num)).strftime('%d.%m')
|
||||
day_name = "Сьогодні" if day_num == 0 else "Завтра"
|
||||
|
||||
message = f"⚡️ <b>{day_name} ({date_str})</b>\n\n"
|
||||
|
||||
day_blocks = [b for b in schedule if b["day"] == day_num]
|
||||
if not day_blocks:
|
||||
return message + "❓ Немає даних"
|
||||
|
||||
# Создаем почасовой график одной строкой
|
||||
hourly = []
|
||||
for block in day_blocks:
|
||||
first = block["first_half"]
|
||||
second = block["second_half"]
|
||||
|
||||
def get_icon(half_data):
|
||||
def get_short_icon(half_data):
|
||||
if half_data["status"] == "off":
|
||||
if half_data["confirmed"] is True:
|
||||
return "🔴" # Точно відключено
|
||||
elif half_data["confirmed"] is False:
|
||||
return "🟠" # Можливо відключено
|
||||
return "🔴"
|
||||
return "●" if half_data["confirmed"] is True else "○"
|
||||
elif half_data["status"] == "on":
|
||||
return "🟢"
|
||||
return "⚪️"
|
||||
return "·"
|
||||
return "?"
|
||||
|
||||
first_icon = get_icon(first)
|
||||
second_icon = get_icon(second)
|
||||
hour_str = f"{block['hour']:02d}"
|
||||
first_icon = get_short_icon(first)
|
||||
second_icon = get_short_icon(second)
|
||||
hourly.append(f"{hour_str}|{first_icon}{second_icon}")
|
||||
|
||||
# Форматируем время
|
||||
hour_start = f"{block['hour']:02d}:00"
|
||||
hour_mid = f"{block['hour']:02d}:30"
|
||||
hour_end = f"{block['hour']+1:02d}:00"
|
||||
# Выводим по 6 часов в строке
|
||||
for i in range(0, len(hourly), 6):
|
||||
chunk = hourly[i:i+6]
|
||||
message += " ".join(chunk) + "\n"
|
||||
|
||||
# Информация о черге
|
||||
queue_info = ""
|
||||
if first["queue"] or second["queue"]:
|
||||
queue = first["queue"] or second["queue"]
|
||||
queue_info = f" | Черга {queue}"
|
||||
message += "\n<i>● підтв • ○ можл • · світло</i>\n\n"
|
||||
|
||||
message += f"{hour_start}-{hour_mid} {first_icon} {hour_mid}-{hour_end} {second_icon}{queue_info}\n"
|
||||
# Добавляем список отключений
|
||||
disconnections = []
|
||||
for block in day_blocks:
|
||||
first = block["first_half"]
|
||||
second = block["second_half"]
|
||||
|
||||
message += "\n"
|
||||
if first["status"] == "off":
|
||||
icon = "🔴" if first["confirmed"] is True else "🟠"
|
||||
time = f"{block['hour']:02d}:00-30"
|
||||
queue = f" Ч{first['queue']}" if first['queue'] else ""
|
||||
disconnections.append(f"{icon} {time}{queue}")
|
||||
|
||||
message += "🟢 - Світло є\n"
|
||||
message += "🔴 - Відключення підтверджено\n"
|
||||
message += "🟠 - Можливе відключення\n"
|
||||
message += "⚪️ - Інформація відсутня"
|
||||
if second["status"] == "off":
|
||||
icon = "🔴" if second["confirmed"] is True else "🟠"
|
||||
time = f"{block['hour']:02d}:30-{block['hour']+1:02d}:00"
|
||||
queue = f" Ч{second['queue']}" if second['queue'] else ""
|
||||
disconnections.append(f"{icon} {time}{queue}")
|
||||
|
||||
if disconnections:
|
||||
message += "<b>Відключення:</b>\n" + "\n".join(disconnections)
|
||||
else:
|
||||
message += "🟢 <b>Відключень немає</b>"
|
||||
|
||||
return message
|
||||
|
||||
@@ -245,6 +350,100 @@ async def check_schedule():
|
||||
await send_to_all_users(f"❌ <b>Помилка!</b>\n\nНе вдалося отримати графік: {str(e)}")
|
||||
|
||||
|
||||
async def check_upcoming_disconnections():
|
||||
"""Проверяет предстоящие отключения и включения, отправляет предупреждения"""
|
||||
global last_schedule
|
||||
|
||||
if last_schedule is None:
|
||||
return
|
||||
|
||||
now = datetime.now()
|
||||
current_time = now.time()
|
||||
|
||||
# Проверяем только текущий день
|
||||
today_blocks = [b for b in last_schedule if b["day"] == 0]
|
||||
|
||||
for block in today_blocks:
|
||||
hour = block["hour"]
|
||||
|
||||
# Проверяем первую половину часа (XX:00 - XX:30)
|
||||
first_half_start = now.replace(hour=hour, minute=0, second=0, microsecond=0)
|
||||
time_until_first = (first_half_start - now).total_seconds() / 60
|
||||
|
||||
# Если до события осталось 5 минут (с погрешностью ±1 минута)
|
||||
if 4 <= time_until_first <= 6:
|
||||
first = block["first_half"]
|
||||
|
||||
# ОТКЛЮЧЕНИЕ
|
||||
if first["status"] == "off":
|
||||
icon = "🔴" if first["confirmed"] is True else "🟠"
|
||||
status_text = "підтверджено" if first["confirmed"] is True else "можливе"
|
||||
queue_info = f" (Черга {first['queue']})" if first["queue"] else ""
|
||||
|
||||
warning = (
|
||||
f"⚠️ <b>УВАГА! ВІДКЛЮЧЕННЯ</b>\n\n"
|
||||
f"Через 5 хвилин відключення {icon}\n"
|
||||
f"Час: {hour:02d}:00 - {hour:02d}:30\n"
|
||||
f"Статус: {status_text}{queue_info}"
|
||||
)
|
||||
await send_to_all_users(warning)
|
||||
logger.info(f"Отправлено предупреждение о отключении в {hour:02d}:00")
|
||||
|
||||
# ВКЛЮЧЕНИЕ
|
||||
elif first["status"] == "on":
|
||||
# Проверяем, было ли отключение до этого
|
||||
prev_block = today_blocks[today_blocks.index(block) - 1] if today_blocks.index(block) > 0 else None
|
||||
was_off = False
|
||||
|
||||
if prev_block:
|
||||
was_off = prev_block["second_half"]["status"] == "off"
|
||||
|
||||
if was_off:
|
||||
warning = (
|
||||
f"✅ <b>УВАГА! ВКЛЮЧЕННЯ</b>\n\n"
|
||||
f"Через 5 хвилин світло має з'явитися 🟢\n"
|
||||
f"Час: {hour:02d}:00"
|
||||
)
|
||||
await send_to_all_users(warning)
|
||||
logger.info(f"Отправлено предупреждение о включении в {hour:02d}:00")
|
||||
|
||||
# Проверяем вторую половину часа (XX:30 - XX+1:00)
|
||||
second_half_start = now.replace(hour=hour, minute=30, second=0, microsecond=0)
|
||||
time_until_second = (second_half_start - now).total_seconds() / 60
|
||||
|
||||
if 4 <= time_until_second <= 6:
|
||||
second = block["second_half"]
|
||||
|
||||
# ОТКЛЮЧЕНИЕ
|
||||
if second["status"] == "off":
|
||||
icon = "🔴" if second["confirmed"] is True else "🟠"
|
||||
status_text = "підтверджено" if second["confirmed"] is True else "можливе"
|
||||
queue_info = f" (Черга {second['queue']})" if second["queue"] else ""
|
||||
|
||||
warning = (
|
||||
f"⚠️ <b>УВАГА! ВІДКЛЮЧЕННЯ</b>\n\n"
|
||||
f"Через 5 хвилин відключення {icon}\n"
|
||||
f"Час: {hour:02d}:30 - {hour+1:02d}:00\n"
|
||||
f"Статус: {status_text}{queue_info}"
|
||||
)
|
||||
await send_to_all_users(warning)
|
||||
logger.info(f"Отправлено предупреждение о отключении в {hour:02d}:30")
|
||||
|
||||
# ВКЛЮЧЕНИЕ
|
||||
elif second["status"] == "on":
|
||||
# Проверяем, было ли отключение в первой половине
|
||||
was_off = block["first_half"]["status"] == "off"
|
||||
|
||||
if was_off:
|
||||
warning = (
|
||||
f"✅ <b>УВАГА! ВКЛЮЧЕННЯ</b>\n\n"
|
||||
f"Через 5 хвилин світло має з'явитися 🟢\n"
|
||||
f"Час: {hour:02d}:30"
|
||||
)
|
||||
await send_to_all_users(warning)
|
||||
logger.info(f"Отправлено предупреждение о включении в {hour:02d}:30")
|
||||
|
||||
|
||||
async def monitoring_loop():
|
||||
"""Основной цикл мониторинга"""
|
||||
# Первая проверка сразу при запуске
|
||||
@@ -254,6 +453,7 @@ async def monitoring_loop():
|
||||
while True:
|
||||
await asyncio.sleep(CHECK_INTERVAL)
|
||||
await check_schedule()
|
||||
await check_upcoming_disconnections()
|
||||
|
||||
|
||||
@dp.message(Command("start"))
|
||||
@@ -265,17 +465,17 @@ async def cmd_start(message: Message):
|
||||
|
||||
await message.answer(
|
||||
"👋 <b>Вітаю!</b>\n\n"
|
||||
"Я буду відстежувати зміни в графіку відключень світла.\n\n"
|
||||
"Доступні команди:\n"
|
||||
"/schedule - Показати поточний графік\n"
|
||||
"/check - Перевірити графік зараз",
|
||||
parse_mode="HTML"
|
||||
"Я відстежую зміни в графіку відключень світла та попереджаю за 5 хвилин до відключення.\n\n"
|
||||
"Використовуйте кнопки нижче 👇",
|
||||
parse_mode="HTML",
|
||||
reply_markup=get_main_keyboard()
|
||||
)
|
||||
|
||||
|
||||
@dp.message(lambda message: message.text == "📊 Графік")
|
||||
@dp.message(Command("schedule"))
|
||||
async def cmd_schedule(message: Message):
|
||||
"""Показывает текущий график"""
|
||||
"""Показывает текущий график (2 дня)"""
|
||||
if message.chat.id not in ALLOWED_CHAT_IDS:
|
||||
await message.answer("❌ У вас немає доступу до цього бота.")
|
||||
return
|
||||
@@ -284,11 +484,44 @@ async def cmd_schedule(message: Message):
|
||||
html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID)
|
||||
schedule = parse_html(html)
|
||||
text = format_schedule_message(schedule, days_to_show=2)
|
||||
await message.answer(text, parse_mode="HTML")
|
||||
await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard())
|
||||
except Exception as e:
|
||||
await message.answer(f"❌ Помилка: {str(e)}")
|
||||
await message.answer(f"❌ Помилка: {str(e)}", reply_markup=get_main_keyboard())
|
||||
|
||||
|
||||
@dp.message(lambda message: message.text == "📅 Сьогодні")
|
||||
async def cmd_today(message: Message):
|
||||
"""Показывает график на сегодня"""
|
||||
if message.chat.id not in ALLOWED_CHAT_IDS:
|
||||
await message.answer("❌ У вас немає доступу до цього бота.")
|
||||
return
|
||||
|
||||
try:
|
||||
html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID)
|
||||
schedule = parse_html(html)
|
||||
text = format_single_day_schedule(schedule, 0)
|
||||
await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard())
|
||||
except Exception as e:
|
||||
await message.answer(f"❌ Помилка: {str(e)}", reply_markup=get_main_keyboard())
|
||||
|
||||
|
||||
@dp.message(lambda message: message.text == "📅 Завтра")
|
||||
async def cmd_tomorrow(message: Message):
|
||||
"""Показывает график на завтра"""
|
||||
if message.chat.id not in ALLOWED_CHAT_IDS:
|
||||
await message.answer("❌ У вас немає доступу до цього бота.")
|
||||
return
|
||||
|
||||
try:
|
||||
html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID)
|
||||
schedule = parse_html(html)
|
||||
text = format_single_day_schedule(schedule, 1)
|
||||
await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard())
|
||||
except Exception as e:
|
||||
await message.answer(f"❌ Помилка: {str(e)}", reply_markup=get_main_keyboard())
|
||||
|
||||
|
||||
@dp.message(lambda message: message.text == "🔄 Перевірити")
|
||||
@dp.message(Command("check"))
|
||||
async def cmd_check(message: Message):
|
||||
"""Принудительная проверка графика"""
|
||||
@@ -296,7 +529,7 @@ async def cmd_check(message: Message):
|
||||
await message.answer("❌ У вас немає доступу до цього бота.")
|
||||
return
|
||||
|
||||
await message.answer("🔄 Перевіряю графік...")
|
||||
await message.answer("🔄 Перевіряю графік...", reply_markup=get_main_keyboard())
|
||||
await check_schedule()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user