From a41b4e53089fb9a3ec74a0551083511c9886d84a Mon Sep 17 00:00:00 2001 From: shmyga Date: Fri, 26 Jun 2026 16:36:40 +0300 Subject: [PATCH] feat(schedule): implement multiple schedule providers --- gallery/easel/depends/api.py | 9 + gallery/easel/depends/schedule.py | 9 + gallery/easel/depends/weather.py | 8 +- gallery/easel/route/api/schedule.py | 4 +- gallery/easel/route/view/schedule/__init__.py | 30 +- .../route/view/schedule/templates/index.html | 64 ++- gallery/painting/matchtv/api.py | 22 +- gallery/painting/yandextv/api.py | 36 +- gallery/sketch/bundle.py | 8 - gallery/sketch/catalog.py | 14 - gallery/sketch/schedule/api.py | 16 +- gallery/sketch/schedule/cached.py | 17 +- gallery/sketch/schedule/catalog.py | 21 - gallery/sketch/schedule/model.py | 22 +- gallery/sketch/source.py | 5 +- static/src/index.ts | 1 + static/src/schedule/schedule.ts | 109 +++++ tests/data/yandextv/__init__.py | 1 + tests/data/yandextv/suggest-tv2.json | 423 ++++++++++++++++++ tests/test_yandextv_api.py | 14 +- 20 files changed, 669 insertions(+), 164 deletions(-) create mode 100644 gallery/easel/depends/api.py create mode 100644 gallery/easel/depends/schedule.py delete mode 100644 gallery/sketch/catalog.py delete mode 100644 gallery/sketch/schedule/catalog.py create mode 100644 static/src/schedule/schedule.ts create mode 100644 tests/data/yandextv/suggest-tv2.json diff --git a/gallery/easel/depends/api.py b/gallery/easel/depends/api.py new file mode 100644 index 0000000..3d6b291 --- /dev/null +++ b/gallery/easel/depends/api.py @@ -0,0 +1,9 @@ +from gallery.easel.core import AppRequest +from gallery.sketch.api import API + + +def api_resolver(api_type: type[API]): + def get_api(request: AppRequest, provider: str | None = None) -> API: + return request.app.state.api.get_api(api_type, provider) + + return get_api diff --git a/gallery/easel/depends/schedule.py b/gallery/easel/depends/schedule.py new file mode 100644 index 0000000..132cafe --- /dev/null +++ b/gallery/easel/depends/schedule.py @@ -0,0 +1,9 @@ +from typing import Annotated + +from fastapi import Depends + +from gallery.sketch.schedule.api import ScheduleApi + +from .api import api_resolver + +ScheduleApiDepends = Annotated[ScheduleApi, Depends(api_resolver(ScheduleApi))] diff --git a/gallery/easel/depends/weather.py b/gallery/easel/depends/weather.py index e0db605..2fb3c14 100644 --- a/gallery/easel/depends/weather.py +++ b/gallery/easel/depends/weather.py @@ -2,12 +2,8 @@ from typing import Annotated from fastapi import Depends -from gallery.easel.core import AppRequest from gallery.sketch.weather.api import WeatherApi +from .api import api_resolver -async def get_weather_api(request: AppRequest, provider: str | None = None) -> WeatherApi: - return request.app.state.api.get_weather(provider) - - -WeatherApiDepends = Annotated[WeatherApi, Depends(get_weather_api)] +WeatherApiDepends = Annotated[WeatherApi, Depends(api_resolver(WeatherApi))] diff --git a/gallery/easel/route/api/schedule.py b/gallery/easel/route/api/schedule.py index ea9d39a..e276a1a 100644 --- a/gallery/easel/route/api/schedule.py +++ b/gallery/easel/route/api/schedule.py @@ -3,13 +3,13 @@ import datetime from fastapi import APIRouter from gallery.easel.core import AppRequest -from gallery.sketch.schedule.model import ChannelId, Schedule +from gallery.sketch.schedule.model import Channel, Schedule router = APIRouter(prefix="/schedule") @router.get("/channels") -async def get_api_schedule_channels(request: AppRequest) -> list[ChannelId]: +async def get_api_schedule_channels(request: AppRequest) -> list[Channel]: schedule_api = request.app.state.api.schedule return await schedule_api.get_channels() diff --git a/gallery/easel/route/view/schedule/__init__.py b/gallery/easel/route/view/schedule/__init__.py index 13c64a9..8b6b057 100644 --- a/gallery/easel/route/view/schedule/__init__.py +++ b/gallery/easel/route/view/schedule/__init__.py @@ -1,26 +1,18 @@ import datetime from pathlib import Path -from typing import Annotated -from fastapi import APIRouter, Depends +from fastapi import APIRouter from fastapi.responses import HTMLResponse, RedirectResponse from gallery.easel.core import AppRequest +from gallery.easel.depends.schedule import ScheduleApiDepends from gallery.sketch.schedule.api import ScheduleApi -from gallery.sketch.schedule.catalog import BUNDLE from ..common.utils.tag import TagType, TagUtil from ..common.utils.template import build_templates from .filters import timedelta_format -async def get_schedule_api(request: AppRequest, provider: str | None = None) -> ScheduleApi: - return request.app.state.api.get_schedule(provider) - - -ScheduleApiDepends = Annotated[ScheduleApi, Depends(get_schedule_api)] - - def context_procesor(request: AppRequest) -> dict: return { "providers": request.app.state.api.get_api_providers(ScheduleApi.TYPE), @@ -32,20 +24,20 @@ templates = build_templates( { "timedelta_format": timedelta_format, }, + context_procesor, ) router = APIRouter(prefix="/schedule") @router.get("/", response_class=HTMLResponse) -async def get_schedule_list(request: AppRequest, schedule_api: ScheduleApiDepends): - channels = await schedule_api.get_channels() - channels_data = BUNDLE.select_items(channels) +async def get_schedule_index(request: AppRequest, schedule_api: ScheduleApiDepends, query: str | None = None): + channels = (await schedule_api.find_channels(query)) if query else [] return templates.TemplateResponse( request=request, name="index.html", context={ - "channels": channels_data, + "channels": channels, }, ) @@ -53,15 +45,15 @@ async def get_schedule_list(request: AppRequest, schedule_api: ScheduleApiDepend @router.get("/tag/{tag}", response_class=HTMLResponse) async def get_schedule_tag(request: AppRequest, schedule_api: ScheduleApiDepends, tag: str, live: bool = False): tag_value = TagUtil.parse_tag(tag) - results = await schedule_api.get_all_schedules(tag_value.date) + # results = await schedule_api.get_all_schedules(tag_value.date) return templates.TemplateResponse( request=request, name="schedule.html", context={ "tag_util": TagUtil, "datetime": datetime, - "response": results[0], - "responses": results, + # "response": results[0], + # "responses": results, "live": live, }, ) @@ -72,11 +64,11 @@ async def get_channel_default(channel: str): return RedirectResponse(f"{channel}/tag/today") -@router.get("/{channel}/tag/{tag}", response_class=HTMLResponse) +@router.get("/{provider}/{channel}/tag/{tag}", response_class=HTMLResponse) async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: str): tag_value = TagUtil.parse_tag(tag) if tag_value.type == TagType.DAY: - response = await schedule_api.get_channel_schedule(channel, tag_value.date) + response = await schedule_api.get_schedule(channel, tag_value.date) else: raise ValueError(tag) return templates.TemplateResponse( diff --git a/gallery/easel/route/view/schedule/templates/index.html b/gallery/easel/route/view/schedule/templates/index.html index 0a93198..f88fe85 100644 --- a/gallery/easel/route/view/schedule/templates/index.html +++ b/gallery/easel/route/view/schedule/templates/index.html @@ -3,16 +3,60 @@ {% block content %}

{{_("TV program")}}

-
- - Все - +
+
+ + + + + +
+
+{% if channels %} +
+ +
+{% endif %} + + {% endblock %} \ No newline at end of file diff --git a/gallery/painting/matchtv/api.py b/gallery/painting/matchtv/api.py index 7075f3a..2031f9e 100644 --- a/gallery/painting/matchtv/api.py +++ b/gallery/painting/matchtv/api.py @@ -4,7 +4,7 @@ import logging from bs4 import BeautifulSoup from gallery.sketch.schedule.api import ScheduleApi -from gallery.sketch.schedule.model import Channel, ChannelId, Schedule, ScheduleValue +from gallery.sketch.schedule.model import Channel, Schedule, ScheduleValue from gallery.sketch.source import ApiSource logger = logging.getLogger("matchtv") @@ -14,18 +14,10 @@ class MatchTvApi(ScheduleApi): PROVIDER = "matchtv" SOURCE = ApiSource("https://matchtv.ru") - async def get_channels(self) -> list[ChannelId]: - return [ - ChannelId.MATCH_TV, - ChannelId.MATCH_IGRA, - ChannelId.MATCH_ARENA, - ChannelId.MATCH_FUTBOL_1, - ChannelId.MATCH_FUTBOL_2, - ChannelId.MATCH_FUTBOL_3, - ChannelId.MATCH_STRANA, - ] + async def find_channels(self, query: str) -> list[Channel]: + raise NotImplementedError - async def get_channel_schedule(self, channel_id: ChannelId, date: datetime.date) -> Schedule: + async def get_channel_schedule(self, channel_id: str, date: datetime.date) -> Schedule: endpoint = f"tvguide/{channel_id}?date={date:%Y%m%d}" data = await self.SOURCE.request(endpoint) soup = BeautifulSoup(data, features="html.parser") @@ -50,4 +42,8 @@ class MatchTvApi(ScheduleApi): if prev_value is not None: prev_value.end = item_date prev_value = value - return Schedule(channel=Channel(id=channel_id, name=channel_name), date=date, values=values) + return Schedule( + channel=Channel(id=channel_id, name=channel_name, provider=self.provider), + date=date, + values=values, + ) diff --git a/gallery/painting/yandextv/api.py b/gallery/painting/yandextv/api.py index f3c66b4..39267ce 100644 --- a/gallery/painting/yandextv/api.py +++ b/gallery/painting/yandextv/api.py @@ -1,27 +1,15 @@ import datetime +import json import logging from bs4 import BeautifulSoup from gallery.sketch.schedule.api import ScheduleApi -from gallery.sketch.schedule.model import Channel, ChannelId, Schedule, ScheduleValue +from gallery.sketch.schedule.model import Channel, Schedule, ScheduleValue from gallery.sketch.source import ApiSource logger = logging.getLogger("matchtv") -CHANNELS_MAP: dict[ChannelId, str] = { - ChannelId.MATCH_TV: "match-tv-49", - ChannelId.MATCH_IGRA: "match-igra-1174", - ChannelId.MATCH_ARENA: "match-arena-1173", - ChannelId.MATCH_FUTBOL_1: "match-futbol-1-646", - ChannelId.MATCH_FUTBOL_2: "match-futbol-2-593", - ChannelId.MATCH_FUTBOL_3: "match-futbol-3-797", - ChannelId.MATCH_STRANA: "match-strana-1356", - ChannelId.MATCH_PLANETA: "match-planeta-1177", - # ChannelId.EUROSPORT: "eurosport-677", - # ChannelId.EUROSPORT_2: "eurosport-2-720", - ChannelId.START: "start-103", -} HEADERS: dict[str, str] = { "Accept": ( @@ -57,11 +45,17 @@ class YandexTvApi(ScheduleApi): PROVIDER = "yandextv" SOURCE = ApiSource("https://tv.yandex.ru", headers=HEADERS) - async def get_channels(self) -> list[ChannelId]: - return list(CHANNELS_MAP.keys()) + async def find_channels(self, query: str) -> list[Channel]: + url = f"https://suggest-multi.yandex.ru/suggest-tv2?v=4&uil=ru&lr=10&count_channels=4&count_programs=4&sn=50&part={query}" + _, values = json.loads(await self.SOURCE.request(url)) + result = [] + for _, name, content in values[:-1]: + channel_id = content["url"].split("/")[-1] + result.append(Channel(id=channel_id, name=name, provider=self.provider)) + return result - async def get_channel_schedule(self, channel_id: ChannelId, date: datetime.date) -> Schedule: - endpoint = f"channel/{CHANNELS_MAP[channel_id]}?date={date:%Y-%m-%d}" + async def get_channel_schedule(self, channel_id: str, date: datetime.date) -> Schedule: + endpoint = f"channel/{channel_id}?date={date:%Y-%m-%d}" data = await self.SOURCE.request(endpoint) soup = BeautifulSoup(data, features="html.parser") if soup.select_one(".CheckboxCaptcha") is not None: @@ -85,4 +79,8 @@ class YandexTvApi(ScheduleApi): if prev_value is not None: prev_value.end = item_date prev_value = value - return Schedule(channel=Channel(id=channel_id, name=channel_name), date=date, values=values) + return Schedule( + channel=Channel(id=channel_id, name=channel_name, provider=self.provider), + date=date, + values=values, + ) diff --git a/gallery/sketch/bundle.py b/gallery/sketch/bundle.py index 70356d0..f2f49c0 100644 --- a/gallery/sketch/bundle.py +++ b/gallery/sketch/bundle.py @@ -1,6 +1,4 @@ from .api import API, Api -from .schedule.api import ScheduleApi -from .weather.api import WeatherApi class ApiBundle: @@ -21,9 +19,3 @@ class ApiBundle: if provider is None or provider == value.provider: return value raise ValueError(api_type, provider) - - def get_weather(self, provider: str | None = None) -> WeatherApi: - return self.get_api(WeatherApi, provider) - - def get_schedule(self, provider: str | None = None) -> ScheduleApi: - return self.get_api(ScheduleApi, provider) diff --git a/gallery/sketch/catalog.py b/gallery/sketch/catalog.py deleted file mode 100644 index c0d9afc..0000000 --- a/gallery/sketch/catalog.py +++ /dev/null @@ -1,14 +0,0 @@ -from typing import Generic, TypeVar - -T = TypeVar("T") - - -class CatalogBundle(Generic[T]): - def __init__(self, items: list[T]) -> None: - self._items_by_id = {item.id: item for item in items} - - def get_item(self, item_id: str) -> T: - return self._items_by_id[item_id] - - def select_items(self, ids: list[str]) -> list[T]: - return [self._items_by_id[id_] for id_ in ids] diff --git a/gallery/sketch/schedule/api.py b/gallery/sketch/schedule/api.py index 11abe2a..b6a3884 100644 --- a/gallery/sketch/schedule/api.py +++ b/gallery/sketch/schedule/api.py @@ -1,25 +1,15 @@ -import asyncio import datetime from ..api import Api -from .model import ChannelId, Schedule +from .model import Channel, Schedule class ScheduleApi(Api): TYPE = "schedule" INTERVAL: float = 0.5 - async def get_channels(self) -> list[ChannelId]: + async def find_channels(self, query: str) -> list[Channel]: raise NotImplementedError - async def get_channel_schedule(self, channel_id: ChannelId, date: datetime.date) -> Schedule: + async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule: raise NotImplementedError - - async def get_all_schedules(self, date: datetime.date) -> list[Schedule]: - channels = await self.get_channels() - results = [] - for channel in channels: - results.append(await self.get_channel_schedule(channel_id=channel, date=date)) - if self.INTERVAL > 0: - await asyncio.sleep(self.INTERVAL) - return results diff --git a/gallery/sketch/schedule/cached.py b/gallery/sketch/schedule/cached.py index ec36150..8563d36 100644 --- a/gallery/sketch/schedule/cached.py +++ b/gallery/sketch/schedule/cached.py @@ -6,7 +6,7 @@ from gallery.sketch.cached import CachedApi, CachePreset from gallery.util import TimeUnit from .api import ScheduleApi -from .model import ChannelId, Schedule +from .model import Channel, Schedule CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6) @@ -15,11 +15,11 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]): CACHE_KEY = ScheduleApi.TYPE @cached( - key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.channels", + key_builder=lambda fun, self, query: f"api.{self.CACHE_KEY}.{self.provider}.find.{query}", **CACHE_PRESET._asdict(), ) - async def get_channels(self) -> list[ChannelId]: - return await self._api.get_channels() + async def find_channels(self, query: str) -> list[Channel]: + return await self._api.find_channels(query) @cached( key_builder=lambda fun, self, channel_id, date: ( @@ -27,12 +27,5 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]): ), **CACHE_PRESET._asdict(), ) - async def get_channel_schedule(self, channel_id: ChannelId, date: datetime.date) -> Schedule: + async def get_channel_schedule(self, channel_id: str, date: datetime.date) -> Schedule: return await self._api.get_channel_schedule(channel_id, date) - - @cached( - key_builder=lambda fun, self, date: (f"api.{self.CACHE_KEY}.{self.provider}.all.{date}"), - **CACHE_PRESET._asdict(), - ) - async def get_all_schedules(self, date: datetime.date) -> list[Schedule]: - return await self._api.get_all_schedules(date) diff --git a/gallery/sketch/schedule/catalog.py b/gallery/sketch/schedule/catalog.py deleted file mode 100644 index 6ed8afd..0000000 --- a/gallery/sketch/schedule/catalog.py +++ /dev/null @@ -1,21 +0,0 @@ -from gallery.sketch.catalog import CatalogBundle - -from .model import Channel, ChannelId - -BUNDLE = CatalogBundle( - [ - Channel(id=ChannelId.MATCH_TV, name="Матч ТВ"), - Channel(id=ChannelId.MATCH_IGRA, name="Матч! Игра"), - Channel(id=ChannelId.MATCH_ARENA, name="Матч! Арена"), - Channel(id=ChannelId.MATCH_FUTBOL_1, name="Футбол 1"), - Channel(id=ChannelId.MATCH_FUTBOL_2, name="Футбол 2"), - Channel(id=ChannelId.MATCH_FUTBOL_3, name="Футбол 3"), - Channel(id=ChannelId.MATCH_STRANA, name="Матч! Страна"), - Channel(id=ChannelId.MATCH_PLANETA, name="Матч! Планета"), - Channel(id=ChannelId.MATCH_PLANETA, name="Матч! Планета"), - Channel(id=ChannelId.EUROSPORT, name="Europsort"), - Channel(id=ChannelId.EUROSPORT_2, name="Europsort 2"), - Channel(id=ChannelId.START, name="Старт!"), - Channel(id=ChannelId.TEST, name="Тест"), - ] -) diff --git a/gallery/sketch/schedule/model.py b/gallery/sketch/schedule/model.py index 790c22b..533c3c2 100644 --- a/gallery/sketch/schedule/model.py +++ b/gallery/sketch/schedule/model.py @@ -1,5 +1,4 @@ import datetime -from enum import StrEnum from pydantic import BaseModel @@ -9,27 +8,10 @@ class Model(BaseModel): use_enum_values = True -class ChannelId(StrEnum): - MATCH_TV = "matchtv" - MATCH_IGRA = "igra" - MATCH_ARENA = "arena" - MATCH_FUTBOL_1 = "futbol-1" - MATCH_FUTBOL_2 = "futbol-2" - MATCH_FUTBOL_3 = "futbol-3" - MATCH_STRANA = "strana" - MATCH_PLANETA = "planeta" - EUROSPORT = "eurosport" - EUROSPORT_2 = "eurosport-2" - START = "start" - TEST = "test" - - def __str__(self) -> str: - return self.value - - class Channel(Model): - id: ChannelId + id: str name: str + provider: str class ScheduleValue(Model): diff --git a/gallery/sketch/source.py b/gallery/sketch/source.py index f2d602f..1b716c1 100644 --- a/gallery/sketch/source.py +++ b/gallery/sketch/source.py @@ -26,7 +26,10 @@ class ApiSource: self._headers = headers async def request(self, endpoint: str) -> str: - url = f"{self._base_url}/{endpoint}" + if endpoint.startswith("https:"): + url = endpoint + else: + url = f"{self._base_url}/{endpoint}" logger.info(url) headers = {"User-Agent": self._user_agent, **(self._headers or {})} async with aiohttp.ClientSession( diff --git a/static/src/index.ts b/static/src/index.ts index f0b971c..6c0de69 100644 --- a/static/src/index.ts +++ b/static/src/index.ts @@ -4,6 +4,7 @@ import "./language"; import "./main.scss"; import "./theme"; import "./weather/weather"; +import "./schedule/schedule"; document.addEventListener("DOMContentLoaded", (event) => { const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); diff --git a/static/src/schedule/schedule.ts b/static/src/schedule/schedule.ts new file mode 100644 index 0000000..c53de76 --- /dev/null +++ b/static/src/schedule/schedule.ts @@ -0,0 +1,109 @@ +export interface Channel { + id: string; + name: string; + provider: string; +} + +export class ScheduleChannelStorage { + private storageKey: string = "schedule:channels"; + + getAll(): Channel[] { + const objects = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}"); + return Object.values(objects); + } + + add(object: Channel) { + const objects = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}"); + objects[object.id] = object; + window.localStorage.setItem(this.storageKey, JSON.stringify(objects)); + } + + remove(id: string) { + const objects = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}"); + delete objects[id]; + window.localStorage.setItem(this.storageKey, JSON.stringify(objects)); + } +} + +export class ScheduleChannelManager { + loadChannels(selector: string) { + const channels = window.scheduleChannelStorage.getAll(); + const container = document.querySelector(selector); + if (container) { + container.innerHTML = ""; + for (const channel of channels) { + const element = new ScheduleChannelElement(); + element.setAttribute("channel", JSON.stringify(channel)); + element.setAttribute("removable", ""); + container.appendChild(element); + } + } + } +} + +class ScheduleChannelElement extends HTMLElement { + static observedAttributes = ["channel", "removable"]; + + channel: Channel | null = null; + + constructor() { + super(); + this.handleClick = this.handleClick.bind(this); + this.handleRemoveClick = this.handleRemoveClick.bind(this); + } + + connectedCallback() { + this.channel = JSON.parse(this.getAttribute("channel") || "{}"); + this.innerHTML = ` + + + ${this.channel?.name} + + + ${this.channel?.provider} + + ✕ + + + `; + this.addEventListener("click", this.handleClick); + if (this.hasAttribute("removable")) { + this.querySelector("[data-remove]")?.addEventListener("click", this.handleRemoveClick); + } else { + this.querySelector("[data-remove]")?.remove(); + } + } + + disconnectedCallback() { + this.removeEventListener("click", this.handleClick); + this.querySelector("[data-remove]")?.removeEventListener("click", this.handleRemoveClick); + } + + handleClick(event: MouseEvent) { + if (this.channel) { + window.scheduleChannelStorage.add(this.channel); + } + } + + handleRemoveClick(event: MouseEvent) { + event.preventDefault(); + event.stopPropagation(); + if (this.channel) { + window.scheduleChannelStorage.remove(this.channel.id); + } + this.remove(); + } +} + +customElements.define("schedule-channel", ScheduleChannelElement); + +declare global { + interface Window { + scheduleChannelStorage: ScheduleChannelStorage; + scheduleChannelManager: ScheduleChannelManager; + } +} + +window.scheduleChannelStorage = new ScheduleChannelStorage(); +window.scheduleChannelManager = new ScheduleChannelManager(); diff --git a/tests/data/yandextv/__init__.py b/tests/data/yandextv/__init__.py index 39ecdc9..a8f99bf 100644 --- a/tests/data/yandextv/__init__.py +++ b/tests/data/yandextv/__init__.py @@ -6,5 +6,6 @@ YANDEXTV_MOCK_SOURCE = MockSource( Path(__file__).parent, { "test": "test.html", + "suggest-tv2": "suggest-tv2.json", }, ) diff --git a/tests/data/yandextv/suggest-tv2.json b/tests/data/yandextv/suggest-tv2.json new file mode 100644 index 0000000..03549e6 --- /dev/null +++ b/tests/data/yandextv/suggest-tv2.json @@ -0,0 +1,423 @@ +[ + "матч", + [ + [ + "bemjson", + "Матч!", + { + "url": "https:\/\/tv.yandex.ru\/channels\/1593", + "target": "_self", + "bemjson": [ + { + "elem": "icon", + "elemMods": { + "size": "l" + }, + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/48x72", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/48x68", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/48x72", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/48x68", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/64x36", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/64x36", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/80x60", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/80x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/80x60", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/80x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/120x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/120x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/130x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/130x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/214x121", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/368x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/184x140", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/428x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/z", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/170x100", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/340x200", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/260x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/160x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/160x120", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/160x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/214x121", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/368x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/184x140", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/428x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/z", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/170x100", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/340x200", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/260x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/160x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/160x120", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/160x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016e2603ad95657bcd08dc16bad081\/orig", + "attrs": { + "style": "margin-right: 5px;" + } + }, + { + "elem": "text", + "content": "Матч!" + } + ], + "label": "Каналы" + } + ], + [ + "bemjson", + "Матч Премьер", + { + "url": "https:\/\/tv.yandex.ru\/channels\/1861", + "target": "_self", + "bemjson": [ + { + "elem": "icon", + "elemMods": { + "size": "l" + }, + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/80x60", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/80x60", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/120x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/120x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/130x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/130x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/184x140", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/160x120", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/160x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/260x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/428x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/340x200", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/z", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/214x121", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/160x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/368x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/170x100", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/184x140", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/160x120", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/160x90", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/260x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/428x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/340x200", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/z", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/214x121", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/160x160", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/368x280", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/170x100", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/28884\/2a00000164ea55a8e4e47294c57caf3f249c\/orig", + "attrs": { + "style": "margin-right: 5px;" + } + }, + { + "elem": "text", + "content": "Матч Премьер" + } + ], + "label": "Каналы" + } + ], + [ + "bemjson", + "Матч! Арена", + { + "url": "https:\/\/tv.yandex.ru\/channels\/1667", + "target": "_self", + "bemjson": [ + { + "elem": "icon", + "elemMods": { + "size": "l" + }, + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a00000160080262b3c5f1b231e9c1a50c16\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a00000160080262b3c5f1b231e9c1a50c16\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/30303\/2a0000016008026461f08cb3fdd86e3a2457\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/30303\/2a0000016008026461f08cb3fdd86e3a2457\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/51763\/2a0000016008026771d2f3c7dac0e8d437f9\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802686dbbbed380ec99afc655\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/51763\/2a000001600802653c391d2e32075819e8f6\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016008026696804b8c4e42f226e439\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/51763\/2a0000016008026771d2f3c7dac0e8d437f9\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802686dbbbed380ec99afc655\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/51763\/2a000001600802653c391d2e32075819e8f6\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a0000016008026696804b8c4e42f226e439\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802507cc14a8dfebd96835e47\/orig", + "attrs": { + "style": "margin-right: 5px;" + } + }, + { + "elem": "text", + "content": "Матч! Арена" + } + ], + "label": "Каналы" + } + ], + [ + "bemjson", + "Матч! Боец", + { + "url": "https:\/\/tv.yandex.ru\/channels\/454", + "target": "_self", + "bemjson": [ + { + "elem": "icon", + "elemMods": { + "size": "l" + }, + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/square", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/small", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/40x30", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/60x45", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/alice_64_48", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/x", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802e5ce16abfff4ef6582314b\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802e5ce16abfff4ef6582314b\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/114x80", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802ed550b3e50934bd9590a8d\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/27485\/2a000001600802ed550b3e50934bd9590a8d\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/middle", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/y", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802fb1f6c51e988763d812c9a\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a000001600803041768d758a55eb7e2badb\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/30303\/2a0000016008030a3439b27b50315e0282d5\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802f430b56de4f7f74fa9d062\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802fb1f6c51e988763d812c9a\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/70787\/2a000001600803041768d758a55eb7e2badb\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/30303\/2a0000016008030a3439b27b50315e0282d5\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802f430b56de4f7f74fa9d062\/orig", + "png": "\/\/avatars.mds.yandex.net\/get-tv-channel-logos\/69315\/2a000001600802bf4bc9e92d4f31f4a9878b\/orig", + "attrs": { + "style": "margin-right: 5px;" + } + }, + { + "elem": "text", + "content": "Матч! Боец" + } + ], + "label": "Каналы" + } + ], + [ + "bemjson", + "Матч Поинт", + { + "url": "https:\/\/tv.yandex.ru\/program\/8975707?eventId=258920426", + "target": "_self", + "label": "Передачи", + "bemjson": [ + { + "elem": "info", + "content": [ + { + "elem": "img", + "elemMods": { + "size": "m" + }, + "src": "\/\/avatars.mds.yandex.net\/get-tv-shows\/28313\/2a0000019e1b8bb6911cbc36699f5e382156\/120x90" + }, + { + "elem": "text", + "elemMods": { + "type": "title-url" + }, + "content": "Матч Поинт" + }, + { + "elem": "desc", + "content": "КИНОМАН, сегодня, 18:00" + } + ] + } + ] + } + ], + [ + "bemjson", + "Матч", + { + "url": "https:\/\/tv.yandex.ru\/program\/9094736?eventId=258988710", + "target": "_self", + "label": "Передачи", + "bemjson": [ + { + "elem": "info", + "content": [ + { + "elem": "img", + "elemMods": { + "size": "m" + }, + "src": "\/\/avatars.mds.yandex.net\/get-tv-shows\/27487\/2a0000019ee0cfaf7272900dc66e5da38072\/120x90" + }, + { + "elem": "text", + "elemMods": { + "type": "title-url" + }, + "content": "Матч" + }, + { + "elem": "desc", + "content": "Союзный, завтра, 19:00" + } + ] + } + ] + } + ], + [ + "bemjson", + "Матч-реванш", + { + "url": "https:\/\/tv.yandex.ru\/program\/8045748?eventId=258901794", + "target": "_self", + "label": "Передачи", + "bemjson": [ + { + "elem": "info", + "content": [ + { + "elem": "img", + "elemMods": { + "size": "m" + }, + "src": "\/\/avatars.mds.yandex.net\/get-tv-shows\/28886\/2a0000019590a3a76b641bcec6df9bb179cc\/120x90" + }, + { + "elem": "text", + "elemMods": { + "type": "title-url" + }, + "content": "Матч-реванш" + }, + { + "elem": "desc", + "content": "Советские мультфильмы, сегодня, 20:04" + } + ] + } + ] + } + ], + [ + "bemjson", + "Матч-реванш", + { + "url": "https:\/\/tv.yandex.ru\/program\/7398398?eventId=258967472", + "target": "_self", + "label": "Передачи", + "bemjson": [ + { + "elem": "info", + "content": [ + { + "elem": "img", + "elemMods": { + "size": "m" + }, + "src": "\/\/avatars.mds.yandex.net\/get-tv-shows\/26422\/2a0000019409646c1723839178f3274f0a03\/120x90" + }, + { + "elem": "text", + "elemMods": { + "type": "title-url" + }, + "content": "Матч-реванш" + }, + { + "elem": "desc", + "content": "Чижик, завтра, 07:55" + } + ] + } + ] + } + ], + [ + "bemjson", + "Все результаты", + { + "url": "https:\/\/tv.yandex.ru\/search?text=%D0%BC%D0%B0%D1%82%D1%87", + "target": "_self", + "label": "Другое", + "bemjson": [ + { + "elem": "info", + "elemMods": { + "type": "title_url" + }, + "content": "Все результаты" + } + ] + } + ] + ] +] \ No newline at end of file diff --git a/tests/test_yandextv_api.py b/tests/test_yandextv_api.py index caf3d5b..1a7a73f 100644 --- a/tests/test_yandextv_api.py +++ b/tests/test_yandextv_api.py @@ -2,8 +2,7 @@ import datetime import pytest -from gallery.painting.yandextv.api import CHANNELS_MAP, YandexTvApi -from gallery.sketch.schedule.model import ChannelId +from gallery.painting.yandextv.api import YandexTvApi from tests.data.yandextv import YANDEXTV_MOCK_SOURCE @@ -11,13 +10,16 @@ from tests.data.yandextv import YANDEXTV_MOCK_SOURCE def yandextv_api_fixture() -> YandexTvApi: api = YandexTvApi() api.SOURCE = YANDEXTV_MOCK_SOURCE - CHANNELS_MAP[ChannelId("test")] = "test" return api +async def test_search(yandextv_api: YandexTvApi): + result = await yandextv_api.find_channels("матч") + print(result) + assert len(result) == 8 + + async def test_channel(yandextv_api: YandexTvApi): - result = await yandextv_api.get_channel_schedule( - ChannelId.TEST, datetime.date.today() - ) + result = await yandextv_api.get_channel_schedule("test", datetime.date.today()) assert result is not None assert len(result.values) > 0