feat(schedule): implement multiple schedule providers
This commit is contained in:
9
gallery/easel/depends/api.py
Normal file
9
gallery/easel/depends/api.py
Normal 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
|
||||||
9
gallery/easel/depends/schedule.py
Normal file
9
gallery/easel/depends/schedule.py
Normal 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))]
|
||||||
@@ -2,12 +2,8 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
|
|
||||||
from gallery.easel.core import AppRequest
|
|
||||||
from gallery.sketch.weather.api import WeatherApi
|
from gallery.sketch.weather.api import WeatherApi
|
||||||
|
|
||||||
|
from .api import api_resolver
|
||||||
|
|
||||||
async def get_weather_api(request: AppRequest, provider: str | None = None) -> WeatherApi:
|
WeatherApiDepends = Annotated[WeatherApi, Depends(api_resolver(WeatherApi))]
|
||||||
return request.app.state.api.get_weather(provider)
|
|
||||||
|
|
||||||
|
|
||||||
WeatherApiDepends = Annotated[WeatherApi, Depends(get_weather_api)]
|
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import datetime
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from gallery.easel.core import AppRequest
|
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 = APIRouter(prefix="/schedule")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/channels")
|
@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
|
schedule_api = request.app.state.api.schedule
|
||||||
return await schedule_api.get_channels()
|
return await schedule_api.get_channels()
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,18 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Annotated
|
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter
|
||||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
|
|
||||||
from gallery.easel.core import AppRequest
|
from gallery.easel.core import AppRequest
|
||||||
|
from gallery.easel.depends.schedule import ScheduleApiDepends
|
||||||
from gallery.sketch.schedule.api import ScheduleApi
|
from gallery.sketch.schedule.api import ScheduleApi
|
||||||
from gallery.sketch.schedule.catalog import BUNDLE
|
|
||||||
|
|
||||||
from ..common.utils.tag import TagType, TagUtil
|
from ..common.utils.tag import TagType, TagUtil
|
||||||
from ..common.utils.template import build_templates
|
from ..common.utils.template import build_templates
|
||||||
from .filters import timedelta_format
|
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:
|
def context_procesor(request: AppRequest) -> dict:
|
||||||
return {
|
return {
|
||||||
"providers": request.app.state.api.get_api_providers(ScheduleApi.TYPE),
|
"providers": request.app.state.api.get_api_providers(ScheduleApi.TYPE),
|
||||||
@@ -32,20 +24,20 @@ templates = build_templates(
|
|||||||
{
|
{
|
||||||
"timedelta_format": timedelta_format,
|
"timedelta_format": timedelta_format,
|
||||||
},
|
},
|
||||||
|
context_procesor,
|
||||||
)
|
)
|
||||||
|
|
||||||
router = APIRouter(prefix="/schedule")
|
router = APIRouter(prefix="/schedule")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_class=HTMLResponse)
|
@router.get("/", response_class=HTMLResponse)
|
||||||
async def get_schedule_list(request: AppRequest, schedule_api: ScheduleApiDepends):
|
async def get_schedule_index(request: AppRequest, schedule_api: ScheduleApiDepends, query: str | None = None):
|
||||||
channels = await schedule_api.get_channels()
|
channels = (await schedule_api.find_channels(query)) if query else []
|
||||||
channels_data = BUNDLE.select_items(channels)
|
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="index.html",
|
name="index.html",
|
||||||
context={
|
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)
|
@router.get("/tag/{tag}", response_class=HTMLResponse)
|
||||||
async def get_schedule_tag(request: AppRequest, schedule_api: ScheduleApiDepends, tag: str, live: bool = False):
|
async def get_schedule_tag(request: AppRequest, schedule_api: ScheduleApiDepends, tag: str, live: bool = False):
|
||||||
tag_value = TagUtil.parse_tag(tag)
|
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(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="schedule.html",
|
name="schedule.html",
|
||||||
context={
|
context={
|
||||||
"tag_util": TagUtil,
|
"tag_util": TagUtil,
|
||||||
"datetime": datetime,
|
"datetime": datetime,
|
||||||
"response": results[0],
|
# "response": results[0],
|
||||||
"responses": results,
|
# "responses": results,
|
||||||
"live": live,
|
"live": live,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -72,11 +64,11 @@ async def get_channel_default(channel: str):
|
|||||||
return RedirectResponse(f"{channel}/tag/today")
|
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):
|
async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: str):
|
||||||
tag_value = TagUtil.parse_tag(tag)
|
tag_value = TagUtil.parse_tag(tag)
|
||||||
if tag_value.type == TagType.DAY:
|
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:
|
else:
|
||||||
raise ValueError(tag)
|
raise ValueError(tag)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
|
|||||||
@@ -3,16 +3,60 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{_("TV program")}}</h1>
|
<h1>{{_("TV program")}}</h1>
|
||||||
<div class="list-group mb-5">
|
<form action=""
|
||||||
<a href="tag/today"
|
method="get"
|
||||||
class="list-group-item list-group-item-action px-4">
|
class="mb-4">
|
||||||
<span class="fw-bold">Все</span>
|
<div class="input-group mb-3">
|
||||||
</a>
|
<input type="text"
|
||||||
{% for channel in channels %}
|
class="form-control"
|
||||||
<a href="{{channel.id}}"
|
id="query"
|
||||||
class="list-group-item list-group-item-action px-4">
|
name="query"
|
||||||
<span class="text-primary">{{channel.name}}</span>
|
placeholder="{{_('Enter the channel name')}}">
|
||||||
</a>
|
<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 %}
|
{% 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 %}
|
{% endblock %}
|
||||||
@@ -4,7 +4,7 @@ import logging
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from gallery.sketch.schedule.api import ScheduleApi
|
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
|
from gallery.sketch.source import ApiSource
|
||||||
|
|
||||||
logger = logging.getLogger("matchtv")
|
logger = logging.getLogger("matchtv")
|
||||||
@@ -14,18 +14,10 @@ class MatchTvApi(ScheduleApi):
|
|||||||
PROVIDER = "matchtv"
|
PROVIDER = "matchtv"
|
||||||
SOURCE = ApiSource("https://matchtv.ru")
|
SOURCE = ApiSource("https://matchtv.ru")
|
||||||
|
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
return [
|
raise NotImplementedError
|
||||||
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 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}"
|
endpoint = f"tvguide/{channel_id}?date={date:%Y%m%d}"
|
||||||
data = await self.SOURCE.request(endpoint)
|
data = await self.SOURCE.request(endpoint)
|
||||||
soup = BeautifulSoup(data, features="html.parser")
|
soup = BeautifulSoup(data, features="html.parser")
|
||||||
@@ -50,4 +42,8 @@ class MatchTvApi(ScheduleApi):
|
|||||||
if prev_value is not None:
|
if prev_value is not None:
|
||||||
prev_value.end = item_date
|
prev_value.end = item_date
|
||||||
prev_value = value
|
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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,27 +1,15 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from gallery.sketch.schedule.api import ScheduleApi
|
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
|
from gallery.sketch.source import ApiSource
|
||||||
|
|
||||||
logger = logging.getLogger("matchtv")
|
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] = {
|
HEADERS: dict[str, str] = {
|
||||||
"Accept": (
|
"Accept": (
|
||||||
@@ -57,11 +45,17 @@ class YandexTvApi(ScheduleApi):
|
|||||||
PROVIDER = "yandextv"
|
PROVIDER = "yandextv"
|
||||||
SOURCE = ApiSource("https://tv.yandex.ru", headers=HEADERS)
|
SOURCE = ApiSource("https://tv.yandex.ru", headers=HEADERS)
|
||||||
|
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
return list(CHANNELS_MAP.keys())
|
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:
|
async def get_channel_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||||
endpoint = f"channel/{CHANNELS_MAP[channel_id]}?date={date:%Y-%m-%d}"
|
endpoint = f"channel/{channel_id}?date={date:%Y-%m-%d}"
|
||||||
data = await self.SOURCE.request(endpoint)
|
data = await self.SOURCE.request(endpoint)
|
||||||
soup = BeautifulSoup(data, features="html.parser")
|
soup = BeautifulSoup(data, features="html.parser")
|
||||||
if soup.select_one(".CheckboxCaptcha") is not None:
|
if soup.select_one(".CheckboxCaptcha") is not None:
|
||||||
@@ -85,4 +79,8 @@ class YandexTvApi(ScheduleApi):
|
|||||||
if prev_value is not None:
|
if prev_value is not None:
|
||||||
prev_value.end = item_date
|
prev_value.end = item_date
|
||||||
prev_value = value
|
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,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
from .api import API, Api
|
from .api import API, Api
|
||||||
from .schedule.api import ScheduleApi
|
|
||||||
from .weather.api import WeatherApi
|
|
||||||
|
|
||||||
|
|
||||||
class ApiBundle:
|
class ApiBundle:
|
||||||
@@ -21,9 +19,3 @@ class ApiBundle:
|
|||||||
if provider is None or provider == value.provider:
|
if provider is None or provider == value.provider:
|
||||||
return value
|
return value
|
||||||
raise ValueError(api_type, provider)
|
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)
|
|
||||||
|
|||||||
@@ -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]
|
|
||||||
@@ -1,25 +1,15 @@
|
|||||||
import asyncio
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from ..api import Api
|
from ..api import Api
|
||||||
from .model import ChannelId, Schedule
|
from .model import Channel, Schedule
|
||||||
|
|
||||||
|
|
||||||
class ScheduleApi(Api):
|
class ScheduleApi(Api):
|
||||||
TYPE = "schedule"
|
TYPE = "schedule"
|
||||||
INTERVAL: float = 0.5
|
INTERVAL: float = 0.5
|
||||||
|
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
raise NotImplementedError
|
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
|
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
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from gallery.sketch.cached import CachedApi, CachePreset
|
|||||||
from gallery.util import TimeUnit
|
from gallery.util import TimeUnit
|
||||||
|
|
||||||
from .api import ScheduleApi
|
from .api import ScheduleApi
|
||||||
from .model import ChannelId, Schedule
|
from .model import Channel, Schedule
|
||||||
|
|
||||||
CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6)
|
CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6)
|
||||||
|
|
||||||
@@ -15,11 +15,11 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
|||||||
CACHE_KEY = ScheduleApi.TYPE
|
CACHE_KEY = ScheduleApi.TYPE
|
||||||
|
|
||||||
@cached(
|
@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(),
|
**CACHE_PRESET._asdict(),
|
||||||
)
|
)
|
||||||
async def get_channels(self) -> list[ChannelId]:
|
async def find_channels(self, query: str) -> list[Channel]:
|
||||||
return await self._api.get_channels()
|
return await self._api.find_channels(query)
|
||||||
|
|
||||||
@cached(
|
@cached(
|
||||||
key_builder=lambda fun, self, channel_id, date: (
|
key_builder=lambda fun, self, channel_id, date: (
|
||||||
@@ -27,12 +27,5 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
|||||||
),
|
),
|
||||||
**CACHE_PRESET._asdict(),
|
**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)
|
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)
|
|
||||||
|
|||||||
@@ -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="Тест"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from enum import StrEnum
|
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -9,27 +8,10 @@ class Model(BaseModel):
|
|||||||
use_enum_values = True
|
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):
|
class Channel(Model):
|
||||||
id: ChannelId
|
id: str
|
||||||
name: str
|
name: str
|
||||||
|
provider: str
|
||||||
|
|
||||||
|
|
||||||
class ScheduleValue(Model):
|
class ScheduleValue(Model):
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ class ApiSource:
|
|||||||
self._headers = headers
|
self._headers = headers
|
||||||
|
|
||||||
async def request(self, endpoint: str) -> str:
|
async def request(self, endpoint: str) -> str:
|
||||||
|
if endpoint.startswith("https:"):
|
||||||
|
url = endpoint
|
||||||
|
else:
|
||||||
url = f"{self._base_url}/{endpoint}"
|
url = f"{self._base_url}/{endpoint}"
|
||||||
logger.info(url)
|
logger.info(url)
|
||||||
headers = {"User-Agent": self._user_agent, **(self._headers or {})}
|
headers = {"User-Agent": self._user_agent, **(self._headers or {})}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import "./language";
|
|||||||
import "./main.scss";
|
import "./main.scss";
|
||||||
import "./theme";
|
import "./theme";
|
||||||
import "./weather/weather";
|
import "./weather/weather";
|
||||||
|
import "./schedule/schedule";
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", (event) => {
|
document.addEventListener("DOMContentLoaded", (event) => {
|
||||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||||
|
|||||||
109
static/src/schedule/schedule.ts
Normal file
109
static/src/schedule/schedule.ts
Normal 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>
|
||||||
|
✕
|
||||||
|
</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();
|
||||||
@@ -6,5 +6,6 @@ YANDEXTV_MOCK_SOURCE = MockSource(
|
|||||||
Path(__file__).parent,
|
Path(__file__).parent,
|
||||||
{
|
{
|
||||||
"test": "test.html",
|
"test": "test.html",
|
||||||
|
"suggest-tv2": "suggest-tv2.json",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
423
tests/data/yandextv/suggest-tv2.json
Normal file
423
tests/data/yandextv/suggest-tv2.json
Normal 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": "Все результаты"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
@@ -2,8 +2,7 @@ import datetime
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from gallery.painting.yandextv.api import CHANNELS_MAP, YandexTvApi
|
from gallery.painting.yandextv.api import YandexTvApi
|
||||||
from gallery.sketch.schedule.model import ChannelId
|
|
||||||
from tests.data.yandextv import YANDEXTV_MOCK_SOURCE
|
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:
|
def yandextv_api_fixture() -> YandexTvApi:
|
||||||
api = YandexTvApi()
|
api = YandexTvApi()
|
||||||
api.SOURCE = YANDEXTV_MOCK_SOURCE
|
api.SOURCE = YANDEXTV_MOCK_SOURCE
|
||||||
CHANNELS_MAP[ChannelId("test")] = "test"
|
|
||||||
return api
|
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):
|
async def test_channel(yandextv_api: YandexTvApi):
|
||||||
result = await yandextv_api.get_channel_schedule(
|
result = await yandextv_api.get_channel_schedule("test", datetime.date.today())
|
||||||
ChannelId.TEST, datetime.date.today()
|
|
||||||
)
|
|
||||||
assert result is not None
|
assert result is not None
|
||||||
assert len(result.values) > 0
|
assert len(result.values) > 0
|
||||||
|
|||||||
Reference in New Issue
Block a user