From bf51ddabc055fd9d357d2af7984f81fc163219a6 Mon Sep 17 00:00:00 2001 From: shmyga Date: Mon, 12 Aug 2024 01:19:14 +0300 Subject: [PATCH] feat(schedule): add matchtv schedule --- gallery/easel/__init__.py | 14 +- gallery/easel/route/api/schedule.py | 5 + gallery/easel/route/view/schedule/__init__.py | 52 + .../route/view/schedule/static/favicon.ico | Bin 0 -> 15406 bytes .../{weather => schedule}/static/index.js | 0 .../route/view/schedule/static/style.css | 21 + .../route/view/schedule/templates/index.html | 26 + .../view/schedule/templates/schedule.html | 41 + .../route/view/weather/templates/weather.html | 1 - gallery/main.py | 3 +- gallery/painting/matchtv/__init__.py | 0 gallery/painting/matchtv/api.py | 72 + gallery/painting/matchtv/mock/__init__.py | 5 + .../painting/matchtv/mock/data/matchtv.html | 5446 +++++++++++++++++ gallery/sketch/schedule/api.py | 13 + gallery/sketch/schedule/model.py | 6 +- tests/test_matchtv_api.py | 24 + 17 files changed, 5721 insertions(+), 8 deletions(-) create mode 100644 gallery/easel/route/api/schedule.py create mode 100644 gallery/easel/route/view/schedule/__init__.py create mode 100644 gallery/easel/route/view/schedule/static/favicon.ico rename gallery/easel/route/view/{weather => schedule}/static/index.js (100%) create mode 100644 gallery/easel/route/view/schedule/static/style.css create mode 100644 gallery/easel/route/view/schedule/templates/index.html create mode 100644 gallery/easel/route/view/schedule/templates/schedule.html create mode 100644 gallery/painting/matchtv/__init__.py create mode 100644 gallery/painting/matchtv/api.py create mode 100644 gallery/painting/matchtv/mock/__init__.py create mode 100644 gallery/painting/matchtv/mock/data/matchtv.html create mode 100644 gallery/sketch/schedule/api.py create mode 100644 tests/test_matchtv_api.py diff --git a/gallery/easel/__init__.py b/gallery/easel/__init__.py index 5348817..79bc2e3 100644 --- a/gallery/easel/__init__.py +++ b/gallery/easel/__init__.py @@ -1,23 +1,31 @@ -import locale +import locale as _locale from fastapi import FastAPI +from gallery.sketch.schedule.api import ScheduleApi from gallery.sketch.weather.api import WeatherApi from .route import doc +from .route.api import schedule as schedule_api_route from .route.api import weather as weather_api_route +from .route.view import schedule as schedule_view_route from .route.view import weather as weather_view_route -def build_app(weather_api: WeatherApi) -> FastAPI: - locale.setlocale(locale.LC_TIME, "ru_RU.UTF-8") +def build_app( + weather_api: WeatherApi, schedule_api: ScheduleApi, *, locale: str = "ru_RU.UTF-8" +) -> FastAPI: + _locale.setlocale(_locale.LC_TIME, locale) app = FastAPI( title="Gallery", docs_url=None, redoc_url=None, ) app.state.weather_api = weather_api + app.state.schedule_api = schedule_api doc.mount(app) weather_api_route.mount(app) + schedule_api_route.mount(app) weather_view_route.mount(app) + schedule_view_route.mount(app) return app diff --git a/gallery/easel/route/api/schedule.py b/gallery/easel/route/api/schedule.py new file mode 100644 index 0000000..e515adf --- /dev/null +++ b/gallery/easel/route/api/schedule.py @@ -0,0 +1,5 @@ +from fastapi import FastAPI + + +def mount(app: FastAPI): + pass diff --git a/gallery/easel/route/view/schedule/__init__.py b/gallery/easel/route/view/schedule/__init__.py new file mode 100644 index 0000000..9d23d11 --- /dev/null +++ b/gallery/easel/route/view/schedule/__init__.py @@ -0,0 +1,52 @@ +import datetime +from pathlib import Path + +from fastapi import FastAPI, Request +from fastapi.responses import HTMLResponse, RedirectResponse +from fastapi.staticfiles import StaticFiles +from fastapi.templating import Jinja2Templates + +from gallery.easel.route.view.weather.util import TagType, TagUtil +from gallery.sketch.schedule.api import ScheduleApi + + +def mount(app: FastAPI): + base_dir = Path(__file__).parent + app.mount( + "/schedule/static", StaticFiles(directory=base_dir / "static"), name="static" + ) + templates = Jinja2Templates(directory=base_dir / "templates") + + @app.get("/schedule", response_class=HTMLResponse) + async def get_schedule_list(request: Request): + schedule_api: ScheduleApi = request.app.state.schedule_api + channels = await schedule_api.get_channels() + return templates.TemplateResponse( + request=request, + name="index.html", + context={ + "channels": channels, + }, + ) + + @app.get("/schedule/{channel}", response_class=RedirectResponse) + async def get_schedule_default(channel: str): + return RedirectResponse(f"{channel}/tag/today") + + @app.get("/schedule/{channel}/tag/{tag}", response_class=HTMLResponse) + async def get_schedule_tag(request: Request, channel: str, tag: str): + tag_value = TagUtil.parse_tag(tag) + schedule_api: ScheduleApi = request.app.state.schedule_api + if tag_value.type == TagType.DAY: + response = await schedule_api.get_channel_schedule(channel, tag_value.date) + else: + raise ValueError(tag) + return templates.TemplateResponse( + request=request, + name="schedule.html", + context={ + "tag_util": TagUtil, + "datetime": datetime, + "response": response, + }, + ) diff --git a/gallery/easel/route/view/schedule/static/favicon.ico b/gallery/easel/route/view/schedule/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a283ecbb19c12721c2eba06930c07b51354f6565 GIT binary patch literal 15406 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 PQv*FU&{G3lt%3go^wSY@ literal 0 HcmV?d00001 diff --git a/gallery/easel/route/view/weather/static/index.js b/gallery/easel/route/view/schedule/static/index.js similarity index 100% rename from gallery/easel/route/view/weather/static/index.js rename to gallery/easel/route/view/schedule/static/index.js diff --git a/gallery/easel/route/view/schedule/static/style.css b/gallery/easel/route/view/schedule/static/style.css new file mode 100644 index 0000000..e435974 --- /dev/null +++ b/gallery/easel/route/view/schedule/static/style.css @@ -0,0 +1,21 @@ +body { + font-size: 1.5rem; +} + +.app-container { + display: flex; + flex-direction: column; + flex-wrap: nowrap; + align-items: center; +} + +table, +th, +td { + /* border: 1px solid rgba(0, 0, 0, 0.2); */ + text-align: left; +} + +td { + padding: 0.1rem 0.4rem; +} diff --git a/gallery/easel/route/view/schedule/templates/index.html b/gallery/easel/route/view/schedule/templates/index.html new file mode 100644 index 0000000..e6acc56 --- /dev/null +++ b/gallery/easel/route/view/schedule/templates/index.html @@ -0,0 +1,26 @@ + + + + + + + + ТВ + + + + + +
    + {% for channel in channels %} +
  • {{channel}}
  • + {% endfor %} +
+ + + \ No newline at end of file diff --git a/gallery/easel/route/view/schedule/templates/schedule.html b/gallery/easel/route/view/schedule/templates/schedule.html new file mode 100644 index 0000000..491ddfb --- /dev/null +++ b/gallery/easel/route/view/schedule/templates/schedule.html @@ -0,0 +1,41 @@ + + + + + + + + Программа | {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}} + + + + + +

