Загрузка данных


import json
import re
import sys
import time
from datetime import datetime

import requests
from bs4 import BeautifulSoup

# User config
BOT_TOKEN = "7423335967:AAE_9_i3AoqwlfpYE_h9qiMs2A44K4V7Qtw"
CHAT_ID = "5408718450"
CHECK_INTERVAL_SECONDS = 10

TARGET_URL = (
    "https://atlasbus.by/"
    "%D0%9C%D0%B0%D1%80%D1%88%D1%80%D1%83%D1%82%D1%8B/"
    "%D0%92%D0%B8%D0%BB%D0%B5%D0%B9%D0%BA%D0%B0/"
    "%D0%9C%D0%B8%D0%BD%D1%81%D0%BA"
    "?date=2026-08-31&passengers=1&from=c620181&to=c625144"
)

TARGET_API_URL = (
    "https://atlasbus.by/api/search"
    "?from_id=c620181&to_id=c625144"
    "&calendar_width=30&date=2026-08-31&passengers=1&operatorId="
)

TARGET_DEPARTURE_TIMES = ["11:10","10:40"]


def send_telegram_message(text: str) -> None:
    url = f"https://api.telegram.org/bot{BOT_TOKEN}/sendMessage"
    payload = {
        "chat_id": CHAT_ID,
        "text": text,
        "disable_web_page_preview": True,
    }
    try:
        response = requests.post(url, data=payload, timeout=20)
        response.raise_for_status()
    except requests.RequestException as error:
        print(f"[{datetime.now()}] Telegram error: {error}")


def normalize_text(text: str) -> str:
    return " ".join(text.replace("\xa0", " ").split())


def extract_next_data(html: str) -> dict:
    soup = BeautifulSoup(html, "html.parser")
    script = soup.find("script", id="__NEXT_DATA__")
    if not script:
        return {}

    try:
        return json.loads(script.string or script.get_text())
    except json.JSONDecodeError:
        return {}


def get_search_rides_state(html: str) -> dict:
    data = extract_next_data(html)
    return (
        data.get("props", {})
        .get("initialState", {})
        .get("searchRides", {})
    )


def get_time(value: str) -> str:
    return str(value or "")[11:16]


def find_target_trip_statuses_from_rides(rides: list):
    statuses = {
        dep: {"found": False, "available": False, "arrival": "", "raw": ""}
        for dep in TARGET_DEPARTURE_TIMES
    }

    for ride in rides:
        departure = get_time(
            ride.get("departure")
            or ride.get("departureDate")
            or ride.get("departureTime")
        )
        arrival = get_time(
            ride.get("arrival")
            or ride.get("arrivalDate")
            or ride.get("arrivalTime")
        )
        if departure in statuses:
            free_seats = int(ride.get("freeSeats", 0) or 0)
            statuses[departure] = {
                "found": True,
                "available": free_seats > 0,
                "arrival": arrival,
                "raw": f"freeSeats={free_seats}",
            }

    return statuses


def find_target_trip_statuses(html: str):
    # Fallback for saved HTML/debug mode. Runtime checks use the API directly.
    search_rides = get_search_rides_state(html)
    statuses = find_target_trip_statuses_from_rides(search_rides.get("rides", []))
    if any(status["found"] for status in statuses.values()):
        return statuses

    # Fallback in case page structure changes.
    soup = BeautifulSoup(html, "html.parser")
    for node in soup.find_all(["div", "section", "article", "li"]):
        block_text = normalize_text(node.get_text(" ", strip=True))
        for dep in TARGET_DEPARTURE_TIMES:
            if dep in block_text:
                statuses[dep] = {
                    "found": True,
                    "available": (
                        "Свободно" in block_text
                        or "Последнее место" in block_text
                        or "Заказать" in block_text
                    ),
                    "arrival": "",
                    "raw": block_text,
                }
    return statuses


def check_once():
    headers = {
        "Accept": "application/json",
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/124.0.0.0 Safari/537.36"
        )
    }
    response = requests.get(TARGET_API_URL, headers=headers, timeout=30)
    response.raise_for_status()
    data = response.json()
    return find_target_trip_statuses_from_rides(data.get("rides", []))


def debug_once() -> None:
    headers = {
        "User-Agent": (
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/124.0.0.0 Safari/537.36"
        )
    }
    response = requests.get(TARGET_URL, headers=headers, timeout=30)
    response.raise_for_status()

    html = response.text
    search_rides = get_search_rides_state(html)
    rides = search_rides.get("rides", [])
    refreshed_rides = search_rides.get("refreshedRides", [])

    print("Atlas debug")
    print(f"HTTP: {response.status_code}")
    print(f"URL: {response.url}")
    print(f"HTML bytes: {len(html)}")
    print(f"searchRides.status: {search_rides.get('status')}")
    print(f"searchRides.statusApi: {search_rides.get('statusApi')}")
    print(f"rides in __NEXT_DATA__: {len(rides)}")
    print(f"refreshedRides in __NEXT_DATA__: {len(refreshed_rides)}")

    all_times = sorted(set(re.findall(r"\b\d{2}:\d{2}\b", html)))
    target_times = [time for time in TARGET_DEPARTURE_TIMES if time in all_times]
    print(f"target times visible in raw HTML: {', '.join(target_times) or 'none'}")

    if rides:
        print("Rides from __NEXT_DATA__:")
        for ride in rides:
            departure = str(
                ride.get("departure")
                or ride.get("departureDate")
                or ride.get("departureTime")
                or ""
            )[11:16]
            arrival = str(
                ride.get("arrival")
                or ride.get("arrivalDate")
                or ride.get("arrivalTime")
                or ""
            )[11:16]
            print(f"- {departure}->{arrival} freeSeats={ride.get('freeSeats')}")
    else:
        print(
            "No rides are present in the server HTML. "
            "Atlas loads them later via a browser API request."
        )


def main():
    if "--debug" in sys.argv:
        debug_once()
        return

    print("Started Atlas monitor")
    print("Target departure times:")
    for dep in TARGET_DEPARTURE_TIMES:
        print(f"- {dep}")
    print(f"Check interval: {CHECK_INTERVAL_SECONDS} sec")

    while True:
        now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        try:
            statuses = check_once()
            for dep in TARGET_DEPARTURE_TIMES:
                status = statuses[dep]
                arrival = status["arrival"] or "?"
                if not status["found"]:
                    print(f"[{now}] {dep}: Trip not found on page")
                elif status["available"]:
                    print(f"[{now}] {dep}->{arrival}: Seats AVAILABLE")
                    send_telegram_message(
                        "Atlasbus: found free seats for your trip\n"
                        f"{dep} -> {arrival}\n"
                        f"{TARGET_URL}"
                    )
                else:
                    print(f"[{now}] {dep}->{arrival}: No seats")
        except requests.RequestException as error:
            print(f"[{now}] Request error: {error}")
        except Exception as error:  # noqa: BLE001
            print(f"[{now}] Unexpected error: {error}")

        time.sleep(CHECK_INTERVAL_SECONDS)


if __name__ == "__main__":
    main()