fix: update parsing logic for half-hours and improve disconnection status handling
This commit is contained in:
@@ -1,10 +1,3 @@
|
|||||||
# Раздел "ТВАРИ, ЧИТАТЕ ВНИМАТЕЛЬНО!"
|
|
||||||
|
|
||||||
# Когда обновят сайт -- расскоментровать 641-646 и закоментировать 647.
|
|
||||||
# Поменять комменты 671 и 672 местами.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -89,7 +82,7 @@ def get_day_statistics(day_blocks: List[Dict]) -> Dict[str, int]:
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
def parse_html(html: str) -> List[Dict]:
|
def parse_html(html: str) -> List[Dict]:
|
||||||
"""Парсит HTML с графиком отключений (нова логіка)"""
|
"""Парсит HTML с графиком отключений (логика от 15.11.2024)"""
|
||||||
soup = BeautifulSoup(html, "html.parser")
|
soup = BeautifulSoup(html, "html.parser")
|
||||||
cells = soup.select(".disconnection-detailed-table-cell.cell")
|
cells = soup.select(".disconnection-detailed-table-cell.cell")
|
||||||
|
|
||||||
@@ -103,56 +96,71 @@ def parse_html(html: str) -> List[Dict]:
|
|||||||
|
|
||||||
cell_classes = cell.get('class', [])
|
cell_classes = cell.get('class', [])
|
||||||
|
|
||||||
# Перевіряємо чи вся година вимкнена
|
# ПРоверка статуса отключения на весь час
|
||||||
full_hour_off = 'has_disconnection' in cell_classes and 'full_hour' in cell_classes
|
full_hour_off = 'has_disconnection' in cell_classes and 'full_hour' in cell_classes
|
||||||
no_disconnection = 'no_disconnection' in cell_classes
|
no_disconnection_cell = 'no_disconnection' in cell_classes
|
||||||
|
|
||||||
hour_block = cell.select_one(".hour_block")
|
hour_block = cell.select_one(".hour_block")
|
||||||
if not hour_block:
|
if not hour_block:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Визначаємо статус та підтвердження для всієї години
|
# Проверка подтверждённости отключения для всего часа
|
||||||
confirmed = None
|
cell_confirmed = None
|
||||||
if 'confirm_1' in cell_classes:
|
if 'confirm_1' in cell_classes:
|
||||||
confirmed = True
|
cell_confirmed = True
|
||||||
elif 'confirm_0' in cell_classes:
|
elif 'confirm_0' in cell_classes:
|
||||||
confirmed = False
|
cell_confirmed = False
|
||||||
|
|
||||||
# Шукаємо часткові відключення через div.fill
|
# Проверка половин часа
|
||||||
fill_divs = hour_block.select(".fill")
|
left = hour_block.select_one(".half.left")
|
||||||
|
right = hour_block.select_one(".half.right")
|
||||||
|
|
||||||
first_half_data = {"status": "on", "queue": None, "confirmed": None}
|
def parse_half(half, is_full_hour_off: bool) -> Dict:
|
||||||
second_half_data = {"status": "on", "queue": None, "confirmed": None}
|
"""Парсит половину часа"""
|
||||||
|
if not half:
|
||||||
|
return {"status": "on", "queue": None, "confirmed": None}
|
||||||
|
|
||||||
|
half_classes = half.get('class', [])
|
||||||
|
|
||||||
|
# Если вся ячейка full_hour - используем статус ячейки
|
||||||
|
if is_full_hour_off:
|
||||||
|
return {"status": "off", "queue": None, "confirmed": cell_confirmed}
|
||||||
|
|
||||||
|
# Определяем статус половины
|
||||||
|
if 'has_disconnection' in half_classes:
|
||||||
|
status = "off"
|
||||||
|
elif 'no_disconnection' in half_classes:
|
||||||
|
status = "on"
|
||||||
|
else:
|
||||||
|
status = "on" # По умолчанию считаем включенным
|
||||||
|
|
||||||
|
# Если выключено - ищем подробности
|
||||||
|
queue = None
|
||||||
|
confirmed = None
|
||||||
|
|
||||||
|
if status == "off":
|
||||||
|
disconnection_div = half.select_one(".disconnection")
|
||||||
|
if disconnection_div:
|
||||||
|
# Ищем номер черги в title
|
||||||
|
if disconnection_div.has_attr("title"):
|
||||||
|
title = disconnection_div["title"]
|
||||||
|
if "Номер черги" in title or "Номер черги:" in title:
|
||||||
|
try:
|
||||||
|
queue = title.split(":")[-1].strip()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Определяем подтверждение
|
||||||
|
disc_classes = disconnection_div.get('class', [])
|
||||||
|
if 'disconnection_confirm_1' in disc_classes:
|
||||||
|
confirmed = True
|
||||||
|
elif 'disconnection_confirm_0' in disc_classes:
|
||||||
|
confirmed = False
|
||||||
|
|
||||||
|
return {"status": status, "queue": queue, "confirmed": confirmed}
|
||||||
|
|
||||||
if full_hour_off:
|
first_half_data = parse_half(left, full_hour_off)
|
||||||
# Вся година вимкнена
|
second_half_data = parse_half(right, full_hour_off)
|
||||||
first_half_data = {"status": "off", "queue": None, "confirmed": confirmed}
|
|
||||||
second_half_data = {"status": "off", "queue": None, "confirmed": confirmed}
|
|
||||||
elif no_disconnection and not fill_divs:
|
|
||||||
# Вся година ввімкнена
|
|
||||||
first_half_data = {"status": "on", "queue": None, "confirmed": None}
|
|
||||||
second_half_data = {"status": "on", "queue": None, "confirmed": None}
|
|
||||||
else:
|
|
||||||
# Перевіряємо часткові відключення
|
|
||||||
for fill_div in fill_divs:
|
|
||||||
title = fill_div.get('title', '')
|
|
||||||
style = fill_div.get('style', '')
|
|
||||||
fill_classes = fill_div.get('class', [])
|
|
||||||
|
|
||||||
# Визначаємо підтвердження
|
|
||||||
fill_confirmed = None
|
|
||||||
if 'confirmed' in fill_classes:
|
|
||||||
fill_confirmed = True
|
|
||||||
elif 'possible' in fill_classes:
|
|
||||||
fill_confirmed = False
|
|
||||||
|
|
||||||
# Парсимо стиль для визначення часу
|
|
||||||
# --start:0;--size:50 означає 0-30 хвилин (перша половина)
|
|
||||||
# --start:50;--size:50 означає 30-60 хвилин (друга половина)
|
|
||||||
if '--start:0' in style or title.endswith(':00—' + f'{current_hour:02d}:30'):
|
|
||||||
first_half_data = {"status": "off", "queue": None, "confirmed": fill_confirmed}
|
|
||||||
elif '--start:50' in style or (f'{current_hour:02d}:30' in title and title.endswith(':00')):
|
|
||||||
second_half_data = {"status": "off", "queue": None, "confirmed": fill_confirmed}
|
|
||||||
|
|
||||||
schedule.append({
|
schedule.append({
|
||||||
"hour": current_hour,
|
"hour": current_hour,
|
||||||
@@ -168,6 +176,7 @@ def parse_html(html: str) -> List[Dict]:
|
|||||||
|
|
||||||
return schedule
|
return schedule
|
||||||
|
|
||||||
|
|
||||||
def get_voe_html(city_id: int, street_id: int, house_id: int) -> str:
|
def get_voe_html(city_id: int, street_id: int, house_id: int) -> str:
|
||||||
"""Получает HTML с сайта VOE"""
|
"""Получает HTML с сайта VOE"""
|
||||||
url = "https://www.voe.com.ua/disconnection/detailed?ajax_form=1&_wrapper_format=drupal_ajax"
|
url = "https://www.voe.com.ua/disconnection/detailed?ajax_form=1&_wrapper_format=drupal_ajax"
|
||||||
@@ -582,7 +591,12 @@ async def cmd_info(message: Message):
|
|||||||
|
|
||||||
await message.answer(
|
await message.answer(
|
||||||
"<b>ℹ️ Про бота</b>\n\n"
|
"<b>ℹ️ Про бота</b>\n\n"
|
||||||
"🚀 <b>Версія:</b> 2.1 (Оптимізована)\n"
|
"🚀 <b>Версія:</b> 2.2 (Стабільна)\n\n"
|
||||||
|
"📝 <b>Реліз-ноути:</b>\n"
|
||||||
|
"├ 15.11.2024: Адаптація під оновлену логіку сайту VOE\n"
|
||||||
|
"├ Виправлено парсинг half.left та half.right\n"
|
||||||
|
"├ Покращено визначення підтвердження відключень\n"
|
||||||
|
"└ Оптимізовано обробку статусу для всієї години\n\n"
|
||||||
"⚡ <b>Функціональність:</b>\n"
|
"⚡ <b>Функціональність:</b>\n"
|
||||||
"├ Моніторинг графіку 24/7\n"
|
"├ Моніторинг графіку 24/7\n"
|
||||||
"├ Сповіщення за 5 хвилин\n"
|
"├ Сповіщення за 5 хвилин\n"
|
||||||
@@ -645,21 +659,18 @@ async def cmd_tomorrow(message: Message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# """Закоментировал до лучших времен"""
|
await message.answer("⏳ Завантаження графіку завтра...")
|
||||||
# await message.answer("⏳ Завантаження графіку завтра...")
|
html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID)
|
||||||
# html = get_voe_html(VOE_CITY_ID, VOE_STREET_ID, VOE_HOUSE_ID)
|
schedule = parse_html(html)
|
||||||
# schedule = parse_html(html)
|
text = format_single_day_schedule(schedule, 1)
|
||||||
# text = format_single_day_schedule(schedule, 1)
|
await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard())
|
||||||
# await message.answer(text, parse_mode="HTML", reply_markup=get_main_keyboard())
|
# await message.answer("❌ Функція тимчасово недоступна. Чекаємо на оновлення сайту", parse_mode="HTML", reply_markup=get_main_keyboard())
|
||||||
await message.answer("❌ Функція тимчасово недоступна. Чекаємо на оновлення сайту", parse_mode="HTML", reply_markup=get_main_keyboard())
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
await message.answer(
|
await message.answer(
|
||||||
f"❌ <b>Помилка:</b> {str(e)}",
|
f"❌ <b>Помилка:</b> {str(e)}",
|
||||||
parse_mode="HTML",
|
parse_mode="HTML",
|
||||||
reply_markup=get_main_keyboard()
|
reply_markup=get_main_keyboard()
|
||||||
)
|
)
|
||||||
# Когда обновят сайт -- расскоментровать 641-646 и закоментировать 647.
|
|
||||||
# Поменять комменты 671 и 672 местами.
|
|
||||||
|
|
||||||
|
|
||||||
@dp.message(Command("check"))
|
@dp.message(Command("check"))
|
||||||
@@ -675,8 +686,7 @@ async def cmd_check(message: Message):
|
|||||||
new_schedule = parse_html(html)
|
new_schedule = parse_html(html)
|
||||||
|
|
||||||
prefix = "✅ <b>Знайдено зміни!</b>\n\n" if schedules_differ(last_schedule, new_schedule) else "✓ <b>Графік без змін</b>\n\n"
|
prefix = "✅ <b>Знайдено зміни!</b>\n\n" if schedules_differ(last_schedule, new_schedule) else "✓ <b>Графік без змін</b>\n\n"
|
||||||
# result = prefix + format_schedule_message(new_schedule, days_to_show=2)
|
result = prefix + format_schedule_message(new_schedule, days_to_show=2)
|
||||||
result = prefix + format_schedule_message(new_schedule, days_to_show=1)
|
|
||||||
|
|
||||||
await message.answer(result, parse_mode="HTML", reply_markup=get_main_keyboard())
|
await message.answer(result, parse_mode="HTML", reply_markup=get_main_keyboard())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -703,9 +713,9 @@ async def handle_text(message: Message):
|
|||||||
elif text == "📅 Сьогодні":
|
elif text == "📅 Сьогодні":
|
||||||
await cmd_today(message)
|
await cmd_today(message)
|
||||||
|
|
||||||
# # Кнопка "Завтра"
|
# Кнопка "Завтра"
|
||||||
# elif text == "📅 Завтра":
|
elif text == "📅 Завтра":
|
||||||
# await cmd_tomorrow(message)
|
await cmd_tomorrow(message)
|
||||||
|
|
||||||
# Кнопка "Оновити"
|
# Кнопка "Оновити"
|
||||||
elif text == "🔄 Оновити":
|
elif text == "🔄 Оновити":
|
||||||
@@ -733,7 +743,7 @@ async def handle_text(message: Message):
|
|||||||
async def main():
|
async def main():
|
||||||
"""Главная функция"""
|
"""Главная функция"""
|
||||||
logger.info("=" * 50)
|
logger.info("=" * 50)
|
||||||
logger.info("🚀 ЗАПУСК БОТА V2.1 (ОПТИМІЗОВАНА ВЕРСІЯ)")
|
logger.info("ЗАПУСК БОТА V2.2 (stable 2.2, 15.11.2025)")
|
||||||
logger.info("=" * 50)
|
logger.info("=" * 50)
|
||||||
|
|
||||||
if not TELEGRAM_TOKEN or TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "YOUR_TOKEN_HERE")
|
if not TELEGRAM_TOKEN or TELEGRAM_TOKEN = os.getenv("TELEGRAM_TOKEN", "YOUR_TOKEN_HERE")
|
||||||
@@ -744,7 +754,7 @@ async def main():
|
|||||||
logger.error("❌ ALLOWED_CHAT_IDS не конфігуровані! Напишіть ID в .env файл")
|
logger.error("❌ ALLOWED_CHAT_IDS не конфігуровані! Напишіть ID в .env файл")
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"🔌 Дозволені чати: {ALLOWED_CHAT_IDS}")
|
logger.info(f"📌 Allowed chat ids: {ALLOWED_CHAT_IDS}")
|
||||||
logger.info(f"⏱ Інтервал перевірки: {CHECK_INTERVAL} сек")
|
logger.info(f"⏱ Інтервал перевірки: {CHECK_INTERVAL} сек")
|
||||||
logger.info("=" * 50)
|
logger.info("=" * 50)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user