+ {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}} +

+ + + + + + + + + + {% for value in response.values %} + + + + + {% endfor %} + +
TIMENAME
{{value.start.strftime('%H:%M')}}{{value.label}}
+ + + \ No newline at end of file diff --git a/gallery/easel/route/view/weather/templates/weather.html b/gallery/easel/route/view/weather/templates/weather.html index 9da5908..d523b72 100644 --- a/gallery/easel/route/view/weather/templates/weather.html +++ b/gallery/easel/route/view/weather/templates/weather.html @@ -171,7 +171,6 @@ - \ No newline at end of file diff --git a/gallery/main.py b/gallery/main.py index c745037..fad5ec1 100644 --- a/gallery/main.py +++ b/gallery/main.py @@ -5,8 +5,9 @@ import uvicorn from gallery.easel import build_app from gallery.painting.gismeteo.api import GismeteoApi +from gallery.painting.matchtv.api import MatchTvApi -app = build_app(GismeteoApi()) +app = build_app(GismeteoApi(), MatchTvApi()) def run(): diff --git a/gallery/painting/matchtv/__init__.py b/gallery/painting/matchtv/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gallery/painting/matchtv/api.py b/gallery/painting/matchtv/api.py new file mode 100644 index 0000000..12e9fd4 --- /dev/null +++ b/gallery/painting/matchtv/api.py @@ -0,0 +1,72 @@ +import datetime +import logging + +import aiohttp +from bs4 import BeautifulSoup + +from gallery.sketch.schedule.api import ScheduleApi +from gallery.sketch.schedule.model import Channel, Schedule, ScheduleValue + +logger = logging.getLogger("matchtv") + + +CHANNEL_LIST = [ + "matchtv", + "igra", + "arena", + "futbol-1", + "futbol-2", + "futbol-2", + "strana", + "planeta", +] + + +class MatchTvApi(ScheduleApi): + BASE_URL = "https://matchtv.ru" + + USER_AGENT = ( + "Mozilla/5.0 (X11; Linux x86_64) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/126.0.0.0 Safari/537.36" + ) + + async def _request(self, endpoint: str) -> str: + url = f"{self.BASE_URL}/{endpoint}" + print(url) + logger.info(url) + async with aiohttp.ClientSession( + headers={ + "User-Agent": self.USER_AGENT, + }, + raise_for_status=True, + ) as session: + async with session.request("GET", url) as response: + return await response.text() + + async def get_channels(self) -> list[str]: + return CHANNEL_LIST + + async def get_channel_schedule( + self, channel_id: str, date: datetime.date + ) -> Schedule: + endpoint = f"channel/{channel_id}/tvguide?date={date:%d-%m-%Y}" + data = await self._request(endpoint) + soup = BeautifulSoup(data, features="html.parser") + values = [] + channel_name = soup.select_one(".caption__heading").text.split("|")[0].strip() + current_date = datetime.datetime.combine( + date.today(), datetime.datetime.min.time() + ) + for item in soup.select(".teleprogram-schedule .teleprogram-schedule__item"): + title = item.select_one(".teleprogram-item__title").text.strip() + time_str = item.select_one(".teleprogram-item__time").text.strip() + hours, minutes = map(int, time_str.split(":")) + item_date = datetime.datetime.combine( + date, datetime.time(hour=hours, minute=minutes) + ) + values.append(ScheduleValue(start=current_date, end=item_date, label=title)) + current_date = item_date + return Schedule( + channel=Channel(id=channel_id, name=channel_name), date=date, values=values + ) diff --git a/gallery/painting/matchtv/mock/__init__.py b/gallery/painting/matchtv/mock/__init__.py new file mode 100644 index 0000000..de6a565 --- /dev/null +++ b/gallery/painting/matchtv/mock/__init__.py @@ -0,0 +1,5 @@ +from pathlib import Path + +from gallery.sketch.mock import MockData + +MATCHTV_MOCK_DATA = MockData(Path(__file__).parent / "data") diff --git a/gallery/painting/matchtv/mock/data/matchtv.html b/gallery/painting/matchtv/mock/data/matchtv.html new file mode 100644 index 0000000..4c82b42 --- /dev/null +++ b/gallery/painting/matchtv/mock/data/matchtv.html @@ -0,0 +1,5446 @@ + + + + + + + + + + + + + + Матч ТВ: программа передач на сегодня, завтра + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + +
+ + + + + + + + + + + + + Перейти к основному контенту + +
+ + + + + + + + + + + + + + + + + +
+ +
+ + +
+
+
+
+ + + +
+ +
+ + + +
+ +
+
+
+ +
+
+
+
+
+
+
+
+ +
+ +
+ + + + + +
+
+ +
+ + + +
+ +
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+ + + + +
+
+
+
+ + + + + + + + + + + + + +
+
+
+ Иллюстрация канала «Матч ТВ» +
+
+
+ + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + +
+
+ +
+
+
+ + + +
+
+ +
+
Прямой эфир
+
+ + + +
+
+ +
+
+ +
+ +
+
+ + + + + + + + +
+
+ + +
+
+ + + + + +
+
    +
  • +
    +
    + 00:00 Бокс. Bare Knuckle FC. Коннор Тирни против Джонни Грэма. Трансляция из Великобритании 16+. +
    + + + + + + + + + +
    +
  • +
