feat(schedule): implement multiple schedule providers

This commit is contained in:
2026-06-26 16:36:40 +03:00
parent 8b20eb1220
commit a41b4e5308
20 changed files with 669 additions and 164 deletions

View File

@@ -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

View File

@@ -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))]

View File

@@ -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))]

View File

@@ -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()

View File

@@ -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(

View File

@@ -3,16 +3,60 @@
{% block content %}
<h1>{{_("TV program")}}</h1>
<div class="list-group mb-5">
<a href="tag/today"
class="list-group-item list-group-item-action px-4">
<span class="fw-bold">Все</span>
</a>
{% for channel in channels %}
<a href="{{channel.id}}"
class="list-group-item list-group-item-action px-4">
<span class="text-primary">{{channel.name}}</span>
</a>
<form action=""
method="get"
class="mb-4">
<div class="input-group mb-3">
<input type="text"
class="form-control"
id="query"
name="query"
placeholder="{{_('Enter the channel name')}}">
<input type="hidden"
class="form-control"
id="provider"
name="provider"
value="{{provider or providers[0]}}">
<button id="providerBtn"
class="btn btn-secondary dropdown-toggle"
type="text"
data-bs-toggle="dropdown"
aria-expanded="false">{{provider or providers[0]}}</button>
<ul class="dropdown-menu dropdown-menu-end">
{% for provider in providers %}
<li>
<a class="dropdown-item"
onclick="provider.value='{{provider}}'; providerBtn.innerText='{{provider}}'">{{provider}}</a>
</li>
{% endfor %}
</div>
</ul>
<button class="btn btn-primary"
type="submit">{{_("Search")}}</button>
</div>
</form>
{% if channels %}
<ul id="channels"
class="list-group mb-5">
{% for channel in channels %}
<schedule-channel location="{{channel.model_dump() | tojson | forceescape}}"></schedule-channel>
{% endfor %}
</ul>
<hr>
{% endif %}
<ul id="storedChannels"
class="list-group mb-5">
</ul>
<script>
(function () {
const params = new URLSearchParams(window.location.search);
const searchQuery = params.get('query');
if (searchQuery) {
document.querySelector('#query').value = searchQuery;
}
document.addEventListener("DOMContentLoaded", (event) => {
scheduleChannelManager.loadChannels('#storedChannels');
});
})();
</script>
{% endblock %}

View File

@@ -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,
)

View File

@@ -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,
)

View File

@@ -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)

View File

@@ -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]

View File

@@ -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

View File

@@ -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)

View File

@@ -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="Тест"),
]
)

View File

@@ -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):

View File

@@ -26,6 +26,9 @@ class ApiSource:
self._headers = headers
async def request(self, endpoint: str) -> str:
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 {})}

View File

@@ -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"]');

View File

@@ -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 = `
<a href="${this.channel?.provider}/${this.channel?.id}"
class="list-group-item list-group-item-action px-4 d-flex justify-content-between align-items-start">
<span>
<span class="text-primary">${this.channel?.name}</span>
</span>
<span>
<span class="badge text-bg-secondary">${this.channel?.provider}</span>
<span class="text-danger ms-2" data-remove>
&#x2715;
</span>
</span>
</a>`;
this.addEventListener("click", this.handleClick);
if (this.hasAttribute("removable")) {
this.querySelector<HTMLElement>("[data-remove]")?.addEventListener("click", this.handleRemoveClick);
} else {
this.querySelector("[data-remove]")?.remove();
}
}
disconnectedCallback() {
this.removeEventListener("click", this.handleClick);
this.querySelector<HTMLElement>("[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();

View File

@@ -6,5 +6,6 @@ YANDEXTV_MOCK_SOURCE = MockSource(
Path(__file__).parent,
{
"test": "test.html",
"suggest-tv2": "suggest-tv2.json",
},
)

View File

@@ -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": "Все результаты"
}
]
}
]
]
]

View File

@@ -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