+
+ +
+
+ + + + + + + + + + + + +
+
+
+
+

«Матч ТВ» — российский федеральный общедоступный канал о спорте и активном образе жизни.

Матч ТВ — это Третья кнопка вашего телевизора!

В нашем эфире — новости, аналитика и развлекательные программы, документальные циклы и специальные репортажи, реалити- и ток-шоу, художественные фильмы и сериалы: всё о российском и мировом спорте!

Канал объединяет лучших спортивных комментаторов и ведущих. Это самая опытная и профессиональная команда на российском ТВ — вместе с нашими зрителями и болельщиками мы прошли все важнейшие турниры и первенства последних лет.

Мы зовем всех присоединиться к сообществу людей, которые по-настоящему любят спорт и живут им: у экранов ТВ, на стадионах или в спортзалах.

#всенаматч!

+
+
+
+
+ + + + + + + + + + + + +
+
+ +
+
+ + + +
+
+ + + + + +
+
+ +
+
+ +
+ + + + + + + + + + +
+
+ + + + +
+
    +
  • + + + + +
    +
    +
    +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    + +
    + + + + +
    + +
    +
    +
    + + + + +
    +
    + +
  • +
+
+ +
+
+ + + + + + + + + + + + +
+
+
+
+ +
+
    +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Звёзды без правил" 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Всё о главном" 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Новости. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Все на Матч! Прямой эфир. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Джулур. Мас-рестлинг". Художественный фильм. Россия, 2021 г. 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Специальный репортаж 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Новости. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Есть тема!" Прямой эфир. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Век нашего спорта". Документальный цикл 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Новости. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Громко" Прямой эфир. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Дартс. OLIMPBET Летняя серия. Прямая трансляция из Москвы. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Футбол. МИР Российская Премьер-Лига. Обзор тура 6+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Футбол. МЕЛБЕТ-Первая Лига. "КАМАЗ" (Набережные Челны) - "Торпедо" (Москва). Прямая трансляция. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Все на Матч! Прямой эфир. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Век нашего спорта". Документальный цикл 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Смешанные единоборства. UFC. Марчин Тыбура против Сергея Спивака. Трансляция из США 16+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Баскетбол 3х3. Международный студенческий кубок. Трансляция из Красноярска 6+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Новости 0+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Пляжный футбол. OLIMPBET Чемпионат России. "Лекс" (Санкт-Петербург) - "Строгино" (Москва). Трансляция из Москвы 0+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + Пляжный футбол. OLIMPBET Чемпионат России. "Краснодар-ЮМР" - "Саратов". Трансляция из Москвы 0+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
  • + + + +
    +
    + +
    + Логотип телеканала «Матч ТВ» + "Звёзды без правил" 12+. +
    +
    +
      +
    +
    +
    + + + + + + + + + + +
    + +
  • +
+
+ +
+ + + + + + + + + + + + + +
+
+
+ + +
+ + +
+ + + + + + + + + + + +
+
+
+ + + +
+ +
+ + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + + + + diff --git a/gallery/sketch/schedule/api.py b/gallery/sketch/schedule/api.py new file mode 100644 index 0000000..2a17dd2 --- /dev/null +++ b/gallery/sketch/schedule/api.py @@ -0,0 +1,13 @@ +import datetime + +from .model import Schedule + + +class ScheduleApi: + async def get_channels(self) -> list[str]: + raise NotImplementedError + + async def get_channel_schedule( + self, channel_id: str, date: datetime.date + ) -> Schedule: + raise NotImplementedError diff --git a/gallery/sketch/schedule/model.py b/gallery/sketch/schedule/model.py index 4ac4306..1c554ae 100644 --- a/gallery/sketch/schedule/model.py +++ b/gallery/sketch/schedule/model.py @@ -13,14 +13,14 @@ class Channel(Model): name: str -class ScheduleItem(Model): +class ScheduleValue(Model): start: datetime.datetime end: datetime.datetime label: str - category: str | None + category: str | None = None class Schedule(Model): channel: Channel date: datetime.date - items: list[ScheduleItem] + values: list[ScheduleValue] diff --git a/tests/test_matchtv_api.py b/tests/test_matchtv_api.py new file mode 100644 index 0000000..c66388f --- /dev/null +++ b/tests/test_matchtv_api.py @@ -0,0 +1,24 @@ +import datetime + +import pytest + +from gallery.painting.matchtv.api import MatchTvApi +from gallery.painting.matchtv.mock import MATCHTV_MOCK_DATA + + +@pytest.fixture(name="matchtv_api", scope="module") +def matchtv_api_fixture() -> MatchTvApi: + api = MatchTvApi() + + async def _request(endpoint: str) -> str: + return MATCHTV_MOCK_DATA.get_html(endpoint.split("/")[1]) + + api._request = _request + return api + + +async def test_channel(matchtv_api: MatchTvApi): + result = await matchtv_api.get_channel_schedule("matchtv", datetime.date.today()) + assert result is not None + assert len(result.items) > 0 + print(">>", "\n".join(map(str, result.items)))