Compare commits
35 Commits
a715bcca89
...
0.4.2
| Author | SHA1 | Date | |
|---|---|---|---|
| bad5dff2b2 | |||
| e2af31b812 | |||
| 6c76dae760 | |||
| 28951df9b5 | |||
| 5aa290c772 | |||
| 13a70990e5 | |||
| a3fab92e3d | |||
| 4fdb7e9717 | |||
| 1d13f6e05d | |||
| fcb000afb0 | |||
| 0730b9f567 | |||
| 081aa8d17f | |||
| 29fe06462e | |||
| 8b20eb1220 | |||
| 5333690c47 | |||
| d82fc4ea46 | |||
| df2f1d0d81 | |||
| 9192ca3e4a | |||
| e37ca6eeac | |||
| 657a62eab5 | |||
| d8e2be013a | |||
| a91ec12d22 | |||
| 9c862863e5 | |||
| fc59e52337 | |||
| 3e93a41400 | |||
| c2cd18386b | |||
| 2bca3dd75a | |||
| 469bd9bc1f | |||
| 027d1e2d55 | |||
| 7cf0012229 | |||
| edc014d98c | |||
| 1813ec213b | |||
| 1b700086f2 | |||
| 02a6ffc931 | |||
| 2dfedfea57 |
@@ -2,4 +2,7 @@ DOCKER_REPO=git.shmyga.ru
|
||||
DOCKER_GROUP=infernalgames
|
||||
DOCKER_ROOT="$DOCKER_REPO/$DOCKER_GROUP"
|
||||
VERSION=$(grep -m 1 'version' ./pyproject.toml | grep -oP 'version\s*=\s*"\K[^"]+')
|
||||
DOCKER_PROJECTS=("gallery")
|
||||
PYTHON_VERSION=$(grep -m 1 'python' ./pyproject.toml | grep -oP 'python\s*=\s*"\^?\K[^"]+')
|
||||
DOCKER_PROJECTS=("gallery")
|
||||
|
||||
OPENWEATHER_KEY="<EMPTY>"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,4 +4,5 @@
|
||||
.venv
|
||||
#.vscode
|
||||
static/node_modules
|
||||
static/dist
|
||||
static/dist
|
||||
.env
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,13 +1,15 @@
|
||||
FROM python:3.12 AS builder
|
||||
FROM python:3.14 AS builder
|
||||
ENV POETRY_HOME="/opt/poetry"
|
||||
ENV PATH="$POETRY_HOME/bin:$PATH"
|
||||
RUN apt update && \
|
||||
apt install -y gettext
|
||||
apt install -y gettext
|
||||
WORKDIR /app
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
COPY pyproject.toml poetry.lock README.md ./
|
||||
RUN poetry config virtualenvs.in-project true
|
||||
RUN poetry install --with app --no-root
|
||||
RUN --mount=type=cache,target=/root/.cache/pypoetry/cache \
|
||||
--mount=type=cache,target=/root/.cache/pypoetry/artifacts \
|
||||
poetry install --with app --no-root
|
||||
COPY locales ./locales
|
||||
RUN cd locales/ru/LC_MESSAGES && msgfmt messages.po
|
||||
|
||||
@@ -15,11 +17,12 @@ FROM node:24 AS node-builder
|
||||
ENV PATH=/app/node_modules/.bin:$PATH
|
||||
WORKDIR /app
|
||||
COPY static/package.json static/package-lock.json ./
|
||||
RUN npm ci
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci
|
||||
COPY static ./
|
||||
RUN npm run build
|
||||
|
||||
FROM python:3.12-slim
|
||||
FROM python:3.14-slim
|
||||
ENV PATH="/app/.venv/bin:$PATH"
|
||||
WORKDIR /app
|
||||
|
||||
@@ -27,7 +30,6 @@ ENV TZ="Europe/Moscow"
|
||||
COPY --from=builder /app ./
|
||||
COPY --from=node-builder /app/dist ./static/dist
|
||||
COPY gallery gallery/
|
||||
#COPY --from=builder /app/gallery/easel/route/view/locales /app/gallery/easel/route/view/locales
|
||||
COPY --from=builder --parents locales/**/*.mo ./
|
||||
|
||||
CMD ["uvicorn", "gallery.main:app", "--host", "0.0.0.0", "--port", "80", "--log-config", "gallery/logging.yaml"]
|
||||
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
build: .
|
||||
environment:
|
||||
- REDIS_HOST=redis
|
||||
- OPENWEATHER_KEY=$OPENWEATHER_KEY
|
||||
- DEBUG=1
|
||||
ports:
|
||||
- 8000:80
|
||||
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
image: ${DOCKER_ROOT}/gallery
|
||||
environment:
|
||||
- REDIS_HOST=redis
|
||||
- OPENWEATHER_KEY=$OPENWEATHER_KEY
|
||||
depends_on:
|
||||
- redis
|
||||
ports:
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 182 KiB |
@@ -8,6 +8,7 @@
|
||||
"python.testing.pytestArgs": ["tests", "-s"],
|
||||
"python.testing.unittestEnabled": false,
|
||||
"python.testing.pytestEnabled": true,
|
||||
"python.terminal.useEnvFile": true,
|
||||
"python-envs.pythonProjects": [
|
||||
{
|
||||
"path": ".",
|
||||
|
||||
@@ -4,8 +4,7 @@ from fastapi.staticfiles import StaticFiles
|
||||
from gallery.sketch.bundle import ApiBundle
|
||||
from gallery.util import root_path
|
||||
|
||||
from .route import api, doc
|
||||
from .route.view import router as view_router
|
||||
from .route import api, doc, view
|
||||
|
||||
|
||||
def build_app(api_bundle: ApiBundle) -> FastAPI:
|
||||
@@ -17,6 +16,6 @@ def build_app(api_bundle: ApiBundle) -> FastAPI:
|
||||
app.state.api = api_bundle
|
||||
app.mount("/static", StaticFiles(directory=root_path / "static/dist"))
|
||||
doc.mount(app)
|
||||
api.mount(app)
|
||||
app.include_router(view_router)
|
||||
app.include_router(api.router)
|
||||
app.include_router(view.router)
|
||||
return app
|
||||
|
||||
0
gallery/easel/depends/__init__.py
Normal file
0
gallery/easel/depends/__init__.py
Normal file
17
gallery/easel/depends/api.py
Normal file
17
gallery/easel/depends/api.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
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) -> API:
|
||||
providers = request.app.state.api.get_api_providers(api_type)
|
||||
if provider not in providers:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={"provider": f"'{provider}' not in {providers}"},
|
||||
)
|
||||
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))]
|
||||
9
gallery/easel/depends/weather.py
Normal file
9
gallery/easel/depends/weather.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from gallery.sketch.weather.api import WeatherApi
|
||||
|
||||
from .api import api_resolver
|
||||
|
||||
WeatherApiDepends = Annotated[WeatherApi, Depends(api_resolver(WeatherApi))]
|
||||
@@ -1,8 +1,7 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi import APIRouter
|
||||
|
||||
from . import schedule, weather
|
||||
|
||||
|
||||
def mount(app: FastAPI):
|
||||
weather.mount(app)
|
||||
schedule.mount(app)
|
||||
router = APIRouter(prefix="/api")
|
||||
router.include_router(weather.router)
|
||||
router.include_router(schedule.router)
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
import datetime
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import APIRouter
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.sketch.schedule.model import ChannelId, Schedule
|
||||
from gallery.easel.depends.schedule import ScheduleApiDepends
|
||||
from gallery.sketch.schedule.api import ScheduleApi
|
||||
from gallery.sketch.schedule.model import Channel, Schedule
|
||||
|
||||
router = APIRouter(prefix="/schedule", tags=["Schedule"])
|
||||
|
||||
|
||||
def mount(app: FastAPI):
|
||||
@app.get("/api/schedule/channels", tags=["API"])
|
||||
async def get_api_schedule_channels(request: AppRequest) -> list[ChannelId]:
|
||||
schedule_api = request.app.state.api.schedule
|
||||
return await schedule_api.get_channels()
|
||||
@router.get("/providers")
|
||||
async def get_api_weather_providers(request: AppRequest) -> list[str]:
|
||||
return request.app.state.api.get_api_providers(ScheduleApi)
|
||||
|
||||
@app.get("/api/schedule/{channel}/{date}", tags=["API"])
|
||||
async def get_api_schedule_channel_schedule(request: AppRequest, channel: str, date: datetime.date) -> Schedule:
|
||||
schedule_api = request.app.state.api.schedule
|
||||
return await schedule_api.get_channel_schedule(ChannelId(channel), date)
|
||||
|
||||
@router.get("/{provider}/channels")
|
||||
async def find_api_schedule_channels(schedule_api: ScheduleApiDepends, query: str) -> list[Channel]:
|
||||
return await schedule_api.find_channels(query)
|
||||
|
||||
|
||||
@router.get("/{provider}/{channel}/{date}")
|
||||
async def get_api_schedule_channel_schedule(
|
||||
schedule_api: ScheduleApiDepends, channel: str, date: datetime.date
|
||||
) -> Schedule:
|
||||
return await schedule_api.get_schedule(channel, date)
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import datetime
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import APIRouter
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.easel.depends.weather import WeatherApiDepends
|
||||
from gallery.sketch.weather.api import WeatherApi
|
||||
from gallery.sketch.weather.model import Location, WeatherResponse
|
||||
|
||||
router = APIRouter(prefix="/weather", tags=["Weather"])
|
||||
|
||||
def mount(app: FastAPI):
|
||||
@app.get("/api/weather/locations", tags=["API"])
|
||||
async def get_api_weather_locations(request: AppRequest, query: str) -> list[Location]:
|
||||
weather_api = request.app.state.api.weather
|
||||
return await weather_api.find_locations(query)
|
||||
|
||||
@app.get("/api/weather/{location}/day/{date}", tags=["API"])
|
||||
async def get_api_weather_day(request: AppRequest, location: str, date: datetime.date) -> WeatherResponse:
|
||||
weather_api = request.app.state.api.weather
|
||||
return await weather_api.get_day(location, date)
|
||||
@router.get("/providers")
|
||||
async def get_api_weather_providers(request: AppRequest) -> list[str]:
|
||||
return request.app.state.api.get_api_providers(WeatherApi)
|
||||
|
||||
@app.get("/api/weather/{location}/days/{days}", tags=["API"])
|
||||
async def get_api_weather_days(request: AppRequest, location: str, days: int) -> WeatherResponse:
|
||||
weather_api = request.app.state.api.weather
|
||||
return await weather_api.get_days(location, days)
|
||||
|
||||
@router.get("/{provider}/locations")
|
||||
async def find_api_weather_locations(weather_api: WeatherApiDepends, query: str) -> list[Location]:
|
||||
return await weather_api.find_locations(query)
|
||||
|
||||
|
||||
@router.get("/{provider}/{location}/day/{date}")
|
||||
async def get_api_weather_day(weather_api: WeatherApiDepends, location: str, date: datetime.date) -> WeatherResponse:
|
||||
return await weather_api.get_day(location, date)
|
||||
|
||||
|
||||
@router.get("/{provider}/{location}/days/{days}")
|
||||
async def get_api_weather_days(weather_api: WeatherApiDepends, location: str, days: int) -> WeatherResponse:
|
||||
return await weather_api.get_days(location, days)
|
||||
|
||||
636
gallery/easel/route/doc/static/redoc.standalone.js
vendored
636
gallery/easel/route/doc/static/redoc.standalone.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from .common import router as common_router
|
||||
from .root import router as root_router
|
||||
from .schedule import router as schedule_router
|
||||
from .translation import set_language
|
||||
from .weather import router as weather_router
|
||||
|
||||
router = APIRouter(dependencies=[Depends(set_language)])
|
||||
router.include_router(common_router)
|
||||
router = APIRouter(tags=["view"], dependencies=[Depends(set_language)], include_in_schema=False)
|
||||
router.include_router(root_router)
|
||||
router.include_router(weather_router)
|
||||
router.include_router(schedule_router)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from ..common.utils.template import build_templates
|
||||
|
||||
|
||||
class Section(NamedTuple):
|
||||
link: str
|
||||
title: str
|
||||
icon: str
|
||||
|
||||
|
||||
SECTIONS = [
|
||||
Section("weather", "Weather", "brightness-high"),
|
||||
Section("schedule", "TV program", "tv"),
|
||||
]
|
||||
|
||||
base_dir = Path(__file__).parent
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
templates = build_templates()
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def get_section_list(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="root_index.html",
|
||||
context={
|
||||
"sections": SECTIONS,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
aria-expanded="false"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="{{_('Select language')}} (default)">
|
||||
<span class="fi fir fi-gb me-2 language-icon-active"></span>
|
||||
<span class="fi fir fi-gb me-2 language-icon-active icon-header"></span>
|
||||
<span class="d-lg-none ms-2"
|
||||
id="bd-language-text">{{_("Select language")}}</span>
|
||||
</button>
|
||||
@@ -70,7 +70,7 @@
|
||||
aria-expanded="false"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-label="Toggle theme (auto)">
|
||||
<span class="bi bi-circle-half me-2 opacity-50 theme-icon-active"></span>
|
||||
<span class="bi bi-circle-half me-2 opacity-50 theme-icon-active icon-header"></span>
|
||||
<span class="d-lg-none ms-2"
|
||||
id="bd-theme-text">{{_("Toggle theme")}}</span>
|
||||
</button>
|
||||
@@ -112,8 +112,11 @@
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
{% if not is_widget %}
|
||||
<footer class="pt-5 my-5 text-muted border-top">
|
||||
Created by shmyga · © 2026
|
||||
<footer class="app-footer pt-5 my-5 text-muted border-top">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Created by shmyga · © 2026</span>
|
||||
<span>v{{ version }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{_("Index")}}{% endblock %}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{_("View")}}</h1>
|
||||
<div class="list-group mb-5">
|
||||
{% for section in sections %}
|
||||
<a href="{{section.link}}"
|
||||
class="list-group-item list-group-item-action px-4">
|
||||
<app-link href="{{section.link}}"
|
||||
icon="{{section.icon}}">
|
||||
{{_(section.title)}}
|
||||
</app-link>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<hr class="col-3 col-md-2 mb-5">
|
||||
<h1>{{_("Docs")}}</h1>
|
||||
<a href="/docs"
|
||||
target="_blank">
|
||||
<h4>Swagger</h4>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -1,12 +1,18 @@
|
||||
import datetime
|
||||
import typing
|
||||
from pathlib import Path
|
||||
|
||||
from babel.dates import format_date
|
||||
from fastapi import Request
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.version import __version__
|
||||
|
||||
from ...translation import _
|
||||
from .tag import TagUtil
|
||||
|
||||
ContextProcessor = typing.Callable[[AppRequest], dict[str, typing.Any]]
|
||||
|
||||
|
||||
def is_widget(request: Request) -> bool:
|
||||
@@ -15,22 +21,31 @@ def is_widget(request: Request) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def context_processor(request: Request) -> dict:
|
||||
def base_context_processor(request: Request) -> dict:
|
||||
return {
|
||||
"is_widget": is_widget(request),
|
||||
"provider": request.query_params.get("provider"),
|
||||
}
|
||||
|
||||
|
||||
def build_templates(templates_dir: Path | None = None, filters: dict | None = None) -> Jinja2Templates:
|
||||
def build_templates(
|
||||
templates_dir: Path | None = None, filters: dict | None = None, context_processor: ContextProcessor | None = None
|
||||
) -> Jinja2Templates:
|
||||
directory = [Path(__file__).parent.parent / "templates"]
|
||||
if templates_dir:
|
||||
directory.append(templates_dir)
|
||||
templates = Jinja2Templates(directory=directory, context_processors=[context_processor])
|
||||
context_processors: list[ContextProcessor] = [base_context_processor]
|
||||
if context_processor:
|
||||
context_processors.append(context_processor)
|
||||
templates = Jinja2Templates(directory=directory, context_processors=context_processors)
|
||||
templates.env.globals.update(
|
||||
{
|
||||
"_": _,
|
||||
"version": __version__,
|
||||
"format_date": format_date,
|
||||
"datetime": datetime,
|
||||
"tag_util": TagUtil,
|
||||
"DATE_FORMAT": "E, d MMMM Y",
|
||||
}
|
||||
)
|
||||
if filters:
|
||||
|
||||
36
gallery/easel/route/view/root/__init__.py
Normal file
36
gallery/easel/route/view/root/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from pathlib import Path
|
||||
from typing import NamedTuple
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from ..common.utils.template import build_templates
|
||||
|
||||
|
||||
class Section(NamedTuple):
|
||||
link: str
|
||||
title: str
|
||||
icon: str
|
||||
|
||||
|
||||
SECTIONS = [
|
||||
Section("weather", "Weather", "brightness-high"),
|
||||
Section("schedule", "TV program", "tv"),
|
||||
]
|
||||
|
||||
base_dir = Path(__file__).parent
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
templates = build_templates(Path(__file__).parent / "templates")
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def get_section_list(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="root_index.html",
|
||||
context={
|
||||
"sections": SECTIONS,
|
||||
},
|
||||
)
|
||||
33
gallery/easel/route/view/root/templates/root_index.html
Normal file
33
gallery/easel/route/view/root/templates/root_index.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}{{_("Index")}}{% endblock %}
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{_("View")}}</h1>
|
||||
<div class="list-group mb-3">
|
||||
{% for section in sections %}
|
||||
<app-link href="{{section.link}}"
|
||||
icon="{{section.icon}}"
|
||||
class="list-group-item list-group-item-action">
|
||||
{{_(section.title)}}
|
||||
</app-link>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<h1>{{_("Docs")}}</h1>
|
||||
<div class="list-group mb-3">
|
||||
<app-link href="/docs"
|
||||
target="_blank"
|
||||
icon="file-earmark-text"
|
||||
class="list-group-item list-group-item-action">
|
||||
Swagger UI
|
||||
</app-link>
|
||||
<app-link href="/redoc"
|
||||
target="_blank"
|
||||
icon="file-earmark-text"
|
||||
class="list-group-item list-group-item-action">
|
||||
ReDoc
|
||||
</app-link>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -5,65 +5,58 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.sketch.schedule.catalog import BUNDLE
|
||||
from gallery.easel.depends.api import api_resolver
|
||||
from gallery.easel.depends.schedule import ScheduleApiDepends
|
||||
from gallery.sketch.schedule.api import ScheduleApi
|
||||
|
||||
from ..common.utils.tag import TagType, TagUtil
|
||||
from ..common.utils.template import build_templates
|
||||
from .filters import timedelta_format
|
||||
|
||||
|
||||
def context_procesor(request: AppRequest) -> dict:
|
||||
return {
|
||||
"providers": request.app.state.api.get_api_providers(ScheduleApi),
|
||||
}
|
||||
|
||||
|
||||
templates = build_templates(
|
||||
Path(__file__).parent / "templates",
|
||||
{
|
||||
"timedelta_format": timedelta_format,
|
||||
},
|
||||
context_procesor,
|
||||
)
|
||||
|
||||
router = APIRouter()
|
||||
router = APIRouter(prefix="/schedule")
|
||||
|
||||
|
||||
@router.get("/schedule", response_class=HTMLResponse)
|
||||
async def get_schedule_list(request: AppRequest):
|
||||
schedule_api = request.app.state.api.schedule
|
||||
channels = await schedule_api.get_channels()
|
||||
channels_data = BUNDLE.select_items(channels)
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def get_schedule_index(request: AppRequest, provider: str | None = None, query: str | None = None):
|
||||
if query and provider:
|
||||
schedule_api = api_resolver(ScheduleApi)(request, provider)
|
||||
channels = await schedule_api.find_channels(query)
|
||||
else:
|
||||
channels = []
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="index.html",
|
||||
context={
|
||||
"channels": channels_data,
|
||||
"channels": channels,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/schedule/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_schedule_tag(request: AppRequest, tag: str, live: bool = False):
|
||||
tag_value = TagUtil.parse_tag(tag)
|
||||
schedule_api = request.app.state.api.schedule
|
||||
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,
|
||||
"live": live,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/schedule/{channel}", response_class=RedirectResponse)
|
||||
@router.get("/{provider}/{channel}", response_class=RedirectResponse)
|
||||
async def get_channel_default(channel: str):
|
||||
return RedirectResponse(f"{channel}/tag/today")
|
||||
|
||||
|
||||
@router.get("/schedule/{channel}/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_channel_tag(request: AppRequest, channel: str, tag: str):
|
||||
@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)
|
||||
schedule_api = request.app.state.api.schedule
|
||||
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(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
{{_("TV program")}} | {{response.channel.name}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}
|
||||
{{_("TV program")}} | {{response.channel.name}} | {{format_date(response.date, DATE_FORMAT,
|
||||
locale=request.state.language)}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
@@ -10,27 +11,34 @@
|
||||
|
||||
{% block content %}
|
||||
<h4>
|
||||
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||
<a class="button"
|
||||
href="../..">⬆️</a>
|
||||
<span>{{response.channel.name}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<a class="button"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||
<a class="icon-link {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">
|
||||
<i class="bi bi-arrow-left-square"></i>
|
||||
</a>
|
||||
<a class="icon-link"
|
||||
href="../../..">
|
||||
<i class="bi bi-arrow-up-square"></i>
|
||||
</a>
|
||||
<span>{{response.channel.name}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||
<a class="icon-link"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">
|
||||
<i class="bi bi-arrow-right-square"></i>
|
||||
</a>
|
||||
</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for value in response.values %}
|
||||
<tr class="{{'table-success' if value.live else ''}}">
|
||||
<td>{{value.start.strftime('%H:%M')}}</td>
|
||||
<td>{{(value.end - value.start) | timedelta_format}}</td>
|
||||
<td>
|
||||
<span>{{value.start.strftime('%H:%M')}}</span>
|
||||
<span class="small ms-1">({{(value.end - value.start) | timedelta_format}})</span>
|
||||
</td>
|
||||
<td>{{value.label}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
@@ -3,16 +3,60 @@
|
||||
|
||||
{% block content %}
|
||||
<h1>{{_("TV program")}}</h1>
|
||||
<div class="list-group mb-5">
|
||||
<a href="schedule/tag/today"
|
||||
class="list-group-item list-group-item-action px-4">
|
||||
<span class="fw-bold">Все</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 %}
|
||||
</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 %}
|
||||
<a href="schedule/{{channel.id}}"
|
||||
class="list-group-item list-group-item-action px-4">
|
||||
<span class="text-primary">{{channel.name}}</span>
|
||||
</a>
|
||||
<schedule-channel channel="{{channel.model_dump() | tojson | forceescape}}"></schedule-channel>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</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 %}
|
||||
@@ -1,51 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
{{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<app-link href="/schedule"
|
||||
icon="tv">{{_("TV program")}}</app-link>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h4>
|
||||
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||
<a class="button"
|
||||
href="..">⬆️</a>
|
||||
<span>{{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<a class="button"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||
</h4>
|
||||
<div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for response in responses %}
|
||||
{% set values = (response.values|selectattr('live') if live else response.values)|list %}
|
||||
{% if values|length > 0 %}
|
||||
<tr class="table-primary fs-4">
|
||||
<td colspan="3">
|
||||
<div>{{response.channel.name}}</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% for value in values %}
|
||||
<tr class="{{'table-success' if not live and value.live else ''}}">
|
||||
<td>{{value.start.strftime('%H:%M')}}</td>
|
||||
<td>{{(value.end - value.start) | timedelta_format}}</td>
|
||||
<td>{{value.label}}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -5,18 +5,29 @@ from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.easel.depends.api import api_resolver
|
||||
from gallery.easel.depends.weather import WeatherApiDepends
|
||||
from gallery.sketch.weather.api import WeatherApi
|
||||
from gallery.sketch.weather.model import WeatherResponse
|
||||
|
||||
from ..common.utils.tag import TagType, TagUtil
|
||||
from ..common.utils.template import build_templates
|
||||
from .filters import cloudness_icon, wind_direction_icon
|
||||
|
||||
|
||||
def context_procesor(request: AppRequest) -> dict:
|
||||
return {
|
||||
"providers": request.app.state.api.get_api_providers(WeatherApi),
|
||||
}
|
||||
|
||||
|
||||
templates = build_templates(
|
||||
Path(__file__).parent / "templates",
|
||||
{
|
||||
"wind_direction_icon": wind_direction_icon,
|
||||
"cloudness_icon": cloudness_icon,
|
||||
},
|
||||
context_procesor,
|
||||
)
|
||||
|
||||
|
||||
@@ -25,20 +36,21 @@ def build_weather_response(request: AppRequest, response: WeatherResponse):
|
||||
request=request,
|
||||
name="weather.html",
|
||||
context={
|
||||
"tag_util": TagUtil,
|
||||
"datetime": datetime,
|
||||
"response": response,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
router = APIRouter(prefix="/weather")
|
||||
|
||||
|
||||
@router.get("/weather", response_class=HTMLResponse)
|
||||
async def get_weather_index(request: AppRequest, query: str | None = None):
|
||||
weather_api = request.app.state.api.weather
|
||||
locations = (await weather_api.find_locations(query)) if query else []
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def get_weather_index(request: AppRequest, provider: str | None = None, query: str | None = None):
|
||||
if query and provider:
|
||||
weather_api = api_resolver(WeatherApi)(request, provider)
|
||||
locations = await weather_api.find_locations(query)
|
||||
else:
|
||||
locations = []
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="index.html",
|
||||
@@ -48,29 +60,31 @@ async def get_weather_index(request: AppRequest, query: str | None = None):
|
||||
)
|
||||
|
||||
|
||||
@router.get("/weather/{location}", response_class=RedirectResponse)
|
||||
async def get_weather_default(location: str):
|
||||
@router.get("/{provider}/{location}", response_class=RedirectResponse)
|
||||
async def get_weather(location: str):
|
||||
return RedirectResponse(f"{location}/tag/today")
|
||||
|
||||
|
||||
@router.get("/weather/{location}/day/{date}", response_class=HTMLResponse)
|
||||
async def get_weather_day(request: AppRequest, location: str, date: datetime.date):
|
||||
weather_api = request.app.state.api.weather
|
||||
@router.get("/{provider}/{location}/day/{date}", response_class=HTMLResponse)
|
||||
async def get_weather_day(
|
||||
request: AppRequest,
|
||||
weather_api: WeatherApiDepends,
|
||||
location: str,
|
||||
date: datetime.date,
|
||||
):
|
||||
response = await weather_api.get_day(location, date)
|
||||
return build_weather_response(request, response)
|
||||
|
||||
|
||||
@router.get("/weather/{location}/days/{days}", response_class=HTMLResponse)
|
||||
async def get_weather_days(request: AppRequest, location: str, days: int):
|
||||
weather_api = request.app.state.api.weather
|
||||
@router.get("/{provider}/{location}/days/{days}", response_class=HTMLResponse)
|
||||
async def get_weather_days(request: AppRequest, weather_api: WeatherApiDepends, location: str, days: int):
|
||||
response = await weather_api.get_days(location, days)
|
||||
return build_weather_response(request, response)
|
||||
|
||||
|
||||
@router.get("/weather/{location}/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_weather_tag(request: AppRequest, location: str, tag: str):
|
||||
@router.get("/{provider}/{location}/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_weather_tag(request: AppRequest, weather_api: WeatherApiDepends, location: str, tag: str):
|
||||
tag_value = TagUtil.parse_tag(tag)
|
||||
weather_api = request.app.state.api.weather
|
||||
if tag_value.type == TagType.DAY:
|
||||
response = await weather_api.get_day(location, tag_value.date)
|
||||
elif tag_value.type == TagType.DAYS:
|
||||
@@ -78,3 +92,10 @@ async def get_weather_tag(request: AppRequest, location: str, tag: str):
|
||||
else:
|
||||
raise ValueError(tag)
|
||||
return build_weather_response(request, response)
|
||||
|
||||
|
||||
@router.get("/zmiyevka-184640/tag/{tag}", response_class=RedirectResponse)
|
||||
async def get_zmiyevka_weather_compat(request: AppRequest):
|
||||
path_parts = request.url.path.split("/")
|
||||
path_parts.insert(2, "gismeteo")
|
||||
return RedirectResponse("/".join(path_parts))
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import datetime
|
||||
|
||||
from gallery.sketch.weather.model import Cloudness, Precipitation, Sky, WindDirection, WindDirectionDeg
|
||||
from gallery.sketch.weather.model import (
|
||||
Cloudness,
|
||||
Precipitation,
|
||||
Sky,
|
||||
WindDirection,
|
||||
WindDirectionDeg,
|
||||
)
|
||||
|
||||
|
||||
def wind_direction_icon(wind_direction_deg: float) -> str:
|
||||
@@ -8,7 +14,7 @@ def wind_direction_icon(wind_direction_deg: float) -> str:
|
||||
if wind_direction == WindDirection.CALM:
|
||||
return "wind-calm"
|
||||
else:
|
||||
return f"wind-from-{wind_direction.name.lower()}"
|
||||
return f"wind-from-{wind_direction.name.lower()}"
|
||||
|
||||
|
||||
def cloudness_icon(sky: Sky, date: datetime.datetime, period: str) -> list[str]:
|
||||
@@ -42,6 +48,7 @@ def cloudness_icon(sky: Sky, date: datetime.datetime, period: str) -> list[str]:
|
||||
Precipitation.SHOWER: "rain",
|
||||
Precipitation.SNOW: "snow",
|
||||
Precipitation.HEAVY_SNOW: "snow",
|
||||
Precipitation.HAIL: "hail",
|
||||
}[sky.precipitation]
|
||||
if sky.cloudness == Cloudness.PARTLY_CLOUDY:
|
||||
main_icon = f"{day_prefix}-{main_icon}"
|
||||
|
||||
@@ -12,63 +12,51 @@
|
||||
id="query"
|
||||
name="query"
|
||||
placeholder="{{_('Enter the city 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 %}
|
||||
</ul>
|
||||
<button class="btn btn-primary"
|
||||
type="submit">{{_("Search")}}</button>
|
||||
</div>
|
||||
</form>
|
||||
{% if locations %}
|
||||
<ul id="locations"
|
||||
class="list-group mb-5">
|
||||
{% for location in locations %}
|
||||
<a href="weather/{{location.id}}"
|
||||
class="list-group-item list-group-item-action px-4"
|
||||
onclick="saveLocation({id:'{{location.id}}', name:'{{location.name}}'});">
|
||||
<span class="fi fi-{{location.country_code}} me-1"></span>
|
||||
<span class="text-primary">{{location.name}}</span>
|
||||
<span class="small ms-1 text-secondary">
|
||||
{{location.country}}, {{location.district}}, {{location.subdistrict}}
|
||||
</span>
|
||||
<span></span>
|
||||
</a>
|
||||
<weather-location location="{{location.model_dump() | tojson | forceescape}}"></weather-location>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<hr>
|
||||
{% endif %}
|
||||
<ul id="storedLocations"
|
||||
class="list-group mb-5">
|
||||
</ul>
|
||||
<script>
|
||||
(function () {
|
||||
document.loadLocations = () => {
|
||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
||||
const container = document.querySelector('#locations');
|
||||
container.innerHTML = '';
|
||||
for (const [id, name] of Object.entries(locations)) {
|
||||
const element = document.createElement('a');
|
||||
element.href = `weather/${id}`;
|
||||
element.className = 'list-group-item list-group-item-action px-4 d-flex justify-content-between align-items-start';
|
||||
element.innerHTML = `
|
||||
<span class="text-primary me-auto">${name}</span>
|
||||
<span class="text-danger" onclick="removeLocation('${id}'); event.preventDefault();">✕</span>
|
||||
`;
|
||||
container.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
document.saveLocation = (location) => {
|
||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
||||
locations[location.id] = location.name;
|
||||
window.localStorage.setItem('locations', JSON.stringify(locations));
|
||||
}
|
||||
|
||||
document.removeLocation = (id) => {
|
||||
const locations = JSON.parse(window.localStorage.getItem('locations') || '{}');
|
||||
delete locations[id];
|
||||
window.localStorage.setItem('locations', JSON.stringify(locations));
|
||||
document.loadLocations();
|
||||
}
|
||||
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const searchQuery = params.get('query');
|
||||
if (searchQuery) {
|
||||
document.querySelector('#query').value = searchQuery;
|
||||
} else {
|
||||
document.loadLocations();
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (event) => {
|
||||
weatherLocationManager.loadLocations('#storedLocations');
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -9,16 +9,24 @@
|
||||
{% block content %}
|
||||
<h4>
|
||||
{% if response.period == 'day' %}
|
||||
<a class="button {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">⬅️</a>
|
||||
<a class="button"
|
||||
href="../tag/days-10">⬆️</a>
|
||||
<span>{{response.location}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<a class="button"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">➡️</a>
|
||||
<a class="icon-link {{'disabled' if response.date == datetime.date.today() else ''}}"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, -1)}}">
|
||||
<i class="bi bi-arrow-left-square"></i>
|
||||
</a>
|
||||
<a class="icon-link"
|
||||
href="../tag/days-10">
|
||||
<i class="bi bi-arrow-up-square"></i>
|
||||
</a>
|
||||
<span>{{response.location}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||
<a class="icon-link"
|
||||
href="../tag/{{tag_util.create_tag('day', response.date, 1)}}">
|
||||
<i class="bi bi-arrow-right-square"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if response.period == 'days' %}
|
||||
<span>{{response.location}} | {{format_date(response.date, 'E, d MMMM Y', locale=request.state.language)}}</span>
|
||||
<span>{{response.location}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||
<span>- {{format_date(response.date + datetime.timedelta(days=(response.values | length - 1)), DATE_FORMAT,
|
||||
locale=request.state.language)}}</span>
|
||||
{% endif %}
|
||||
</h4>
|
||||
<div class="table-responsive">
|
||||
@@ -58,8 +66,7 @@
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{{ value.sky }}">
|
||||
{% for icon in value.sky | cloudness_icon(value.date, response.period) %}
|
||||
<div class="wi wi-{{icon}} text-primary"
|
||||
style="font-size: 3.5rem;"></div>
|
||||
<div class="wi wi-{{icon}} wi-xl text-primary"></div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
@@ -95,7 +102,7 @@
|
||||
<td class="wind"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-title="{{ value.wind_direction }}">
|
||||
<div class="wi wi-wind-deg wi-{{value.wind_direction | wind_direction_icon}} fs-1 text-primary"></div>
|
||||
<div class="wi wi-wind-deg wi-{{value.wind_direction | wind_direction_icon}} wi-l text-primary"></div>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
@@ -130,7 +137,7 @@
|
||||
{% for value in response.values %}
|
||||
<td class="precipitation"
|
||||
style="background-color: rgba(0, 128, 255, {{value.precipitation * 0.1}});">
|
||||
<span class="value">{{value.precipitation or ' '}}</span>
|
||||
<span class="value">{{(value.precipitation | round(2)) if value.precipitation else ' '}}</span>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
|
||||
@@ -85,11 +85,14 @@ class GismeteoApi(WeatherApi):
|
||||
Location(
|
||||
id=f"{item['slug']}-{item['id']}",
|
||||
name=item["translations"]["kk"]["city"]["name"],
|
||||
provider=self.provider,
|
||||
lat=item["coordinates"]["latitude"],
|
||||
lon=item["coordinates"]["longitude"],
|
||||
country=item["translations"]["kk"]["country"]["name"],
|
||||
country_code=item["country"]["code"].lower(),
|
||||
district=item["translations"]["kk"]["district"]["name"],
|
||||
district=(
|
||||
item["translations"]["kk"]["district"]["name"] if item["translations"]["kk"]["district"] else ""
|
||||
),
|
||||
subdistrict=(
|
||||
item["translations"]["kk"]["subdistrict"]["name"]
|
||||
if "subdistrict" in item["translations"]["kk"]
|
||||
|
||||
@@ -39,14 +39,11 @@ class DateParser(RowParser[datetime.datetime]):
|
||||
KEY = "date"
|
||||
|
||||
def parse_row(self, tag: Tag) -> Iterable[datetime.datetime]:
|
||||
datetime_date_tag = tag.select_one(".widget-row.widget-row-datetime-date > .row-item")
|
||||
if datetime_date_tag:
|
||||
date_str = datetime_date_tag.find(text=True, recursive=False).text
|
||||
date = dateparser.parse(date_str, languages=["ru"])
|
||||
for item in tag.select(".widget-row.widget-row-datetime-time > .row-item"):
|
||||
time_str = item.text
|
||||
time = dateparser.parse(time_str, languages=["ru"])
|
||||
time = time.replace(year=date.year, month=date.month, day=date.day)
|
||||
datetime_time_row = tag.select_one(".widget-row.widget-row-datetime-time")
|
||||
if datetime_time_row:
|
||||
for item in datetime_time_row.select(".row-item > time-value"):
|
||||
timestamp = int(item.attrs["timestamp"])
|
||||
time = datetime.datetime.fromtimestamp(timestamp)
|
||||
yield time
|
||||
else:
|
||||
for item in tag.select(".widget-row.widget-row-date > .row-item"):
|
||||
@@ -68,21 +65,20 @@ class SkyParser(RowParser[Sky]):
|
||||
|
||||
PRECIPITATION_MAP: dict[str, Precipitation] = {
|
||||
"без осадков": Precipitation.NO,
|
||||
"небольшой дождь": Precipitation.SMALL_RAIN, # TODO: remove it?
|
||||
"небольшой дождь": Precipitation.SMALL_RAIN,
|
||||
"сильный дождь": Precipitation.HEAVY_RAIN, # TODO: remove it?
|
||||
"сильный дождь": Precipitation.HEAVY_RAIN,
|
||||
"ливневый дождь": Precipitation.SHOWER,
|
||||
"дождь": Precipitation.RAIN,
|
||||
"ливень": Precipitation.SHOWER,
|
||||
"снег": Precipitation.SNOW,
|
||||
"небольшой снег": Precipitation.SNOW,
|
||||
"сильный снег": Precipitation.HEAVY_SNOW,
|
||||
"мокрый снег": Precipitation.SNOW,
|
||||
"небольшой снег": Precipitation.SNOW,
|
||||
"сильный снег": Precipitation.HEAVY_SNOW,
|
||||
"мокрый снег": Precipitation.SNOW,
|
||||
"снег с дождём": Precipitation.SNOW,
|
||||
"сильный снег с дождём": Precipitation.HEAVY_SNOW,
|
||||
"небольшой снег с дождём": Precipitation.SNOW,
|
||||
"небольшой мокрый снег": Precipitation.SNOW,
|
||||
"сильный снег с дождём": Precipitation.HEAVY_SNOW,
|
||||
"небольшой снег с дождём": Precipitation.SNOW,
|
||||
"небольшой мокрый снег": Precipitation.SNOW,
|
||||
"град": Precipitation.HAIL,
|
||||
}
|
||||
|
||||
THUNDER = "гроза"
|
||||
@@ -152,6 +148,7 @@ class WindDirectionParser(RowParser[WindDirection]):
|
||||
|
||||
WIND_DIRECTION_MAP: dict[str, WindDirection] = {
|
||||
"—": WindDirection.CALM,
|
||||
"": WindDirection.CALM,
|
||||
"штиль": WindDirection.CALM,
|
||||
"с": WindDirection.N,
|
||||
"св": WindDirection.NE,
|
||||
@@ -173,8 +170,12 @@ class PrecipitationParser(RowParser[float]):
|
||||
KEY = "precipitation"
|
||||
|
||||
def parse_row(self, tag: Tag) -> Iterable[float]:
|
||||
for item in tag.select(".widget-row[data-row=precipitation-bars] > .row-item > .item-unit"):
|
||||
yield float(item.text.replace(",", "."))
|
||||
for item in tag.select(".widget-row[data-row=precipitation-bars] > .row-item"):
|
||||
value = item.select_one("precipitation-value")
|
||||
if value:
|
||||
yield float(value.attrs["value"])
|
||||
else:
|
||||
yield 0
|
||||
|
||||
|
||||
class PressureParser(RowParser[list[int]]):
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
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")
|
||||
@@ -14,31 +13,30 @@ 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]:
|
||||
endpoint = "api/v1/channels"
|
||||
data = json.loads(await self.SOURCE.request(endpoint))
|
||||
result = []
|
||||
query = query.lower()
|
||||
for item in data["result"]:
|
||||
if query in item["name"].lower() or query in item["alias"]:
|
||||
channel_id = item["alias"]
|
||||
name = item["name"].split("|")[0].strip()
|
||||
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"tvguide/{channel_id}?date={date:%Y%m%d}"
|
||||
data = await self.SOURCE.request(endpoint)
|
||||
soup = BeautifulSoup(data, features="html.parser")
|
||||
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||
endpoint = f"api/v1/channels/{channel_id}/tv-schedule?date={date:%Y%m%d}"
|
||||
data = json.loads(await self.SOURCE.request(endpoint))
|
||||
channel_data = data["result"]["channels"][0]
|
||||
channel = Channel(id=channel_data["alias"], name=channel_data["name"], provider=self.provider)
|
||||
values = []
|
||||
channel_name = soup.select_one(".p-tv-guide-header__title").text.replace("Телепрограмма ", "").strip()
|
||||
current_day = datetime.datetime.combine(date.today(), datetime.datetime.min.time())
|
||||
end = current_day + datetime.timedelta(days=1, hours=6)
|
||||
prev_value: ScheduleValue | None = None
|
||||
for item in soup.select(
|
||||
".p-tv-guide-schedule-channel-carcass__transmissions .p-tv-guide-schedule-channel-transmission"
|
||||
):
|
||||
title = item.select_one(".p-tv-guide-schedule-channel-transmission__title").text.strip()
|
||||
time_str = item.select_one(".p-tv-guide-schedule-channel-transmission__time-block").text.strip()
|
||||
for item in channel_data["schedule"]:
|
||||
title = item["title"]
|
||||
time_str = item["time"]
|
||||
hours, minutes = map(int, time_str.split(":"))
|
||||
item_date = current_day.replace(hour=hours, minute=minutes)
|
||||
if prev_value is not None and item_date.hour < prev_value.start.hour:
|
||||
@@ -50,4 +48,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,
|
||||
date=date,
|
||||
values=values,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
from os import environ
|
||||
|
||||
from aiocache import cached
|
||||
|
||||
@@ -9,7 +10,9 @@ from gallery.sketch.weather.model import Location, WeatherResponse, WeatherValue
|
||||
from gallery.sketch.weather.util import merge_weather_values
|
||||
from gallery.util import TimeUnit
|
||||
|
||||
from .openweather import Forecast, OpenWeather
|
||||
from .openweather import Forecast
|
||||
from .openweather import Location as OpenWeatherLocation
|
||||
from .openweather import OpenWeather
|
||||
from .parser import FORECAST_ITEM_PARSER
|
||||
|
||||
logger = logging.getLogger("openweather")
|
||||
@@ -17,12 +20,20 @@ logger = logging.getLogger("openweather")
|
||||
|
||||
class OpenWeatherApi(WeatherApi):
|
||||
PROVIDER = "openweather"
|
||||
SOURCE = OpenWeather("517a6bccceaa1c48127f6199ec3fb7cf")
|
||||
SOURCE = OpenWeather(environ["OPENWEATHER_KEY"])
|
||||
|
||||
@classmethod
|
||||
def _parse_location(cls, location_id: str) -> tuple[float, float]:
|
||||
return tuple(map(float, location_id.split(":", maxsplit=2)))
|
||||
|
||||
@cached(
|
||||
key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.location",
|
||||
alias="redis",
|
||||
ttl=TimeUnit.DAY,
|
||||
)
|
||||
async def _get_location(self, location_id: str) -> OpenWeatherLocation:
|
||||
return await self.SOURCE.get_location(*self._parse_location(location_id))
|
||||
|
||||
@cached(
|
||||
key_builder=lambda fun, self, location_id: f"api.weather.{self.provider}.source.{location_id}.forecast",
|
||||
alias="redis",
|
||||
@@ -32,9 +43,24 @@ class OpenWeatherApi(WeatherApi):
|
||||
return await self.SOURCE.get_forecast(*self._parse_location(location_id))
|
||||
|
||||
async def find_locations(self, query: str) -> list[Location]:
|
||||
raise NotImplementedError
|
||||
result = await self.SOURCE.find_locations(query)
|
||||
return [
|
||||
Location(
|
||||
id=f"{item.lat}:{item.lon}",
|
||||
name=item.name,
|
||||
provider=self.provider,
|
||||
lat=item.lat,
|
||||
lon=item.lon,
|
||||
country=item.country,
|
||||
country_code=item.country.lower(),
|
||||
district=item.state or "",
|
||||
subdistrict="",
|
||||
)
|
||||
for item in result
|
||||
]
|
||||
|
||||
async def get_day(self, location_id: str, date: datetime.date) -> WeatherResponse:
|
||||
location: OpenWeatherLocation = await self._get_location(location_id)
|
||||
data: Forecast = await self._get_location_forecast(location_id)
|
||||
values = []
|
||||
for item in data.list:
|
||||
@@ -42,13 +68,14 @@ class OpenWeatherApi(WeatherApi):
|
||||
if value.date.date() == date:
|
||||
values.append(value)
|
||||
return WeatherResponse(
|
||||
location=location_id,
|
||||
location=location.name,
|
||||
date=date,
|
||||
period="day",
|
||||
values=values,
|
||||
)
|
||||
|
||||
async def get_days(self, location_id: str, days: int) -> WeatherResponse:
|
||||
location: OpenWeatherLocation = await self._get_location(location_id)
|
||||
data: Forecast = await self._get_location_forecast(location_id)
|
||||
values_by_date: dict[datetime.datetime, list[WeatherValue]] = defaultdict(list)
|
||||
for item in data.list:
|
||||
@@ -57,7 +84,7 @@ class OpenWeatherApi(WeatherApi):
|
||||
values_by_date[item_date].append(value)
|
||||
values = [merge_weather_values(date, values) for date, values in values_by_date.items()]
|
||||
return WeatherResponse(
|
||||
location=location_id,
|
||||
location=location.name,
|
||||
date=datetime.date.today(),
|
||||
period="days",
|
||||
values=list(sorted(values, key=lambda item: item.date)),
|
||||
|
||||
@@ -53,7 +53,7 @@ class ForecastItem(Model):
|
||||
weather: list[Weather]
|
||||
clouds: Clouds
|
||||
wind: Wind
|
||||
visibility: int
|
||||
visibility: int | None = None
|
||||
pop: float
|
||||
rain: Rain | None = None
|
||||
sys: Sys
|
||||
@@ -67,6 +67,15 @@ class Forecast(Model):
|
||||
list: list[ForecastItem]
|
||||
|
||||
|
||||
class Location(Model):
|
||||
name: str
|
||||
lat: float
|
||||
lon: float
|
||||
country: str
|
||||
state: str | None = None
|
||||
local_names: dict[str, str] | None = None
|
||||
|
||||
|
||||
class OpenWeather:
|
||||
BASE_URL = "https://api.openweathermap.org"
|
||||
|
||||
@@ -78,4 +87,17 @@ class OpenWeather:
|
||||
endpoint = f"data/2.5/forecast?lat={lat}&lon={lon}&appid={self._api_key}&units=metric"
|
||||
response = await self._source.request(endpoint)
|
||||
response_data = json.loads(response)
|
||||
return Forecast(**response_data)
|
||||
return Forecast.model_validate(response_data)
|
||||
|
||||
async def find_locations(self, query: str, limit: int = 5) -> list[Location]:
|
||||
endpoint = f"geo/1.0/direct?q={query}&limit={limit}&appid={self._api_key}"
|
||||
response = await self._source.request(endpoint)
|
||||
response_data = json.loads(response)
|
||||
return [Location.model_validate(item) for item in response_data]
|
||||
|
||||
async def get_location(self, lat: float, lon: float) -> Location:
|
||||
limit = 1
|
||||
endpoint = f"geo/1.0/reverse?lat={lat}&lon={lon}&limit={limit}&appid={self._api_key}"
|
||||
response = await self._source.request(endpoint)
|
||||
response_data = json.loads(response)
|
||||
return Location.model_validate(response_data[0])
|
||||
|
||||
@@ -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,21 @@ 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 = (
|
||||
"https://suggest-multi.yandex.ru/suggest-tv2?"
|
||||
f"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:
|
||||
if content["label"] == "Каналы":
|
||||
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_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||
endpoint = f"channels/{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 +83,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,
|
||||
)
|
||||
|
||||
@@ -2,8 +2,13 @@ from typing import TypeVar
|
||||
|
||||
|
||||
class Api:
|
||||
TYPE: str
|
||||
PROVIDER: str
|
||||
|
||||
@property
|
||||
def type(self) -> str:
|
||||
return self.TYPE
|
||||
|
||||
@property
|
||||
def provider(self) -> str:
|
||||
return self.PROVIDER
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from .api import API, Api
|
||||
from .schedule.api import ScheduleApi
|
||||
from .weather.api import WeatherApi
|
||||
|
||||
|
||||
class ApiBundle(list[Api]):
|
||||
def __init__(self, values: list[Api]) -> None:
|
||||
super().__init__(values)
|
||||
class ApiBundle:
|
||||
def __init__(self, values: list[Api]):
|
||||
self._values = values
|
||||
self._providers_by_api = defaultdict(list)
|
||||
self._api_map = {}
|
||||
for value in self._values:
|
||||
self._providers_by_api[value.type].append(value.provider)
|
||||
self._api_map[(value.type, value.provider)] = value
|
||||
|
||||
def get_api_by_provider(self, provider: str) -> Api:
|
||||
for value in self:
|
||||
if value.PROVIDER == provider:
|
||||
return value
|
||||
raise ValueError(provider)
|
||||
def get_api_providers(self, api_type: type[API]) -> list[str]:
|
||||
return self._providers_by_api[api_type.TYPE]
|
||||
|
||||
def get_api_by_type(self, api_type: type[API]) -> API:
|
||||
for value in self:
|
||||
if isinstance(value, api_type):
|
||||
return value
|
||||
raise ValueError(api_type)
|
||||
|
||||
@property
|
||||
def weather(self) -> WeatherApi:
|
||||
return self.get_api_by_type(WeatherApi)
|
||||
|
||||
@property
|
||||
def schedule(self) -> ScheduleApi:
|
||||
return self.get_api_by_type(ScheduleApi)
|
||||
def get_api(self, api_type: type[API], provider: str) -> API:
|
||||
return self._api_map[(api_type.TYPE, 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,24 +1,14 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
|
||||
from ..api import Api
|
||||
from .model import ChannelId, Schedule
|
||||
from .model import Channel, Schedule
|
||||
|
||||
|
||||
class ScheduleApi(Api):
|
||||
INTERVAL: float = 0.5
|
||||
TYPE = "schedule"
|
||||
|
||||
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
|
||||
|
||||
@@ -6,20 +6,20 @@ 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)
|
||||
|
||||
|
||||
class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
||||
CACHE_KEY = "schedule"
|
||||
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:
|
||||
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)
|
||||
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||
return await self._api.get_schedule(channel_id, 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
|
||||
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):
|
||||
|
||||
@@ -26,7 +26,10 @@ class ApiSource:
|
||||
self._headers = headers
|
||||
|
||||
async def request(self, endpoint: str) -> str:
|
||||
url = f"{self._base_url}/{endpoint}"
|
||||
if endpoint.startswith("https:"):
|
||||
url = endpoint
|
||||
else:
|
||||
url = f"{self._base_url}/{endpoint}"
|
||||
logger.info(url)
|
||||
headers = {"User-Agent": self._user_agent, **(self._headers or {})}
|
||||
async with aiohttp.ClientSession(
|
||||
|
||||
@@ -5,6 +5,7 @@ from .model import Location, WeatherResponse
|
||||
|
||||
|
||||
class WeatherApi(Api):
|
||||
TYPE = "weather"
|
||||
|
||||
async def find_locations(self, query: str) -> list[Location]:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -11,7 +11,7 @@ CACHE_PRESET = DEFAULT_CACHE_PRESET
|
||||
|
||||
|
||||
class CachedWeatherApi(WeatherApi, CachedApi[WeatherApi]):
|
||||
CACHE_KEY = "weather"
|
||||
CACHE_KEY = WeatherApi.TYPE
|
||||
|
||||
@cached(
|
||||
key_builder=lambda fun, self, query: f"api.{self.CACHE_KEY}.{self.provider}.locations.{query}",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from enum import StrEnum, auto
|
||||
from typing import Self
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -12,6 +13,7 @@ class Model(BaseModel):
|
||||
class Location(Model):
|
||||
id: str
|
||||
name: str
|
||||
provider: str
|
||||
lat: float
|
||||
lon: float
|
||||
country: str
|
||||
@@ -20,21 +22,22 @@ class Location(Model):
|
||||
subdistrict: str
|
||||
|
||||
|
||||
class Cloudness(str, Enum):
|
||||
CLEAR = "clear"
|
||||
PARTLY_CLOUDY = "party_cloudy"
|
||||
CLOUDY = "cloudy"
|
||||
MAINLY_CLOUDY = "mainly_cloudy"
|
||||
class Cloudness(StrEnum):
|
||||
CLEAR = auto()
|
||||
PARTLY_CLOUDY = auto()
|
||||
CLOUDY = auto()
|
||||
MAINLY_CLOUDY = auto()
|
||||
|
||||
|
||||
class Precipitation(str, Enum):
|
||||
NO = "no"
|
||||
SMALL_RAIN = "small_rain"
|
||||
RAIN = "rain"
|
||||
HEAVY_RAIN = "heavy_rain"
|
||||
SHOWER = "shower"
|
||||
SNOW = "snow"
|
||||
HEAVY_SNOW = "heavy_snow"
|
||||
class Precipitation(StrEnum):
|
||||
NO = auto()
|
||||
SMALL_RAIN = auto()
|
||||
RAIN = auto()
|
||||
HEAVY_RAIN = auto()
|
||||
SHOWER = auto()
|
||||
SNOW = auto()
|
||||
HEAVY_SNOW = auto()
|
||||
HAIL = auto()
|
||||
|
||||
|
||||
class Sky(Model):
|
||||
@@ -44,16 +47,16 @@ class Sky(Model):
|
||||
fog: bool
|
||||
|
||||
|
||||
class WindDirection(str, Enum):
|
||||
CALM = "calm"
|
||||
N = "N"
|
||||
NE = "NE"
|
||||
E = "E"
|
||||
SE = "SE"
|
||||
S = "S"
|
||||
SW = "SW"
|
||||
W = "W"
|
||||
NW = "NW"
|
||||
class WindDirection(StrEnum):
|
||||
CALM = auto()
|
||||
N = auto()
|
||||
NE = auto()
|
||||
E = auto()
|
||||
SE = auto()
|
||||
S = auto()
|
||||
SW = auto()
|
||||
W = auto()
|
||||
NW = auto()
|
||||
|
||||
|
||||
class WindDirectionDeg(float):
|
||||
@@ -89,7 +92,7 @@ class WindDirectionDeg(float):
|
||||
raise ValueError(self)
|
||||
|
||||
@classmethod
|
||||
def from_direction(cls, direction: WindDirection) -> "WindDirectionDeg":
|
||||
def from_direction(cls, direction: WindDirection) -> Self:
|
||||
return cls(
|
||||
{
|
||||
WindDirection.CALM: -1,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
__version__ = "0.2.2"
|
||||
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
2837
poetry.lock
generated
2837
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,32 @@
|
||||
[tool.poetry]
|
||||
name = "gallery"
|
||||
version = "0.2.3"
|
||||
version = "0.4.2"
|
||||
description = ""
|
||||
authors = ["shmyga <shmyga.z@gmail.com>"]
|
||||
readme = "README.md"
|
||||
packages = [{ include = "gallery" }]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12"
|
||||
aiohttp = "^3.9.5"
|
||||
beautifulsoup4 = "^4.12.3"
|
||||
dateparser = "^1.2.0"
|
||||
pydantic = "^2.8.2"
|
||||
aiocache = { extras = ["redis"], version = "^0.12.2" }
|
||||
python = "^3.14"
|
||||
aiohttp = "^3.14"
|
||||
beautifulsoup4 = "^4.15"
|
||||
dateparser = "^1.4"
|
||||
pydantic = "^2.13"
|
||||
aiocache = { extras = ["redis"], version = "^0.12" }
|
||||
|
||||
[tool.poetry.group.app.dependencies]
|
||||
fastapi = "^0.111.1"
|
||||
jinja2 = "^3.1.4"
|
||||
babel = "^2.18.0"
|
||||
fastapi = {extras = ["standard"], version = "^0.139.0"}
|
||||
jinja2 = "^3.1"
|
||||
babel = "^2.18"
|
||||
|
||||
[tool.poetry.group.test.dependencies]
|
||||
pytest = "^8.3.1"
|
||||
pytest-asyncio = "^0.23.8"
|
||||
pytest = "^9.1"
|
||||
pytest-asyncio = "^1.4"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pylint = "^3.2.6"
|
||||
black = "^24.4.2"
|
||||
isort = "^5.13.2"
|
||||
pylint = "^4.0"
|
||||
black = "^26.5"
|
||||
isort = "^8.0"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
@@ -4,6 +4,6 @@ cd "$(dirname $(dirname "$0"))" || exit
|
||||
|
||||
TARGET="gallery"
|
||||
|
||||
poetry run pylint $TARGET
|
||||
poetry run pylint $TARGET -sn
|
||||
poetry run isort $TARGET --check-only
|
||||
poetry run black $TARGET -q --check --diff
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
set -e
|
||||
cd "$(dirname $(dirname "$0"))" || exit
|
||||
|
||||
PYTHON_VERSION=3.12
|
||||
# env
|
||||
if [[ ! -f .env ]]; then
|
||||
cp .env-base .env
|
||||
fi
|
||||
source .env
|
||||
|
||||
# python
|
||||
poetry env use ${PYTHON_VERSION}
|
||||
poetry install
|
||||
|
||||
# static
|
||||
cd static || exit
|
||||
|
||||
if [[ -f $HOME/.nvm/nvm.sh ]]; then
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use
|
||||
source "$HOME/.nvm/nvm.sh"
|
||||
nvm use
|
||||
fi
|
||||
npm ci
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
set -e
|
||||
cd "$(dirname $(dirname "$0"))" || exit
|
||||
|
||||
poetry run pytest tests
|
||||
OPENWEATHER_KEY="" poetry run pytest tests
|
||||
|
||||
4
static/package-lock.json
generated
4
static/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "gallery",
|
||||
"version": "0.2.3",
|
||||
"version": "0.4.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "gallery",
|
||||
"version": "0.2.3",
|
||||
"version": "0.4.2",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gallery",
|
||||
"version": "0.2.3",
|
||||
"version": "0.4.2",
|
||||
"scripts": {
|
||||
"build": "vite build",
|
||||
"dev": "vite build --watch"
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
class AppLinkElement extends HTMLElement {
|
||||
static observedAttributes = ["icon", "href"];
|
||||
static observedAttributes = ["icon", "href", "target"];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.innerHTML = `
|
||||
<a href="${this.getAttribute("href")}"
|
||||
<a href="${this.getAttribute("href")}" target="${this.getAttribute("target") || ""}"
|
||||
class="d-flex align-items-center text-body text-decoration-none">
|
||||
<span class="fs-4">
|
||||
<span class="bi bi-${this.getAttribute("icon")} me-1"></span>
|
||||
|
||||
@@ -3,6 +3,8 @@ import "./components";
|
||||
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"]');
|
||||
|
||||
22
static/src/lib/bootstrap-icons.scss
vendored
22
static/src/lib/bootstrap-icons.scss
vendored
@@ -1,4 +1,6 @@
|
||||
//$bootstrap-icons: "circle-half", "moon-stars-fill", "brightness-high", "gear", "sun-fill", "tv";
|
||||
.icon-link {
|
||||
vertical-align: -0.25rem;
|
||||
}
|
||||
|
||||
.bi {
|
||||
display: inline-block;
|
||||
@@ -13,12 +15,6 @@
|
||||
mask-repeat: no-repeat;
|
||||
background-color: currentColor;
|
||||
|
||||
//@each $icon in $bootstrap-icons {
|
||||
// &.bi-#{$icon} {
|
||||
// mask-image: url("bootstrap-icons/icons/#{$icon}.svg");
|
||||
// }
|
||||
///
|
||||
|
||||
&.bi-circle-half {
|
||||
mask-image: url(bootstrap-icons/icons/circle-half.svg);
|
||||
}
|
||||
@@ -37,4 +33,16 @@
|
||||
&.bi-tv {
|
||||
mask-image: url(bootstrap-icons/icons/tv.svg);
|
||||
}
|
||||
&.bi-arrow-left-square {
|
||||
mask-image: url(bootstrap-icons/icons/arrow-left-square.svg);
|
||||
}
|
||||
&.bi-arrow-right-square {
|
||||
mask-image: url(bootstrap-icons/icons/arrow-right-square.svg);
|
||||
}
|
||||
&.bi-arrow-up-square {
|
||||
mask-image: url(bootstrap-icons/icons/arrow-up-square.svg);
|
||||
}
|
||||
&.bi-file-earmark-text {
|
||||
mask-image: url(bootstrap-icons/icons/file-earmark-text.svg);
|
||||
}
|
||||
}
|
||||
|
||||
2
static/src/lib/bootstrap.scss
vendored
2
static/src/lib/bootstrap.scss
vendored
@@ -30,7 +30,7 @@
|
||||
//@import "bootstrap/scss/accordion";
|
||||
//@import "bootstrap/scss/breadcrumb";
|
||||
//@import "bootstrap/scss/pagination";
|
||||
//@import "bootstrap/scss/badge";
|
||||
@import "bootstrap/scss/badge";
|
||||
//@import "bootstrap/scss/alert";
|
||||
//@import "bootstrap/scss/progress";
|
||||
@import "bootstrap/scss/list-group";
|
||||
|
||||
@@ -12,4 +12,6 @@
|
||||
.fir {
|
||||
@extend .fis;
|
||||
border-radius: 50%;
|
||||
}
|
||||
height: 1em;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
@@ -7,12 +7,18 @@
|
||||
.wi-thunderstorm {
|
||||
mask-image: url(./svg/wi-thunderstorm.svg);
|
||||
}
|
||||
.wi-hail {
|
||||
mask-image: url(./svg/wi-hail.svg);
|
||||
}
|
||||
.wi-day-rain {
|
||||
mask-image: url(./svg/wi-day-rain.svg);
|
||||
}
|
||||
.wi-cloudy {
|
||||
mask-image: url(./svg/wi-cloudy.svg);
|
||||
}
|
||||
.wi-day-hail {
|
||||
mask-image: url(./svg/wi-day-hail.svg);
|
||||
}
|
||||
.wi-night-clear {
|
||||
mask-image: url(./svg/wi-night-clear.svg);
|
||||
}
|
||||
@@ -49,6 +55,9 @@
|
||||
.wi-day-sunny {
|
||||
mask-image: url(./svg/wi-day-sunny.svg);
|
||||
}
|
||||
.wi-night-alt-hail {
|
||||
mask-image: url(./svg/wi-night-alt-hail.svg);
|
||||
}
|
||||
.wi-night-alt-cloudy {
|
||||
mask-image: url(./svg/wi-night-alt-cloudy.svg);
|
||||
}
|
||||
|
||||
42
static/src/lib/weather-icons/svg/wi-day-hail.svg
Normal file
42
static/src/lib/weather-icons/svg/wi-day-hail.svg
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<path d="M1.48,16.88c0,1.34,0.47,2.49,1.4,3.45s2.07,1.47,3.4,1.53c0.12,0,0.18-0.06,0.18-0.17v-1.34c0-0.12-0.06-0.18-0.18-0.18
|
||||
c-0.86-0.04-1.59-0.39-2.19-1.03s-0.9-1.4-0.9-2.26c0-0.83,0.28-1.55,0.85-2.17s1.27-0.97,2.1-1.07l0.53-0.04
|
||||
c0.13,0,0.2-0.06,0.2-0.18l0.07-0.55c0.11-1.08,0.56-1.99,1.37-2.71c0.81-0.73,1.76-1.09,2.86-1.09c1.09,0,2.04,0.36,2.86,1.09
|
||||
c0.82,0.73,1.29,1.63,1.4,2.71l0.07,0.58c0,0.12,0.06,0.19,0.17,0.19h1.62c0.89,0,1.67,0.32,2.32,0.96c0.65,0.64,0.98,1.4,0.98,2.27
|
||||
c0,0.87-0.3,1.62-0.91,2.26c-0.61,0.64-1.34,0.98-2.19,1.03c-0.13,0-0.2,0.06-0.2,0.18v1.34c0,0.11,0.07,0.17,0.2,0.17
|
||||
c1.34-0.04,2.47-0.55,3.39-1.51c0.93-0.97,1.39-2.12,1.39-3.46c0-0.74-0.14-1.41-0.41-2.01c0.79-0.96,1.18-2.07,1.18-3.33
|
||||
c0-0.94-0.24-1.82-0.71-2.63c-0.48-0.81-1.12-1.45-1.93-1.93c-0.81-0.47-1.69-0.71-2.63-0.71c-1.56,0-2.86,0.58-3.9,1.75
|
||||
c-0.8-0.44-1.7-0.66-2.71-0.66c-1.41,0-2.67,0.44-3.76,1.31s-1.8,2-2.11,3.37c-1.11,0.26-2.02,0.84-2.74,1.74
|
||||
C1.84,14.7,1.48,15.73,1.48,16.88z M6.82,23.94c0.1,0.22,0.25,0.37,0.46,0.45c0.2,0.1,0.41,0.11,0.63,0.02
|
||||
c0.22-0.08,0.37-0.23,0.45-0.45c0.1-0.22,0.11-0.43,0.02-0.65c-0.08-0.21-0.23-0.36-0.45-0.44c-0.2-0.11-0.41-0.12-0.62-0.03
|
||||
c-0.22,0.09-0.37,0.24-0.47,0.47C6.74,23.49,6.73,23.7,6.82,23.94z M7.46,21.1c0,0.14,0.03,0.27,0.09,0.38
|
||||
c0.19,0.31,0.49,0.46,0.89,0.46c0.32,0,0.55-0.22,0.69-0.65l1.04-3.22c0.08-0.24,0.06-0.47-0.07-0.67s-0.31-0.33-0.55-0.37
|
||||
C9.34,16.98,9.13,17,8.93,17.1c-0.2,0.11-0.34,0.28-0.41,0.5l-1.03,3.22C7.47,20.92,7.46,21.02,7.46,21.1z M9.33,26.72
|
||||
c0,0.13,0.02,0.23,0.05,0.29c0.09,0.22,0.24,0.37,0.45,0.45c0.09,0.05,0.21,0.07,0.35,0.07c0.06,0,0.16-0.02,0.3-0.06
|
||||
c0.22-0.08,0.38-0.23,0.47-0.45s0.1-0.44,0-0.66c-0.1-0.22-0.25-0.37-0.45-0.45s-0.41-0.08-0.62,0c-0.19,0.07-0.33,0.19-0.42,0.35
|
||||
C9.37,26.42,9.33,26.58,9.33,26.72z M9.94,4.57c0,0.25,0.08,0.45,0.24,0.6l0.65,0.65c0.16,0.16,0.34,0.25,0.54,0.27
|
||||
c0.21,0.03,0.41-0.05,0.61-0.23c0.2-0.18,0.3-0.4,0.3-0.64c0-0.24-0.08-0.44-0.24-0.6l-0.64-0.64c-0.19-0.17-0.39-0.25-0.62-0.25
|
||||
c-0.24,0-0.45,0.08-0.61,0.24C10.02,4.14,9.94,4.34,9.94,4.57z M10.06,24.03c0,0.16,0.05,0.32,0.16,0.48s0.27,0.27,0.48,0.33
|
||||
c0.11,0.02,0.19,0.04,0.24,0.04c0.15,0,0.28-0.03,0.38-0.08c0.2-0.08,0.34-0.27,0.43-0.57l1.8-6.14c0.07-0.24,0.05-0.45-0.06-0.65
|
||||
c-0.11-0.2-0.27-0.33-0.5-0.39c-0.24-0.07-0.46-0.05-0.66,0.06c-0.2,0.11-0.34,0.27-0.41,0.51l-1.84,6.19
|
||||
C10.07,23.92,10.06,24,10.06,24.03z M13.51,23.64c0,0.13,0.02,0.23,0.07,0.31c0.09,0.21,0.24,0.35,0.45,0.44
|
||||
c0.11,0.05,0.22,0.08,0.35,0.08c0.06,0,0.16-0.02,0.3-0.06c0.23-0.09,0.38-0.23,0.46-0.44c0.08-0.22,0.08-0.43,0-0.63
|
||||
c-0.08-0.2-0.22-0.35-0.42-0.45c-0.23-0.11-0.44-0.12-0.66-0.03c-0.21,0.09-0.37,0.24-0.48,0.47
|
||||
C13.53,23.41,13.51,23.51,13.51,23.64z M14.23,21.08c0,0.16,0.05,0.31,0.15,0.45c0.1,0.15,0.26,0.25,0.46,0.31
|
||||
c0.09,0.02,0.17,0.03,0.25,0.03c0.39,0,0.65-0.2,0.79-0.61l1.03-3.18c0.08-0.23,0.05-0.45-0.07-0.65s-0.29-0.33-0.52-0.39
|
||||
c-0.24-0.07-0.45-0.05-0.64,0.06s-0.32,0.27-0.4,0.51l-1.02,3.2C14.25,20.94,14.23,21.03,14.23,21.08z M15.3,9
|
||||
c0.67-0.64,1.5-0.97,2.48-0.97c0.98,0,1.81,0.34,2.5,1.03c0.69,0.68,1.04,1.51,1.04,2.49c0,0.62-0.17,1.24-0.52,1.85
|
||||
c-0.96-0.96-2.12-1.44-3.51-1.44H17C16.7,10.8,16.14,9.81,15.3,9z M16.92,3.73c0,0.24,0.08,0.44,0.25,0.61
|
||||
c0.17,0.17,0.37,0.25,0.61,0.25c0.23,0,0.43-0.08,0.59-0.25c0.16-0.17,0.24-0.37,0.24-0.61V1.67c0-0.24-0.08-0.44-0.24-0.61
|
||||
c-0.16-0.17-0.35-0.25-0.59-0.25c-0.24,0-0.44,0.08-0.61,0.25c-0.17,0.17-0.25,0.37-0.25,0.61V3.73z M22.47,6.02
|
||||
c0,0.24,0.08,0.44,0.25,0.6c0.15,0.17,0.34,0.26,0.58,0.26c0.23,0,0.44-0.09,0.6-0.26l1.44-1.44c0.18-0.15,0.27-0.35,0.27-0.6
|
||||
c0-0.24-0.09-0.44-0.26-0.61c-0.17-0.17-0.38-0.25-0.61-0.25c-0.22,0-0.41,0.09-0.57,0.27l-1.45,1.43
|
||||
C22.56,5.57,22.47,5.78,22.47,6.02z M23.28,17.92c0,0.23,0.08,0.43,0.24,0.6l0.66,0.63c0.14,0.18,0.34,0.27,0.6,0.27
|
||||
c0.24,0,0.43-0.09,0.57-0.27c0.18-0.16,0.27-0.36,0.27-0.6c0-0.24-0.09-0.44-0.27-0.61l-0.65-0.62c-0.16-0.18-0.35-0.26-0.58-0.26
|
||||
s-0.43,0.08-0.6,0.25C23.36,17.48,23.28,17.69,23.28,17.92z M24.74,11.55c0,0.24,0.09,0.44,0.26,0.6c0.18,0.18,0.38,0.26,0.62,0.26
|
||||
h2.03c0.24,0,0.44-0.08,0.61-0.25c0.17-0.17,0.26-0.37,0.26-0.61c0-0.23-0.08-0.43-0.25-0.59c-0.17-0.16-0.38-0.24-0.62-0.24h-2.03
|
||||
c-0.25,0-0.46,0.08-0.63,0.24C24.83,11.12,24.74,11.32,24.74,11.55z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
30
static/src/lib/weather-icons/svg/wi-hail.svg
Normal file
30
static/src/lib/weather-icons/svg/wi-hail.svg
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<path d="M4.64,16.9c0,1.33,0.46,2.47,1.39,3.43c0.93,0.96,2.06,1.47,3.4,1.53c0.11,0,0.17-0.06,0.17-0.17v-1.34
|
||||
c0-0.11-0.06-0.17-0.17-0.17c-0.86-0.04-1.58-0.38-2.18-1.02c-0.6-0.64-0.9-1.39-0.9-2.26c0-0.83,0.28-1.54,0.84-2.16
|
||||
c0.56-0.61,1.26-0.97,2.09-1.07l0.53-0.03c0.13,0,0.2-0.06,0.2-0.19l0.06-0.53c0.11-1.08,0.56-1.99,1.37-2.71
|
||||
c0.81-0.73,1.76-1.09,2.85-1.09c1.09,0,2.04,0.36,2.85,1.09c0.81,0.73,1.27,1.63,1.39,2.71l0.08,0.58c0,0.11,0.06,0.17,0.18,0.17
|
||||
h1.61c0.89,0,1.66,0.32,2.31,0.96c0.65,0.64,0.98,1.39,0.98,2.27c0,0.87-0.3,1.62-0.9,2.26c-0.6,0.64-1.33,0.98-2.18,1.02
|
||||
c-0.13,0-0.2,0.06-0.2,0.17v1.34c0,0.11,0.07,0.17,0.2,0.17c0.87-0.02,1.67-0.26,2.4-0.71c0.73-0.45,1.31-1.05,1.73-1.8
|
||||
c0.42-0.75,0.63-1.57,0.63-2.44c0-0.89-0.22-1.72-0.67-2.47c-0.44-0.75-1.05-1.35-1.81-1.78S21.29,12,20.4,12h-0.32
|
||||
c-0.32-1.34-1.03-2.43-2.1-3.28s-2.3-1.28-3.68-1.28c-1.41,0-2.66,0.44-3.75,1.31c-1.09,0.87-1.79,1.99-2.1,3.35
|
||||
c-1.11,0.26-2.02,0.83-2.73,1.73S4.64,15.75,4.64,16.9z M10.09,24.1c0.09,0.21,0.25,0.37,0.46,0.46c0.2,0.1,0.41,0.11,0.62,0.02
|
||||
c0.22-0.09,0.36-0.24,0.45-0.45c0.1-0.22,0.11-0.43,0.02-0.64c-0.08-0.21-0.24-0.35-0.45-0.44c-0.2-0.11-0.4-0.12-0.61-0.03
|
||||
c-0.21,0.09-0.36,0.24-0.46,0.47C10.01,23.66,10.01,23.86,10.09,24.1z M10.72,21.28c0,0.16,0.05,0.31,0.15,0.45
|
||||
c0.1,0.15,0.26,0.25,0.46,0.32c0.19,0.11,0.4,0.12,0.62,0.01c0.22-0.1,0.37-0.3,0.44-0.6l0.9-3.38c0.06-0.25,0.04-0.47-0.08-0.67
|
||||
c-0.12-0.2-0.29-0.32-0.53-0.36c-0.08-0.02-0.16-0.03-0.24-0.03c-0.16,0-0.32,0.05-0.47,0.15c-0.15,0.1-0.26,0.25-0.32,0.44
|
||||
l-0.88,3.39C10.73,21.16,10.72,21.25,10.72,21.28z M12.58,26.87c0,0.12,0.02,0.22,0.06,0.29c0.09,0.22,0.24,0.37,0.45,0.45
|
||||
c0.09,0.05,0.2,0.08,0.33,0.08c0.06,0,0.16-0.02,0.3-0.06c0.22-0.08,0.38-0.23,0.47-0.45c0.1-0.22,0.1-0.44,0-0.66
|
||||
c-0.1-0.22-0.25-0.37-0.45-0.46c-0.2-0.09-0.4-0.09-0.62,0c-0.19,0.08-0.32,0.2-0.41,0.36C12.62,26.58,12.58,26.73,12.58,26.87z
|
||||
M13.31,24.26c0,0.37,0.21,0.61,0.63,0.73c0.11,0.03,0.19,0.04,0.24,0.04c0.15,0,0.28-0.03,0.38-0.08c0.21-0.08,0.35-0.27,0.42-0.57
|
||||
l1.67-6.29c0.06-0.24,0.04-0.45-0.06-0.65c-0.1-0.19-0.27-0.32-0.49-0.38c-0.08-0.02-0.17-0.03-0.27-0.03
|
||||
c-0.16,0-0.32,0.05-0.48,0.15c-0.16,0.1-0.26,0.25-0.3,0.44l-1.71,6.34C13.32,24.1,13.31,24.2,13.31,24.26z M16.74,23.8
|
||||
c0,0.12,0.02,0.23,0.08,0.32c0.08,0.19,0.23,0.34,0.44,0.44c0.11,0.04,0.23,0.07,0.35,0.07c0.06,0,0.16-0.02,0.3-0.06
|
||||
c0.21-0.08,0.37-0.23,0.46-0.44c0.07-0.22,0.07-0.43-0.01-0.63c-0.08-0.2-0.22-0.35-0.42-0.45c-0.23-0.11-0.44-0.12-0.65-0.03
|
||||
c-0.21,0.09-0.36,0.24-0.46,0.47C16.77,23.59,16.74,23.69,16.74,23.8z M17.47,21.23c0,0.14,0.05,0.29,0.16,0.45
|
||||
c0.11,0.16,0.26,0.27,0.45,0.33c0.16,0.03,0.25,0.05,0.27,0.05c0.09,0,0.22-0.03,0.37-0.1c0.2-0.09,0.33-0.27,0.4-0.52l0.9-3.34
|
||||
c0.02-0.17,0.03-0.26,0.03-0.26c0-0.16-0.05-0.31-0.15-0.46c-0.1-0.15-0.25-0.25-0.45-0.31c-0.09-0.02-0.18-0.03-0.26-0.03
|
||||
c-0.16,0-0.32,0.05-0.47,0.15s-0.25,0.25-0.31,0.45l-0.9,3.36L17.47,21.23z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
34
static/src/lib/weather-icons/svg/wi-night-alt-hail.svg
Normal file
34
static/src/lib/weather-icons/svg/wi-night-alt-hail.svg
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
||||
<path d="M4.1,16.91c0,1.33,0.46,2.48,1.39,3.43s2.06,1.47,3.4,1.53c0.12,0,0.18-0.06,0.18-0.17v-1.34c0-0.11-0.06-0.17-0.18-0.17
|
||||
c-0.86-0.04-1.58-0.38-2.18-1.02s-0.9-1.39-0.9-2.26c0-0.83,0.28-1.55,0.84-2.17c0.56-0.61,1.26-0.97,2.1-1.07l0.53-0.03
|
||||
c0.13,0,0.2-0.06,0.2-0.18l0.07-0.54c0.11-1.08,0.56-1.99,1.37-2.72c0.81-0.73,1.76-1.1,2.85-1.1c1.08,0,2.03,0.37,2.85,1.1
|
||||
c0.82,0.73,1.28,1.64,1.4,2.72l0.08,0.58c0,0.11,0.06,0.17,0.17,0.17h1.61c0.89,0,1.66,0.32,2.31,0.96c0.65,0.64,0.98,1.4,0.98,2.27
|
||||
c0,0.87-0.3,1.62-0.9,2.26c-0.6,0.64-1.33,0.98-2.18,1.02c-0.13,0-0.2,0.06-0.2,0.17v1.34c0,0.11,0.07,0.17,0.2,0.17
|
||||
c1.33-0.04,2.46-0.55,3.38-1.51c0.93-0.96,1.39-2.11,1.39-3.45c0-0.86-0.22-1.66-0.65-2.41c0.79-0.74,1.3-1.62,1.55-2.62l0.13-0.68
|
||||
c0.02-0.01,0.03-0.03,0.03-0.07c0-0.07-0.05-0.13-0.16-0.16l-0.56-0.17c-0.57-0.17-1.05-0.45-1.46-0.85
|
||||
c-0.4-0.4-0.69-0.81-0.86-1.25c-0.17-0.43-0.25-0.87-0.25-1.32c-0.01-0.24,0.02-0.51,0.08-0.79l0.14-0.58
|
||||
c0.03-0.09-0.02-0.16-0.14-0.22l-0.8-0.25c-0.42-0.12-0.86-0.19-1.31-0.19c-0.35,0-0.71,0.04-1.08,0.13s-0.76,0.22-1.17,0.4
|
||||
c-0.41,0.18-0.8,0.45-1.19,0.8c-0.38,0.35-0.72,0.75-1,1.22c-0.75-0.32-1.54-0.49-2.37-0.49c-1.41,0-2.67,0.44-3.76,1.31
|
||||
s-1.79,1.99-2.1,3.36c-1.11,0.26-2.02,0.83-2.74,1.73S4.1,15.76,4.1,16.91z M9.58,23.94c0.09,0.21,0.24,0.36,0.46,0.45
|
||||
c0.19,0.1,0.4,0.11,0.62,0.02c0.22-0.08,0.37-0.23,0.45-0.45c0.1-0.22,0.11-0.43,0.02-0.65c-0.08-0.21-0.23-0.36-0.45-0.44
|
||||
c-0.2-0.1-0.41-0.11-0.62-0.02c-0.21,0.09-0.37,0.24-0.47,0.46C9.5,23.48,9.49,23.69,9.58,23.94z M10.2,21.11
|
||||
c0,0.15,0.05,0.3,0.16,0.45s0.26,0.26,0.46,0.32c0.26,0.1,0.48,0.1,0.67,0c0.19-0.1,0.32-0.29,0.4-0.57l0.88-3.21
|
||||
c0.07-0.25,0.04-0.47-0.08-0.67c-0.12-0.2-0.3-0.32-0.54-0.37c-0.22-0.07-0.43-0.05-0.63,0.07c-0.2,0.11-0.33,0.28-0.4,0.51
|
||||
l-0.88,3.22c0,0.02-0.01,0.06-0.02,0.12C10.21,21.03,10.2,21.08,10.2,21.11z M12.07,26.71c0,0.12,0.02,0.22,0.06,0.29
|
||||
c0.09,0.22,0.24,0.37,0.45,0.45c0.09,0.05,0.2,0.07,0.33,0.07c0.06,0,0.16-0.02,0.3-0.06c0.23-0.08,0.39-0.23,0.48-0.45
|
||||
c0.1-0.22,0.1-0.44,0-0.66c-0.1-0.22-0.25-0.37-0.45-0.46c-0.2-0.09-0.4-0.09-0.61,0c-0.19,0.08-0.33,0.2-0.42,0.36
|
||||
C12.11,26.42,12.07,26.57,12.07,26.71z M12.81,24.06c0,0.38,0.21,0.64,0.64,0.78c0.09,0.03,0.17,0.05,0.23,0.05
|
||||
c0.11,0,0.23-0.03,0.35-0.08c0.23-0.08,0.39-0.27,0.47-0.57l1.65-6.12c0.06-0.24,0.04-0.45-0.07-0.65c-0.11-0.19-0.28-0.32-0.5-0.39
|
||||
c-0.23-0.07-0.45-0.05-0.65,0.07c-0.2,0.11-0.34,0.28-0.4,0.51l-1.68,6.17C12.82,23.92,12.81,24,12.81,24.06z M16.25,23.64
|
||||
c0,0.13,0.02,0.23,0.07,0.31c0.08,0.2,0.23,0.35,0.44,0.44c0.12,0.05,0.23,0.08,0.35,0.08c0.06,0,0.16-0.02,0.3-0.06
|
||||
c0.22-0.09,0.37-0.23,0.45-0.44c0.08-0.22,0.08-0.43,0-0.63c-0.08-0.2-0.22-0.35-0.42-0.45c-0.22-0.1-0.44-0.11-0.65-0.02
|
||||
c-0.22,0.08-0.37,0.24-0.47,0.46C16.27,23.41,16.25,23.51,16.25,23.64z M16.97,21.08c0,0.16,0.05,0.32,0.15,0.46
|
||||
c0.1,0.14,0.25,0.25,0.45,0.31c0.17,0.02,0.26,0.03,0.27,0.03c0.41,0,0.66-0.2,0.77-0.61l0.87-3.17c0.06-0.24,0.04-0.45-0.07-0.65
|
||||
c-0.11-0.19-0.28-0.32-0.5-0.39c-0.23-0.07-0.45-0.05-0.64,0.07c-0.2,0.11-0.33,0.28-0.4,0.51L17,20.81
|
||||
C16.98,20.9,16.97,20.99,16.97,21.08z M17.62,8.83c0.31-0.57,0.75-1.01,1.3-1.31c0.55-0.3,1.14-0.45,1.76-0.44
|
||||
c0.11,0,0.2,0.01,0.25,0.02v0.31c0,0.98,0.26,1.89,0.78,2.75c0.52,0.86,1.25,1.51,2.17,1.95c-0.19,0.44-0.44,0.79-0.75,1.07
|
||||
C22.25,12.39,21.17,12,19.88,12h-0.32C19.3,10.75,18.66,9.69,17.62,8.83z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
@@ -13,4 +13,11 @@
|
||||
mask-position: 50%;
|
||||
mask-repeat: no-repeat;
|
||||
background-color: currentColor;
|
||||
|
||||
&.wi-l {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
&.wi-xl {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@import "./lib/weather-icons/weather-icons";
|
||||
|
||||
@import "./widget.scss";
|
||||
@import "./weather.scss";
|
||||
@import "./weather/weather.scss";
|
||||
|
||||
.table.table-compact {
|
||||
td {
|
||||
@@ -12,11 +12,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: inline-block;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background-size: contain;
|
||||
.icon-header {
|
||||
@include font-size($h4-font-size);
|
||||
}
|
||||
|
||||
.app-header {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
@import "./lib/bootstrap";
|
||||
|
||||
$default-space: 2;
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
.app-header {
|
||||
flex-direction: column;
|
||||
padding-bottom: map-get($spacers, $default-space) !important;
|
||||
margin-bottom: map-get($spacers, $default-space) !important;
|
||||
|
||||
> .link-list {
|
||||
border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important;
|
||||
padding-bottom: map-get($spacers, 1);
|
||||
margin-bottom: map-get($spacers, 1);
|
||||
padding-bottom: map-get($spacers, $default-space);
|
||||
margin-bottom: map-get($spacers, $default-space);
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
|
||||
@@ -22,12 +26,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.app-footer {
|
||||
padding-top: map-get($spacers, $default-space) !important;
|
||||
margin-top: map-get($spacers, $default-space) !important;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
flex-direction: column !important;
|
||||
|
||||
> .nav-item {
|
||||
.btn {
|
||||
padding: 0.125rem !important;
|
||||
padding: map-get($spacers, 1) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
111
static/src/schedule/schedule.ts
Normal file
111
static/src/schedule/schedule.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
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="d-flex justify-content-between align-items-start text-decoration-none">
|
||||
<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.classList = "list-group-item list-group-item-action";
|
||||
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();
|
||||
console.log("ok");
|
||||
118
static/src/weather/weather.ts
Normal file
118
static/src/weather/weather.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
export interface Location {
|
||||
id: string;
|
||||
name: string;
|
||||
provider: string;
|
||||
country_code: string;
|
||||
country?: string;
|
||||
district?: string;
|
||||
subdistrict?: string;
|
||||
}
|
||||
|
||||
export class WeatherLocationStorage {
|
||||
private storageKey: string = "weather:locations";
|
||||
|
||||
getAll(): Location[] {
|
||||
const locations = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}");
|
||||
return Object.values(locations);
|
||||
}
|
||||
|
||||
add(location: Location) {
|
||||
const locations = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}");
|
||||
locations[location.id] = location;
|
||||
window.localStorage.setItem(this.storageKey, JSON.stringify(locations));
|
||||
}
|
||||
|
||||
remove(id: string) {
|
||||
const locations = JSON.parse(window.localStorage.getItem(this.storageKey) || "{}");
|
||||
delete locations[id];
|
||||
window.localStorage.setItem(this.storageKey, JSON.stringify(locations));
|
||||
}
|
||||
}
|
||||
|
||||
export class WeatherLocationManager {
|
||||
loadLocations(selector: string) {
|
||||
const locations = window.weatherLocationStorage.getAll();
|
||||
const container = document.querySelector(selector);
|
||||
if (container) {
|
||||
container.innerHTML = "";
|
||||
for (const location of locations) {
|
||||
const element = new WeatherLocationElement();
|
||||
element.setAttribute("location", JSON.stringify(location));
|
||||
element.setAttribute("removable", "");
|
||||
container.appendChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WeatherLocationElement extends HTMLElement {
|
||||
static observedAttributes = ["location", "removable"];
|
||||
|
||||
location: Location | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
this.handleRemoveClick = this.handleRemoveClick.bind(this);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.location = JSON.parse(this.getAttribute("location") || "{}");
|
||||
this.innerHTML = `
|
||||
<a href="${this.location?.provider}/${this.location?.id}"
|
||||
class="d-flex justify-content-between align-items-start text-decoration-none">
|
||||
<span>
|
||||
<span class="fi fi-${this.location?.country_code} me-1"></span>
|
||||
<span class="text-primary">${this.location?.name}</span>
|
||||
<span class="small ms-1 text-secondary">
|
||||
${this.location?.country}, ${this.location?.district}, ${this.location?.subdistrict}
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span class="badge text-bg-secondary">${this.location?.provider}</span>
|
||||
<span class="text-danger ms-2" data-remove>
|
||||
✕
|
||||
</span>
|
||||
</span>
|
||||
</a>`;
|
||||
this.classList = 'list-group-item list-group-item-action';
|
||||
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.location) {
|
||||
window.weatherLocationStorage.add(this.location);
|
||||
}
|
||||
}
|
||||
|
||||
handleRemoveClick(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (this.location) {
|
||||
window.weatherLocationStorage.remove(this.location.id);
|
||||
}
|
||||
this.remove();
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("weather-location", WeatherLocationElement);
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
weatherLocationStorage: WeatherLocationStorage;
|
||||
weatherLocationManager: WeatherLocationManager;
|
||||
}
|
||||
}
|
||||
|
||||
window.weatherLocationStorage = new WeatherLocationStorage();
|
||||
window.weatherLocationManager = new WeatherLocationManager();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,6 +5,7 @@ from tests.common.mock import MockSource
|
||||
MATCHTV_MOCK_SOURCE = MockSource(
|
||||
Path(__file__).parent,
|
||||
{
|
||||
"test": "test.html",
|
||||
"test": "test.json",
|
||||
"channels": "channels.json",
|
||||
},
|
||||
)
|
||||
|
||||
588
tests/data/matchtv/channels.json
Normal file
588
tests/data/matchtv/channels.json
Normal file
@@ -0,0 +1,588 @@
|
||||
{
|
||||
"result": [
|
||||
{
|
||||
"id": 10,
|
||||
"weight": 1,
|
||||
"alias": "matchtv",
|
||||
"name": "\u041c\u0430\u0442\u0447 \u0422\u0412",
|
||||
"description": "\u003Cp\u003E\u003Cspan style=\u0022font-weight: 700;\u0022\u003E\u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb\u003C\/span\u003E\u0026nbsp;\u2014 \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0439 \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u043e\u0026nbsp;\u0441\u043f\u043e\u0440\u0442\u0435 \u0438\u0026nbsp;\u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u043e\u0431\u0440\u0430\u0437\u0435 \u0436\u0438\u0437\u043d\u0438.\u003C\/p\u003E\u003Cp\u003E\u041c\u0430\u0442\u0447 \u0422\u0412\u0026nbsp;\u2014 \u044d\u0442\u043e\u0026nbsp;\u003Cspan style=\u0022font-weight: 700;\u0022\u003E\u0422\u0440\u0435\u0442\u044c\u044f \u043a\u043d\u043e\u043f\u043a\u0430\u003C\/span\u003E\u0026nbsp;\u0432\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430!\u003Cbr\u003E\u003C\/p\u003E\u003Cp\u003E\u003Cspan style=\u0022font-weight: 700;\u0022\u003E\u0412\u0026nbsp;\u043d\u0430\u0448\u0435\u043c \u044d\u0444\u0438\u0440\u0435\u003C\/span\u003E\u0026nbsp;\u2014 \u043d\u043e\u0432\u043e\u0441\u0442\u0438, \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0438\u0026nbsp;\u0440\u0430\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0446\u0438\u043a\u043b\u044b \u0438\u0026nbsp;\u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436\u0438, \u0440\u0435\u0430\u043b\u0438\u0442\u0438- \u0438\u0026nbsp;\u0442\u043e\u043a-\u0448\u043e\u0443, \u0445\u0443\u0434\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b \u0438\u0026nbsp;\u0441\u0435\u0440\u0438\u0430\u043b\u044b:\u0026nbsp;\u003Cspan style=\u0022font-weight: 700;\u0022\u003E\u0432\u0441\u0451 \u043e\u0026nbsp;\u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u043c \u0438\u0026nbsp;\u043c\u0438\u0440\u043e\u0432\u043e\u043c \u0441\u043f\u043e\u0440\u0442\u0435!\u003C\/span\u003E\u003C\/p\u003E\u003Cp\u003E\u041a\u0430\u043d\u0430\u043b \u043e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0435\u0442 \u043b\u0443\u0447\u0448\u0438\u0445 \u0441\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u044b\u0445\u0026nbsp;\u003Cspan style=\u0022font-weight: 700;\u0022\u003E\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432 \u0438\u0026nbsp;\u0432\u0435\u0434\u0443\u0449\u0438\u0445\u003C\/span\u003E. \u042d\u0442\u043e \u0441\u0430\u043c\u0430\u044f \u043e\u043f\u044b\u0442\u043d\u0430\u044f \u0438\u0026nbsp;\u043f\u0440\u043e\u0444\u0435\u0441\u0441\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u0430\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043d\u0430\u0026nbsp;\u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u043c \u0422\u0412\u0026nbsp;\u2014\u0026nbsp;\u003Cspan style=\u0022font-weight: 700;\u0022\u003E\u0432\u043c\u0435\u0441\u0442\u0435 \u0441\u0026nbsp;\u043d\u0430\u0448\u0438\u043c\u0438 \u0437\u0440\u0438\u0442\u0435\u043b\u044f\u043c\u0438 \u0438\u0026nbsp;\u0431\u043e\u043b\u0435\u043b\u044c\u0449\u0438\u043a\u0430\u043c\u0438\u003C\/span\u003E\u0026nbsp;\u043c\u044b\u0026nbsp;\u043f\u0440\u043e\u0448\u043b\u0438 \u0432\u0441\u0435 \u0432\u0430\u0436\u043d\u0435\u0439\u0448\u0438\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b \u0438\u0026nbsp;\u043f\u0435\u0440\u0432\u0435\u043d\u0441\u0442\u0432\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0445 \u043b\u0435\u0442.\u003C\/p\u003E\u003Cp\u003E\u041c\u044b\u0026nbsp;\u0437\u043e\u0432\u0435\u043c \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u044c\u0441\u044f \u043a\u0026nbsp;\u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u0443 \u043b\u044e\u0434\u0435\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e-\u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u043c\u0443 \u043b\u044e\u0431\u044f\u0442 \u0441\u043f\u043e\u0440\u0442 \u0438\u0026nbsp;\u0436\u0438\u0432\u0443\u0442\u0026nbsp;\u0438\u043c: \u0443\u0026nbsp;\u044d\u043a\u0440\u0430\u043d\u043e\u0432\u0026nbsp;\u0422\u0412, \u043d\u0430\u0026nbsp;\u0441\u0442\u0430\u0434\u0438\u043e\u043d\u0430\u0445 \u0438\u043b\u0438 \u0432\u0026nbsp;\u0441\u043f\u043e\u0440\u0442\u0437\u0430\u043b\u0430\u0445.\u003C\/p\u003E\u003Cp\u003E#\u0432\u0441\u0435\u043d\u0430\u043c\u0430\u0442\u0447!\u003C\/p\u003E",
|
||||
"shortDescription": "\u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb \u2014 \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0439 \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u043e \u0441\u043f\u043e\u0440\u0442\u0435 \u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u043e\u0431\u0440\u0430\u0437\u0435 \u0436\u0438\u0437\u043d\u0438. \u041c\u0430\u0442\u0447 \u0422\u0412 \u2014 \u044d\u0442\u043e \u0422\u0440\u0435\u0442\u044c\u044f \u043a\u043d\u043e\u043f\u043a\u0430 \u0432\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430!\r\n\u0412 \u043d\u0430\u0448\u0435\u043c \u044d\u0444\u0438\u0440\u0435 \u2014 \u043d\u043e\u0432\u043e\u0441\u0442\u0438, \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0438 \u0440\u0430\u0437\u0432\u043b\u0435\u043a\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0446\u0438\u043a\u043b\u044b \u0438 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436\u0438, \u0440\u0435\u0430\u043b\u0438\u0442\u0438- \u0438 \u0442\u043e\u043a-\u0448\u043e\u0443, \u0445\u0443\u0434\u043e\u0436\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b \u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u044b: \u0432\u0441\u0451 \u043e \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u043c \u0438 \u043c\u0438\u0440\u043e\u0432\u043e\u043c \u0441\u043f\u043e\u0440\u0442\u0435!",
|
||||
"videoPlayerCode": "https:\/\/video.matchtv.ru\/iframe\/channel\/106",
|
||||
"platformVideoId": null,
|
||||
"package": null,
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606457,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u041c\u0430\u0442\u0447 \u0422\u0412",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447 \u0422\u0412. \u0410\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430 \u0441\u043e\u0431\u044b\u0442\u0438\u0439, \u043e\u0431\u0437\u043e\u0440\u044b, \u0438\u043d\u0442\u0435\u0440\u0432\u044c\u044e \u0441\u043f\u043e\u0440\u0442\u0441\u043c\u0435\u043d\u043e\u0432",
|
||||
"seoDescription": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447 \u0422\u0412. \u0420\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0439 \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u043e \u0441\u043f\u043e\u0440\u0442\u0435 \u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u043c \u043e\u0431\u0440\u0430\u0437\u0435 \u0436\u0438\u0437\u043d\u0438.",
|
||||
"seoKeywords": "\u041c\u0430\u0442\u0447 \u0422\u0412",
|
||||
"seoMeta": "\u041c\u0430\u0442\u0447 \u0422\u0412 \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/70c6\/1bb4\/70c61bb4b7a0ccb739834fa99a5dd9a8.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/0de1\/49ba\/0de149babcacd733028970463f32a00d.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/4fbd\/42cc\/4fbd42ccdde063ff630a7970dbce1034.png",
|
||||
"tvChannel": { "id": 130, "name": "\u041c\u0430\u0442\u0447 \u0422\u0412", "serviceTvId": 2883, "priority": 20 },
|
||||
"terms": [
|
||||
{ "id": 167824, "title": "\u041c\u0430\u0442\u0447 \u0422\u0412" },
|
||||
{ "id": 174766, "title": "\u0412\u0442\u043e\u0440\u043e\u0435 \u0434\u044b\u0445\u0430\u043d\u0438\u0435" },
|
||||
{
|
||||
"id": 174768,
|
||||
"title": "\u041a\u043e\u043d\u0442\u0438\u043d\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0432\u0435\u0447\u0435\u0440"
|
||||
},
|
||||
{
|
||||
"id": 174769,
|
||||
"title": "\u041b\u0443\u0447\u0448\u0430\u044f \u0438\u0433\u0440\u0430 \u0441 \u043c\u044f\u0447\u043e\u043c"
|
||||
},
|
||||
{
|
||||
"id": 174770,
|
||||
"title": "\u0411\u0438\u0430\u0442\u043b\u043e\u043d \u0441 \u0414\u043c\u0438\u0442\u0440\u0438\u0435\u043c \u0413\u0443\u0431\u0435\u0440\u043d\u0438\u0435\u0432\u044b\u043c"
|
||||
},
|
||||
{ "id": 174771, "title": "\u041e\u0441\u043e\u0431\u044b\u0439 \u0434\u0435\u043d\u044c" },
|
||||
{ "id": 174772, "title": "\u041c\u0430\u043c\u0430 \u0432 \u0438\u0433\u0440\u0435" },
|
||||
{
|
||||
"id": 174773,
|
||||
"title": "\u0410\u043d\u0430\u0442\u043e\u043c\u0438\u044f \u0441\u043f\u043e\u0440\u0442\u0430"
|
||||
},
|
||||
{ "id": 174774, "title": "\u0414\u0443\u0431\u043b\u0435\u0440" },
|
||||
{
|
||||
"id": 174775,
|
||||
"title": "\u0421\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0435\u0441"
|
||||
},
|
||||
{ "id": 174776, "title": "1+1" },
|
||||
{ "id": 174778, "title": "\u0411\u0435\u0437\u0443\u043c\u043d\u044b\u0439 \u0441\u043f\u043e\u0440\u0442" },
|
||||
{ "id": 176117, "title": "\u0414\u0435\u0442\u0441\u043a\u0438\u0439 \u0432\u043e\u043f\u0440\u043e\u0441" },
|
||||
{ "id": 176120, "title": "\u0422\u043e\u0447\u043a\u0430" },
|
||||
{ "id": 176121, "title": "\u0421\u043f\u043e\u0440\u0442 \u0437\u0430 \u0433\u0440\u0430\u043d\u044c\u044e" },
|
||||
{
|
||||
"id": 183813,
|
||||
"title": "\u0412\u0441\u0435 \u043d\u0430 \u0444\u0443\u0442\u0431\u043e\u043b: \u0410\u0444\u0438\u0448\u0430"
|
||||
},
|
||||
{ "id": 183814, "title": "\u0417\u0430\u043a\u0443\u043b\u0438\u0441\u044c\u0435 \u041a\u0425\u041b" },
|
||||
{
|
||||
"id": 183815,
|
||||
"title": "\u0417\u0430\u043a\u0443\u043b\u0438\u0441\u044c\u0435 \u0427\u041c \u0441 \u0410\u043b\u0438\u0441\u043e\u0439 \u0417\u043d\u0430\u0440\u043e\u043a"
|
||||
},
|
||||
{
|
||||
"id": 183816,
|
||||
"title": "\u0411\u043e\u0439 \u0432 \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u0433\u043e\u0440\u043e\u0434\u0435"
|
||||
},
|
||||
{ "id": 183818, "title": "\u0418\u043d\u0441\u043f\u0435\u043a\u0442\u043e\u0440 \u0417\u041e\u0416" },
|
||||
{
|
||||
"id": 183819,
|
||||
"title": "\u0414\u0435\u043d\u044c\u0433\u0438 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u0441\u043f\u043e\u0440\u0442\u0430"
|
||||
},
|
||||
{ "id": 183820, "title": "\u041e\u043b\u0438\u043c\u043f\u0438\u0439\u0446\u044b.Live" },
|
||||
{
|
||||
"id": 183821,
|
||||
"title": "\u041f\u043e\u0441\u043b\u0435 \u0444\u0443\u0442\u0431\u043e\u043b\u0430 \u0441 \u0413\u0435\u043e\u0440\u0433\u0438\u0435\u043c \u0427\u0435\u0440\u0434\u0430\u043d\u0446\u0435\u0432\u044b\u043c"
|
||||
},
|
||||
{ "id": 183822, "title": "\u0414\u0435\u0441\u044f\u0442\u043a\u0430" },
|
||||
{ "id": 183824, "title": "\u0412\u0441\u0435 \u043d\u0430 \u0444\u0443\u0442\u0431\u043e\u043b" },
|
||||
{ "id": 183825, "title": "\u0412\u0441\u0435 \u043d\u0430 \u041c\u0430\u0442\u0447!" },
|
||||
{
|
||||
"id": 183826,
|
||||
"title": "\u041a\u0443\u0431\u043e\u043a \u0432\u043e\u0439\u043d\u044b \u0438 \u043c\u0438\u0440\u0430"
|
||||
},
|
||||
{
|
||||
"id": 185340,
|
||||
"title": "\u0421\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u044b\u0439 \u0437\u0430\u0433\u043e\u0432\u043e\u0440"
|
||||
},
|
||||
{
|
||||
"id": 185341,
|
||||
"title": "\u0421\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u044b\u0439 \u0440\u0435\u043f\u043e\u0440\u0442\u0435\u0440"
|
||||
},
|
||||
{
|
||||
"id": 187565,
|
||||
"title": "\u0422\u043e\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0444\u0443\u0442\u0431\u043e\u043b"
|
||||
},
|
||||
{ "id": 188522, "title": "\u0410\u0432\u0442\u043e\u0438\u043d\u0441\u043f\u0435\u043a\u0446\u0438\u044f" },
|
||||
{
|
||||
"id": 195075,
|
||||
"title": "\u041a\u043e\u043c\u0430\u043d\u0434\u0430 \u043d\u0430 \u043f\u0440\u043e\u043a\u0430\u0447\u043a\u0443 "
|
||||
},
|
||||
{ "id": 195076, "title": "\u0411\u0435\u0448\u0435\u043d\u0430\u044f \u0441\u0443\u0448\u043a\u0430" },
|
||||
{ "id": 195079, "title": "\u0411\u043b\u0438\u0446" },
|
||||
{ "id": 195510, "title": "\u0421\u0438\u043b\u044c\u043d\u043e\u0435 \u0448\u043e\u0443" },
|
||||
{
|
||||
"id": 195771,
|
||||
"title": "\u0423\u0442\u043e\u043c\u043b\u0435\u043d\u043d\u044b\u0435 \u0441\u043b\u0430\u0432\u043e\u0439"
|
||||
},
|
||||
{
|
||||
"id": 198090,
|
||||
"title": "\u0420\u043e\u0441\u0441\u0438\u044f \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u0430\u044f"
|
||||
},
|
||||
{ "id": 198535, "title": "\u0414\u0435\u043d\u044c \u0418\u043a\u0441" },
|
||||
{ "id": 199051, "title": "\u0412\u044d\u043b\u043a\u0430\u043c \u0442\u0443 \u0420\u0430\u0448\u0430" },
|
||||
{ "id": 199111, "title": "\u041d\u0430\u0448\u0438 \u043d\u0430 \u0427\u041c" },
|
||||
{ "id": 199112, "title": "\u0420\u043e\u0441\u0441\u0438\u044f \u0436\u0434\u0435\u0442! " },
|
||||
{
|
||||
"id": 199117,
|
||||
"title": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0438\u044f \u0441\u0431\u043e\u0440\u043d\u043e\u0439"
|
||||
},
|
||||
{ "id": 199447, "title": "\u041d\u0430\u0448\u0438 \u043f\u043e\u0431\u0435\u0434\u044b" },
|
||||
{
|
||||
"id": 199675,
|
||||
"title": "\u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430. Live"
|
||||
},
|
||||
{ "id": 201543, "title": "\u0428\u0435\u043b\u043a\u043e\u0432\u044b\u0439 \u043f\u0443\u0442\u044c" },
|
||||
{
|
||||
"id": 203403,
|
||||
"title": "\u0422\u0430\u0435\u0442 \u043b\u0435\u0434 \u0441 \u0410\u043b\u0435\u043a\u0441\u0435\u0435\u043c \u042f\u0433\u0443\u0434\u0438\u043d\u044b\u043c"
|
||||
},
|
||||
{
|
||||
"id": 204089,
|
||||
"title": "\u0421 \u0447\u0435\u0433\u043e \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0444\u0443\u0442\u0431\u043e\u043b"
|
||||
},
|
||||
{ "id": 204096, "title": "\u0412\u0441\u0435 \u043d\u0430 \u0445\u043e\u043a\u043a\u0435\u0439" },
|
||||
{ "id": 204138, "title": "\u041d\u043e\u0432\u043e\u0441\u0442\u0438" },
|
||||
{ "id": 204186, "title": "\u0420\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u043e\u0440\u0442" },
|
||||
{ "id": 204582, "title": "\u0413\u0435\u043d \u043f\u043e\u0431\u0435\u0434\u044b" },
|
||||
{ "id": 204593, "title": "\u0424\u0443\u0442\u0411\u043e\u043b\u044c\u043d\u043e" },
|
||||
{ "id": 204848, "title": "\u041a\u0438\u0431\u0435\u0440\u0430\u0442\u043b\u0435\u0442\u0438\u043a\u0430" },
|
||||
{ "id": 205028, "title": "\u041a\u0443\u0440\u0441 \u0415\u0432\u0440\u043e" },
|
||||
{ "id": 205174, "title": "\u0421\u0430\u043c\u044b\u0435 \u0441\u0438\u043b\u044c\u043d\u044b\u0435" },
|
||||
{ "id": 206192, "title": "\u0412\u0430\u043d\u043a\u0443\u0432\u0435\u0440. Live" },
|
||||
{ "id": 206286, "title": "\u0414\u0430\u043a\u0430\u0440-2019" },
|
||||
{ "id": 206303, "title": "\u041a\u0430\u0436\u0434\u043e\u043c\u0443 - \u0441\u043f\u043e\u0440\u0442" },
|
||||
{ "id": 206587, "title": "\u041a\u0430\u0442\u0430\u0440 Live" },
|
||||
{ "id": 206631, "title": "\u041a\u0430\u0442\u0430\u0440\u0441\u043a\u0438\u0435 \u0438\u0433\u0440\u044b" },
|
||||
{ "id": 207868, "title": "\u0412\u0441\u0435 \u043d\u0430 \u043b\u044b\u0436\u0438!" },
|
||||
{
|
||||
"id": 208192,
|
||||
"title": "\u0414\u043d\u0435\u0432\u043d\u0438\u043a \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0438\u0430\u0434\u044b "
|
||||
},
|
||||
{
|
||||
"id": 208377,
|
||||
"title": "\u0422\u0440\u0435\u043d\u0435\u0440\u0441\u043a\u0438\u0439 \u0448\u0442\u0430\u0431"
|
||||
},
|
||||
{ "id": 208446, "title": "\u041a\u0430\u043f\u0438\u0442\u0430\u043d\u044b" },
|
||||
{
|
||||
"id": 208506,
|
||||
"title": "\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436"
|
||||
},
|
||||
{
|
||||
"id": 208625,
|
||||
"title": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b"
|
||||
},
|
||||
{ "id": 208750, "title": "\u0418\u0433\u0440\u0430\u0435\u043c \u0437\u0430 \u0432\u0430\u0441" },
|
||||
{ "id": 209068, "title": "\u041c\u0430\u0441\u0442\u0435\u0440 \u0441\u043f\u043e\u0440\u0442\u0430" },
|
||||
{
|
||||
"id": 209349,
|
||||
"title": "\u041d\u0435\u0438\u0437\u0432\u0435\u0434\u0430\u043d\u043d\u0430\u044f \u0445\u043e\u043a\u043a\u0435\u0439\u043d\u0430\u044f \u0420\u043e\u0441\u0441\u0438\u044f"
|
||||
},
|
||||
{
|
||||
"id": 213332,
|
||||
"title": "\u0413\u0440\u0430\u043d-\u043f\u0440\u0438 \u0441 \u0410\u043b\u0435\u043a\u0441\u0435\u0435\u043c \u041f\u043e\u043f\u043e\u0432\u044b\u043c"
|
||||
}
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17829286,
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. \u0421\u0435\u043d\u0435\u0433\u0430\u043b - \u0418\u0440\u0430\u043a. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u041a\u0430\u043d\u0430\u0434\u044b [6+]",
|
||||
"description": "",
|
||||
"start": 1782542400,
|
||||
"finish": 1782550500,
|
||||
"startTime": "2026-06-27T09:40:00+03:00",
|
||||
"finishTime": "2026-06-27T11:55:00+03:00",
|
||||
"ageRating": "6+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17829286-1782495234.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"weight": 2,
|
||||
"alias": "premier",
|
||||
"name": "\u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440",
|
||||
"description": "\u003Cp\u003E\u041f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u043e\u0026nbsp;\u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u043c \u0444\u0443\u0442\u0431\u043e\u043b\u0435:\u003C\/p\u003E\u003Cul\u003E\u003Cli\u003E\u0432\u0441\u0435 \u043c\u0430\u0442\u0447\u0438 \u041c\u0418\u0420 \u0420\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u0439 \u041f\u0440\u0435\u043c\u044c\u0435\u0440-\u041b\u0438\u0433\u0438;\u003C\/li\u003E\u003Cli\u003E\u043a\u0443\u0431\u043a\u043e\u0432\u044b\u0435 \u043c\u0430\u0442\u0447\u0438 \u0438\u0026nbsp;\u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0435\u0441\u043a\u0438\u0435 \u0432\u0441\u0442\u0440\u0435\u0447\u0438 \u043a\u043b\u0443\u0431\u043e\u0432 \u0420\u041f\u041b;\u003Cbr\u003E\u003C\/li\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u041b\u0438\u0433\u0438 PARI;\u003C\/li\u003E\u003Cli\u003E\u0438\u0433\u0440\u044b \u0441\u0431\u043e\u0440\u043d\u043e\u0439 \u0420\u043e\u0441\u0441\u0438\u0438 \u0438\u0026nbsp;\u043c\u043e\u043b\u043e\u0434\u0435\u0436\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043d\u043e\u0439.\u003Cbr\u003E\u003C\/li\u003E\u003C\/ul\u003E\u003Cp\u003E\u0422\u0430\u043a\u0436\u0435 \u0432\u0026nbsp;\u044d\u0444\u0438\u0440\u0435 \u041c\u0410\u0422\u0427 \u041f\u0420\u0415\u041c\u042c\u0415\u0420: \u044d\u043a\u0441\u043a\u043b\u044e\u0437\u0438\u0432\u043d\u044b\u0435 \u0432\u044b\u043f\u0443\u0441\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c \u00ab8-16\u00bb, \u00ab\u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u0438\u0433\u0440\u044b\u00bb, \u00ab\u0420\u0435\u0446\u0435\u043f\u0422\u0443\u0440\u0430\u00bb, \u0441\u0442\u0443\u0434\u0438\u0439\u043d\u0430\u044f \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430, \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436\u0438. \u0412\u0435\u0441\u044c \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0439 \u0444\u0443\u0442\u0431\u043e\u043b \u0432\u0026nbsp;\u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435! \u042d\u0442\u043e \u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b\u0026nbsp;\u0434\u0435\u043b\u0430\u0435\u043c \u0432\u043c\u0435\u0441\u0442\u0435.\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u041f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b \u043e \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u043c \u0444\u0443\u0442\u0431\u043e\u043b\u0435, \u0433\u0434\u0435 \u0435\u0441\u0442\u044c \u0432\u0441\u0435 \u043c\u0430\u0442\u0447\u0438 \u041c\u0418\u0420 \u0420\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u0439 \u041f\u0440\u0435\u043c\u044c\u0435\u0440-\u041b\u0438\u0433\u0438, \u043a\u0443\u0431\u043a\u043e\u0432\u044b\u0435 \u043c\u0430\u0442\u0447\u0438 \u0438 \u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0435\u0441\u043a\u0438\u0435 \u0432\u0441\u0442\u0440\u0435\u0447\u0438 \u043a\u043b\u0443\u0431\u043e\u0432 \u041c\u0438\u0440 \u0420\u041f\u041b, \u0438\u0433\u0440\u044b \u0441\u0431\u043e\u0440\u043d\u043e\u0439 \u0420\u043e\u0441\u0441\u0438\u0438 \u0438 \u043c\u043e\u043b\u043e\u0434\u0435\u0436\u043d\u043e\u0439 \u0441\u0431\u043e\u0440\u043d\u043e\u0439 \u0438 \u00ab\u043f\u0435\u0440\u0435\u043a\u043b\u0438\u0447\u043a\u0438\u00bb \u043c\u0435\u0436\u0434\u0443 \u043c\u0430\u0442\u0447\u0430\u043c\u0438, \u0438\u0434\u0443\u0449\u0438\u043c\u0438 \u0432 \u043e\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u044f.\r\n\u0422\u0430\u043a\u0436\u0435 \u0432 \u044d\u0444\u0438\u0440\u0435 \u041c\u0410\u0422\u0427 \u041f\u0420\u0415\u041c\u042c\u0415\u0420: \u044d\u043a\u0441\u043a\u043b\u044e\u0437\u0438\u0432\u043d\u044b\u0435 \u0432\u044b\u043f\u0443\u0441\u043a\u0438 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c \u00ab8-16\u00bb, \u00ab\u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u0438\u0433\u0440\u044b\u00bb, \u00ab\u0420\u0435\u0446\u0435\u043f\u0422\u0443\u0440\u0430\u00bb, \u0441\u0442\u0443\u0434\u0438\u0439\u043d\u0430\u044f \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430, \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0435 \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436\u0438. \u0412\u0435\u0441\u044c \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u0438\u0439 \u0444\u0443\u0442\u0431\u043e\u043b \u0432 \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435! \u042d\u0442\u043e \u0438\u0441\u0442\u043e\u0440\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u044b \u0434\u0435\u043b\u0430\u0435\u043c \u0432\u043c\u0435\u0441\u0442\u0435.",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_04153373fcaa00fc4e37db89e6d7f82a\/17_91502360\/8be99f14cce8abdaa9e2dd4bc8f12c8c\/4934710878?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "41f83e48ab4a92e5afd57c6ab57b2584",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 1142310,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043c\u0430\u0442\u0447\u0435\u0439 \u0420\u041f\u041b \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0432\u0441\u0435\u0445 \u043c\u0430\u0442\u0447\u0435\u0439 \u041c\u0438\u0440 \u0420\u041f\u041b, \u0438\u0433\u0440 \u041a\u0443\u0431\u043a\u0430 \u0420\u043e\u0441\u0441\u0438\u0438, \u0442\u043e\u0432\u0430\u0440\u0438\u0449\u0435\u0441\u043a\u0438\u0445 \u0432\u0441\u0442\u0440\u0435\u0447 \u043a\u043b\u0443\u0431\u043e\u0432 \u041c\u0438\u0440 \u0420\u041f\u041b \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440\u00bb!",
|
||||
"seoKeywords": "\u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/1969\/778c\/1969778c1a0e470077b19bd32648f18f.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/dd5c\/7cb6\/dd5c7cb6cf41d45010a6485dd66415d9.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/7168\/f84a\/7168f84a807771d47d5af0d7a0749ca1.svg",
|
||||
"tvChannel": {
|
||||
"id": 7,
|
||||
"name": "\u041c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440",
|
||||
"serviceTvId": 3459,
|
||||
"priority": 25
|
||||
},
|
||||
"terms": [
|
||||
{ "id": 20317, "title": "\u041c\u0438\u0440 \u0420\u041f\u041b" },
|
||||
{
|
||||
"id": 205664,
|
||||
"title": "\u0424\u041e\u041d\u0411\u0415\u0422 \u041a\u0423\u0411\u041e\u041a \u041c\u0410\u0422\u0427 \u041f\u0420\u0415\u041c\u042c\u0415\u0420"
|
||||
},
|
||||
{
|
||||
"id": 211845,
|
||||
"title": "\u041a\u0443\u0431\u043e\u043a \u041f\u0430\u0440\u0438\u043c\u0430\u0442\u0447 \u041f\u0440\u0435\u043c\u044c\u0435\u0440"
|
||||
},
|
||||
{ "id": 204095, "title": "8-16" },
|
||||
{ "id": 205212, "title": "\u0418\u043d\u0441\u0430\u0439\u0434\u0435\u0440\u044b" },
|
||||
{
|
||||
"id": 208377,
|
||||
"title": "\u0422\u0440\u0435\u043d\u0435\u0440\u0441\u043a\u0438\u0439 \u0448\u0442\u0430\u0431"
|
||||
},
|
||||
{ "id": 208446, "title": "\u041a\u0430\u043f\u0438\u0442\u0430\u043d\u044b" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17828869,
|
||||
"title": "\u0022\u0421\u043e\u0432\u0435\u0442\u0441\u043a\u0438\u0439 \u0444\u0443\u0442\u0431\u043e\u043b\u0022. \u0426\u0421\u041a\u0410 (\u041c\u043e\u0441\u043a\u0432\u0430). \u0427\u0430\u0441\u0442\u044c 2 [12+]",
|
||||
"description": "\u041a\u0430\u043a \u043d\u0430\u0447\u0438\u043d\u0430\u043b\u0441\u044f \u0441\u043e\u0432\u0435\u0442\u0441\u043a\u0438\u0439 \u0444\u0443\u0442\u0431\u043e\u043b? \u041a\u0430\u043a\u0438\u043c\u0438 \u0431\u044b\u043b\u0438 \u0435\u0433\u043e \u043f\u0435\u0440\u0432\u044b\u0435 \u0443\u0441\u043f\u0435\u0445\u0438? \u041a\u0430\u043a\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0441\u0442\u0430\u043b\u0438 \u0441\u0438\u043c\u0432\u043e\u043b\u0430\u043c\u0438 \u044d\u043f\u043e\u0445\u0438? \u041a\u0430\u043a\u043e\u0432\u044b \u0431\u044b\u043b\u0438 \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 \u0441\u043e\u0432\u0435\u0442\u0441\u043a\u043e\u0439 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0439 \u0448\u043a\u043e\u043b\u044b? \u041e\u0442\u0432\u0435\u0442\u044b \u043d\u0430 \u044d\u0442\u0438 \u0432\u043e\u043f\u0440\u043e\u0441\u044b \u2014 \u0432 \u043d\u0430\u0448\u0435\u0439 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0435!",
|
||||
"start": 1782547500,
|
||||
"finish": 1782549000,
|
||||
"startTime": "2026-06-27T11:05:00+03:00",
|
||||
"finishTime": "2026-06-27T11:30:00+03:00",
|
||||
"ageRating": "12+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17828869-1781880696.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"weight": 3,
|
||||
"alias": "strana",
|
||||
"name": "\u041c\u0430\u0442\u0447! \u0421\u0442\u0440\u0430\u043d\u0430",
|
||||
"description": "\u003Cp\u003E\u042d\u0442\u043e \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b, \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u043e\u0439 \u0436\u0438\u0437\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u044b \u0438\u0026nbsp;\u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044e \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u0441\u043f\u043e\u0440\u0442\u0430. \u0411\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u0435\u0449\u0430\u043d\u0438\u044f\u0026nbsp;\u2014 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043a\u043b\u0443\u0431\u043d\u044b\u0445 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439. \u0410\u0026nbsp;\u0442\u0430\u043a\u0436\u0435 \u0432\u0435\u0441\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043d\u0442: \u043d\u043e\u0432\u043e\u0441\u0442\u0438, \u0441\u043f\u0435\u0446\u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b \u0438\u0026nbsp;\u043f\u0440\u0435\u043c\u044c\u0435\u0440\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c. \u0421\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430\u0448 \u0441\u043f\u043e\u0440\u0442 \u0432\u0441\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u043e\u0439 \u0438\u0026nbsp;\u0431\u043e\u043b\u0435\u0435\u043c \u0437\u0430 \u043d\u0430\u0448\u0438\u0445\u003C\/p\u003E",
|
||||
"shortDescription": "\u042d\u0442\u043e \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u0430\u043d\u0430\u043b, \u0446\u0435\u043b\u0438\u043a\u043e\u043c \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u043d\u044b\u0439 \u0441\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u043e\u0439 \u0436\u0438\u0437\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u044b \u0438 \u0440\u0430\u0437\u0432\u0438\u0442\u0438\u044e \u0440\u043e\u0441\u0441\u0438\u0439\u0441\u043a\u043e\u0433\u043e \u0441\u043f\u043e\u0440\u0442\u0430. \u0411\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u0435\u0449\u0430\u043d\u0438\u044f \u2014 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043a\u043b\u0443\u0431\u043d\u044b\u0445 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439. \u0410 \u0442\u0430\u043a\u0436\u0435 \u0432\u0435\u0441\u044c \u0430\u043a\u0442\u0443\u0430\u043b\u044c\u043d\u044b\u0439 \u043a\u043e\u043d\u0442\u0435\u043d\u0442: \u043d\u043e\u0432\u043e\u0441\u0442\u0438, \u0441\u043f\u0435\u0446\u043f\u0440\u043e\u0435\u043a\u0442\u044b, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b \u0438 \u043f\u0440\u0435\u043c\u044c\u0435\u0440\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c. \u0421\u043c\u043e\u0442\u0440\u0438\u043c \u043d\u0430\u0448 \u0441\u043f\u043e\u0440\u0442 \u0432\u0441\u0435\u0439 \u0441\u0442\u0440\u0430\u043d\u043e\u0439 \u0438 \u0431\u043e\u043b\u0435\u0435\u043c \u0437\u0430 \u043d\u0430\u0448\u0438\u0445!",
|
||||
"videoPlayerCode": "https:\/\/video.matchtv.ru\/iframe\/channel\/96",
|
||||
"platformVideoId": null,
|
||||
"package": null,
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606417,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u041c\u0430\u0442\u0447! C\u0442\u0440\u0430\u043d\u0430",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0421\u0442\u0440\u0430\u043d\u0430 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0421\u0442\u0440\u0430\u043d\u0430\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439, \u043d\u043e\u0432\u043e\u0441\u0442\u0438, \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436\u0438, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b, \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0421\u0442\u0440\u0430\u043d\u0430\u00bb!",
|
||||
"seoKeywords": "\u041c\u0430\u0442\u0447! C\u0442\u0440\u0430\u043d\u0430",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/492b\/2610\/492b26105962fd92ea1eb704a40d009b.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/2fd5\/d4e4\/2fd5d4e435c7c25bc29b752383b2e02e.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/a86c\/df54\/a86cdf54fdb7ab9e893986bcbbf8e017.svg",
|
||||
"tvChannel": {
|
||||
"id": 143,
|
||||
"name": "\u041c\u0430\u0442\u0447 \u0421\u0442\u0440\u0430\u043d\u0430",
|
||||
"serviceTvId": 3568,
|
||||
"priority": 28
|
||||
},
|
||||
"terms": [
|
||||
{ "id": 207869, "title": "\u041c\u0430\u0442\u0447 \u0421\u0442\u0440\u0430\u043d\u0430" },
|
||||
{ "id": 183823, "title": "\u0412\u0438\u0434 \u0441\u0432\u0435\u0440\u0445\u0443" },
|
||||
{
|
||||
"id": 208192,
|
||||
"title": "\u0414\u043d\u0435\u0432\u043d\u0438\u043a \u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0438\u0430\u0434\u044b "
|
||||
},
|
||||
{
|
||||
"id": 208212,
|
||||
"title": "\u0423\u043d\u0438\u0432\u0435\u0440\u0441\u0438\u0430\u0434\u0430-2019. \u0421\u0442\u0443\u0434\u0438\u044f"
|
||||
},
|
||||
{ "id": 208507, "title": "\u0421\u0442\u0440\u0430\u043d\u0430.Live" },
|
||||
{
|
||||
"id": 208508,
|
||||
"title": "\u0421\u0442\u0440\u0430\u043d\u0430 \u0441\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u0430\u044f"
|
||||
},
|
||||
{
|
||||
"id": 208958,
|
||||
"title": "\u0421\u0442\u0440\u0430\u043d\u0430 \u0441\u043c\u043e\u0442\u0440\u0438\u0442 \u0441\u043f\u043e\u0440\u0442"
|
||||
},
|
||||
{ "id": 211925, "title": "\u0418\u0433\u0440\u044b \u043a\u043e\u0440\u043e\u043b\u0435\u0439" },
|
||||
{ "id": 211926, "title": "\u041f\u043e\u0440\u0430 \u043d\u0430 \u0442\u0435\u043d\u043d\u0438\u0441" },
|
||||
{ "id": 252187, "title": "\u041b\u0438\u0446\u0430 \u0441\u0442\u0440\u0430\u043d\u044b" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17830233,
|
||||
"title": "\u0421\u0430\u043c\u0431\u043e. \u041a\u043e\u043c\u0430\u043d\u0434\u043d\u044b\u0439 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u0420\u043e\u0441\u0441\u0438\u0438. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u041c\u043e\u0441\u043a\u0432\u044b [12+]",
|
||||
"description": "",
|
||||
"start": 1782547500,
|
||||
"finish": 1782551700,
|
||||
"startTime": "2026-06-27T11:05:00+03:00",
|
||||
"finishTime": "2026-06-27T12:15:00+03:00",
|
||||
"ageRating": "12+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17830233-1782222276.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"weight": 4,
|
||||
"alias": "futbol-1",
|
||||
"name": "\u0424\u0443\u0442\u0431\u043e\u043b 1 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"description": "\u003Cp\u003E\u0424\u0443\u0442\u0431\u043e\u043b\u0026nbsp;1,2,3\u0026nbsp;\u2014 \u044d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432\u0026nbsp;\u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435. \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432\u0026nbsp;\u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435:\u003Cbr\u003E\u003C\/p\u003E\u003Cul\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438\u0026nbsp;\u043b\u0438\u0433;\u003C\/li\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u0437\u0430\u0026nbsp;\u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043a\u0443\u0431\u043a\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0438\u0440\u0430;\u003C\/li\u003E\u003Cli\u003E\u043e\u0442\u0431\u043e\u0440\u043e\u0447\u043d\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b;\u003C\/li\u003E\u003Cli\u003E\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u0438\u0026nbsp;\u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430.\u003C\/li\u003E\u003C\/ul\u003E\u003Cp\u003E\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435\u0441\u044c \u0438\u0026nbsp;\u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0439\u0442\u0435\u0441\u044c \u0442\u043e\u043f\u043e\u0432\u044b\u043c \u0444\u0443\u0442\u0431\u043e\u043b\u043e\u043c \u0432\u0026nbsp;\u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u043b\u0443\u0447\u0448\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432 \u0438\u0026nbsp;\u044d\u043a\u0441\u043f\u0435\u0440\u0442\u043e\u0432!\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u0424\u0443\u0442\u0431\u043e\u043b 1,2,3 \u2014 \u044d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435. \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043c\u0430\u0442\u0447\u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438 \u043b\u0438\u0433, \u043c\u0430\u0442\u0447\u0438 \u0437\u0430 \u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043a\u0443\u0431\u043a\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0438\u0440\u0430, \u043e\u0442\u0431\u043e\u0440\u043e\u0447\u043d\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u0438 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443.",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_8546b1b795cf43e91e6cbb319db40fea\/17_97595835\/f119d3c8f364d6a7f3747fecf47c70ea\/4934710873?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "df213c9c6dc3669522dc77447bf69bdf",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 1044754,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u0424\u0443\u0442\u0431\u043e\u043b 1 | \u041f\u0430\u043a\u0435\u0442 \u0421\u043f\u043e\u0440\u0442",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 1 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043c\u0430\u0442\u0447\u0435\u0439 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 1\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043c\u0430\u0442\u0447\u0435\u0439 \u0435\u0432\u0440\u043e\u043f\u0435\u0439\u0441\u043a\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438 \u043a\u0443\u0431\u043a\u043e\u0432, \u043e\u0431\u0437\u043e\u0440\u044b \u0438\u0433\u0440 \u0438 \u0442\u0443\u0440\u043e\u0432, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 1\u00bb!",
|
||||
"seoKeywords": "\u0424\u0443\u0442\u0431\u043e\u043b 1",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/042c\/80f5\/042c80f5a8074b9d3458af1561ba5ec0.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/b407\/d1aa\/b407d1aa02288bdfcc7f9736645693d5.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/964b\/5631\/964b5631bc31a3141083cbd8520993f1.svg",
|
||||
"tvChannel": { "id": 8, "name": "\u0424\u0443\u0442\u0431\u043e\u043b 1", "serviceTvId": 205, "priority": 50 },
|
||||
"terms": [
|
||||
{ "id": 118, "title": "\u0424\u0443\u0442\u0431\u043e\u043b" },
|
||||
{ "id": 213528, "title": "\u0424\u0443\u0442\u0431\u043e\u043b 1" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17828977,
|
||||
"title": "\u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. \u0423\u0440\u0443\u0433\u0432\u0430\u0439 - \u0418\u0441\u043f\u0430\u043d\u0438\u044f. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u041c\u0435\u043a\u0441\u0438\u043a\u0438 [6+]",
|
||||
"description": "",
|
||||
"start": 1782547800,
|
||||
"finish": 1782555300,
|
||||
"startTime": "2026-06-27T11:10:00+03:00",
|
||||
"finishTime": "2026-06-27T13:15:00+03:00",
|
||||
"ageRating": "6+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17828977-1782307268.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"weight": 5,
|
||||
"alias": "futbol-2",
|
||||
"name": "\u0424\u0443\u0442\u0431\u043e\u043b 2 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"description": "\u003Cp\u003E\u0424\u0443\u0442\u0431\u043e\u043b\u0026nbsp;1,2,3\u0026nbsp;\u2014 \u044d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432\u0026nbsp;\u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435. \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432\u0026nbsp;\u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435:\u003Cbr\u003E\u003C\/p\u003E\u003Cul\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438\u0026nbsp;\u043b\u0438\u0433;\u003C\/li\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u0437\u0430\u0026nbsp;\u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043a\u0443\u0431\u043a\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0438\u0440\u0430;\u003C\/li\u003E\u003Cli\u003E\u043e\u0442\u0431\u043e\u0440\u043e\u0447\u043d\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b;\u003C\/li\u003E\u003Cli\u003E\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u0438\u0026nbsp;\u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430.\u003C\/li\u003E\u003C\/ul\u003E\u003Cp\u003E\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435\u0441\u044c \u0438\u0026nbsp;\u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0439\u0442\u0435\u0441\u044c \u0442\u043e\u043f\u043e\u0432\u044b\u043c \u0444\u0443\u0442\u0431\u043e\u043b\u043e\u043c \u0432\u0026nbsp;\u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u043b\u0443\u0447\u0448\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432 \u0438\u0026nbsp;\u044d\u043a\u0441\u043f\u0435\u0440\u0442\u043e\u0432!\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u0424\u0443\u0442\u0431\u043e\u043b 1,2,3 \u2014 \u044d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435. \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043c\u0430\u0442\u0447\u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438 \u043b\u0438\u0433, \u043c\u0430\u0442\u0447\u0438 \u0437\u0430 \u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043a\u0443\u0431\u043a\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0438\u0440\u0430, \u043e\u0442\u0431\u043e\u0440\u043e\u0447\u043d\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u0438 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443. \r\n\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435\u0441\u044c \u0438 \u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0439\u0442\u0435\u0441\u044c \u0442\u043e\u043f\u043e\u0432\u044b\u043c \u0444\u0443\u0442\u0431\u043e\u043b\u043e\u043c \u0432 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u043b\u0443\u0447\u0448\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u044d\u043a\u0441\u043f\u0435\u0440\u0442\u043e\u0432!",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_79df03f3381d2bac53c7dbce81bf1003\/17_900a338a44154bfccc74e8b251cb4850\/aa7c274e64856c35114ee0ddfc50dfb4\/4934710899?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "900a338a44154bfccc74e8b251cb4850",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606437,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u0424\u0443\u0442\u0431\u043e\u043b 2 | \u041f\u0430\u043a\u0435\u0442 \u0421\u043f\u043e\u0440\u0442",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 2 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043c\u0430\u0442\u0447\u0435\u0439 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 2\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043c\u0430\u0442\u0447\u0435\u0439 \u0435\u0432\u0440\u043e\u043f\u0435\u0439\u0441\u043a\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438 \u043a\u0443\u0431\u043a\u043e\u0432, \u043e\u0431\u0437\u043e\u0440\u044b \u0438\u0433\u0440 \u0438 \u0442\u0443\u0440\u043e\u0432, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 2\u00bb!",
|
||||
"seoKeywords": "\u0424\u0443\u0442\u0431\u043e\u043b 2",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/ab60\/61f4\/ab6061f4cecf5112c21bd1e549f467dc.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/7013\/6755\/701367555ed778b6fac513ec9060b43e.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/1c9d\/f020\/1c9df020717d419561cac6c7b439dc00.svg",
|
||||
"tvChannel": { "id": 14, "name": "\u0424\u0443\u0442\u0431\u043e\u043b 2", "serviceTvId": 1261, "priority": 60 },
|
||||
"terms": [
|
||||
{ "id": 118, "title": "\u0424\u0443\u0442\u0431\u043e\u043b" },
|
||||
{ "id": 213529, "title": "\u0424\u0443\u0442\u0431\u043e\u043b 2" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17829068,
|
||||
"title": "\u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u0418\u0441\u043f\u0430\u043d\u0438\u0438. \u0411\u0430\u0440\u0441\u0435\u043b\u043e\u043d\u0430 - \u0410\u043b\u0430\u0432\u0435\u0441 [6+]",
|
||||
"description": "",
|
||||
"start": 1782543600,
|
||||
"finish": 1782550800,
|
||||
"startTime": "2026-06-27T10:00:00+03:00",
|
||||
"finishTime": "2026-06-27T12:00:00+03:00",
|
||||
"ageRating": "6+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17829068-1782282983.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"weight": 6,
|
||||
"alias": "futbol-3",
|
||||
"name": "\u0424\u0443\u0442\u0431\u043e\u043b 3 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"description": "\u003Cp\u003E\u0424\u0443\u0442\u0431\u043e\u043b\u0026nbsp;1,2,3\u0026nbsp;\u2014 \u044d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432\u0026nbsp;\u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435. \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432\u0026nbsp;\u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435:\u003Cbr\u003E\u003C\/p\u003E\u003Cul\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438\u0026nbsp;\u043b\u0438\u0433;\u003C\/li\u003E\u003Cli\u003E\u043c\u0430\u0442\u0447\u0438 \u0437\u0430\u0026nbsp;\u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043a\u0443\u0431\u043a\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0438\u0440\u0430;\u003C\/li\u003E\u003Cli\u003E\u043e\u0442\u0431\u043e\u0440\u043e\u0447\u043d\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b;\u003C\/li\u003E\u003Cli\u003E\u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u0438\u0026nbsp;\u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0430.\u003C\/li\u003E\u003C\/ul\u003E\u003Cp\u003E\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0439\u0442\u0435\u0441\u044c \u0438\u0026nbsp;\u043d\u0430\u0441\u043b\u0430\u0436\u0434\u0430\u0439\u0442\u0435\u0441\u044c \u0442\u043e\u043f\u043e\u0432\u044b\u043c \u0444\u0443\u0442\u0431\u043e\u043b\u043e\u043c \u0432\u0026nbsp;\u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 \u043b\u0443\u0447\u0448\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432 \u0438\u0026nbsp;\u044d\u043a\u0441\u043f\u0435\u0440\u0442\u043e\u0432!\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u0424\u0443\u0442\u0431\u043e\u043b 1,2,3 \u2014 \u044d\u0442\u043e \u0441\u0440\u0430\u0437\u0443 \u0442\u0440\u0438 \u043a\u0430\u043d\u0430\u043b\u0430 \u0432 \u043e\u0434\u043d\u043e\u043c \u043f\u0440\u0435\u043c\u0438\u0430\u043b\u044c\u043d\u043e\u043c \u043f\u0430\u043a\u0435\u0442\u0435. \u0417\u0434\u0435\u0441\u044c \u043c\u043e\u0436\u043d\u043e \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0432 \u0432\u044b\u0441\u043e\u043a\u043e\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043c\u0430\u0442\u0447\u0438 \u0432\u0435\u0434\u0443\u0449\u0438\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438 \u043b\u0438\u0433, \u043c\u0430\u0442\u0447\u0438 \u0437\u0430 \u0433\u043b\u0430\u0432\u043d\u044b\u0435 \u043a\u0443\u0431\u043a\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0438\u0440\u0430, \u043e\u0442\u0431\u043e\u0440\u043e\u0447\u043d\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u0438 \u0430\u043d\u0430\u043b\u0438\u0442\u0438\u043a\u0443.",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_1f8dc610f3a99c722390b4da99c3eb97\/17_c21e41488d60529b326f403d57bfc904\/629c2077843e3af30d42fedd5c694a43\/4934710893?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "c21e41488d60529b326f403d57bfc904",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606433,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u0424\u0443\u0442\u0431\u043e\u043b 3",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 3 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u043c\u0430\u0442\u0447\u0435\u0439 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 3\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u043c\u0430\u0442\u0447\u0435\u0439 \u0435\u0432\u0440\u043e\u043f\u0435\u0439\u0441\u043a\u0438\u0445 \u0444\u0443\u0442\u0431\u043e\u043b\u044c\u043d\u044b\u0445 \u0447\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442\u043e\u0432 \u0438 \u043a\u0443\u0431\u043a\u043e\u0432, \u043e\u0431\u0437\u043e\u0440\u044b \u0438\u0433\u0440 \u0438 \u0442\u0443\u0440\u043e\u0432, \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0424\u0443\u0442\u0431\u043e\u043b 3\u00bb!",
|
||||
"seoKeywords": "\u0424\u0443\u0442\u0431\u043e\u043b 3",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/3d63\/1cbe\/3d631cbef4cc0b09408fc9ac1ada1a99.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/e434\/26c6\/e43426c648c7fb88f7e4dd0bd0dbf503.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/9baa\/c2fe\/9baac2fecd8a8a19f8e7072eb6c87cc7.svg",
|
||||
"tvChannel": { "id": 140, "name": "\u0424\u0443\u0442\u0431\u043e\u043b 3", "serviceTvId": 2305, "priority": 70 },
|
||||
"terms": [
|
||||
{ "id": 118, "title": "\u0424\u0443\u0442\u0431\u043e\u043b" },
|
||||
{ "id": 213530, "title": "\u0424\u0443\u0442\u0431\u043e\u043b 3" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17829808,
|
||||
"title": "\u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u0411\u0440\u0430\u0437\u0438\u043b\u0438\u0438. \u0022\u041a\u043e\u0440\u0438\u0442\u0438\u0431\u0430\u0022 - \u0022\u0411\u0430\u0438\u044f\u0022 [6+]",
|
||||
"description": "",
|
||||
"start": 1782543600,
|
||||
"finish": 1782550800,
|
||||
"startTime": "2026-06-27T10:00:00+03:00",
|
||||
"finishTime": "2026-06-27T12:00:00+03:00",
|
||||
"ageRating": "6+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17829808-1781881048.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"weight": 7,
|
||||
"alias": "boec",
|
||||
"name": "\u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"description": "\u003Cp\u003E\u003C\/p\u003E\u003Cp\u003E\u0422\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447 \u0411\u043e\u0435\u0446\u0026nbsp;\u2014 \u044d\u0442\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c \u043f\u0440\u044f\u043c\u044b\u0445 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0439 \u0438\u0437\u0026nbsp;\u0440\u0438\u043d\u0433\u0430 \u0438\u0026nbsp;\u043e\u043a\u0442\u0430\u0433\u043e\u043d\u0430. \u0411\u043e\u043a\u0441, \u041c\u041c\u0410, \u043a\u0438\u043a\u0431\u043e\u043a\u0441\u0438\u043d\u0433, \u0441\u0430\u043c\u0431\u043e \u0438\u0026nbsp;\u0431\u043e\u0440\u044c\u0431\u0430, \u0432\u043e\u0441\u0442\u043e\u0447\u043d\u044b\u0435 \u0435\u0434\u0438\u043d\u043e\u0431\u043e\u0440\u0441\u0442\u0432\u0430 \u0438\u0026nbsp;\u043d\u0435\u0026nbsp;\u0442\u043e\u043b\u044c\u043a\u043e. \u041b\u0443\u0447\u0448\u0438\u0435 \u043f\u043e\u0435\u0434\u0438\u043d\u043a\u0438 \u043e\u0442\u0026nbsp;\u043f\u0440\u043e\u043c\u043e\u0443\u0442\u0435\u0440\u043e\u0432 \u0432\u0441\u0435\u0433\u043e \u043c\u0438\u0440\u0430: \u0431\u043e\u0438 \u043b\u0435\u0433\u0435\u043d\u0434 \u0438\u0026nbsp;\u0432\u044b\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u044f \u0432\u043e\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0437\u0432\u0435\u0437\u0434. \u041c\u0430\u0442\u0447 \u0411\u043e\u0435\u0446\u0026nbsp;\u2014 \u044d\u0442\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0431\u043e\u0435\u0432\u043e\u0435 \u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u043e!\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u0422\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447 \u0411\u043e\u0435\u0446 \u2014 \u044d\u0442\u043e \u043c\u0430\u043a\u0441\u0438\u043c\u0443\u043c \u043f\u0440\u044f\u043c\u044b\u0445 \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0439 \u0438\u0437 \u0440\u0438\u043d\u0433\u0430 \u0438 \u043e\u043a\u0442\u0430\u0433\u043e\u043d\u0430. \u0411\u043e\u043a\u0441, \u041c\u041c\u0410, \u043a\u0438\u043a\u0431\u043e\u043a\u0441\u0438\u043d\u0433, \u0441\u0430\u043c\u0431\u043e \u0438 \u0431\u043e\u0440\u044c\u0431\u0430, \u0432\u043e\u0441\u0442\u043e\u0447\u043d\u044b\u0435 \u0435\u0434\u0438\u043d\u043e\u0431\u043e\u0440\u0441\u0442\u0432\u0430 \u0438 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e. \u041b\u0443\u0447\u0448\u0438\u0435 \u043f\u043e\u0435\u0434\u0438\u043d\u043a\u0438 \u043e\u0442 \u043f\u0440\u043e\u043c\u043e\u0443\u0442\u0435\u0440\u043e\u0432 \u0432\u0441\u0435\u0433\u043e \u043c\u0438\u0440\u0430: \u0431\u043e\u0438 \u043b\u0435\u0433\u0435\u043d\u0434 \u0438 \u0432\u044b\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u044f \u0432\u043e\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0437\u0432\u0435\u0437\u0434. \u041c\u0430\u0442\u0447 \u0411\u043e\u0435\u0446 \u2014 \u044d\u0442\u043e \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0431\u043e\u0435\u0432\u043e\u0435 \u0438\u0441\u043a\u0443\u0441\u0441\u0442\u0432\u043e!",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_8e011e545b9aa539a9e585eb0015d0d4\/17_6a6ffd269d21331c36923698e5058dff\/27a704d2e26671812d744beb5f2174a9\/4934710901?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "6a6ffd269d21331c36923698e5058dff",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606425,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446 | \u041f\u0430\u043a\u0435\u0442 \u0421\u043f\u043e\u0440\u0442",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u041c\u041c\u0410 \u0438 \u0431\u043e\u043a\u0441\u0430 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0442\u0443\u0440\u043d\u0438\u0440\u043e\u0432 \u043f\u043e \u041c\u041c\u0410, \u0431\u043e\u0435\u0432 \u043f\u043e \u0431\u043e\u043a\u0441\u0443, \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u043f\u043e \u0434\u0440\u0443\u0433\u0438\u043c \u0435\u0434\u0438\u043d\u043e\u0431\u043e\u0440\u0441\u0442\u0432\u0430\u043c, \u0442\u0435\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446\u00bb!",
|
||||
"seoKeywords": "\u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446",
|
||||
"seoMeta": "\u003Clink rel=\u0022canonical\u0022 href=\u0022https:\/\/matchtv.ru\/channel\/boec\u0022\u003E",
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/a5ee\/374a\/a5ee374ac3cfa8d2ad130bf37a29a53b.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/5b37\/7bf3\/5b377bf386985eb0bc00c7f727c045c6.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/dfd7\/8054\/dfd78054f3b2b5a3ee14c527632ce48f.svg",
|
||||
"tvChannel": {
|
||||
"id": 3,
|
||||
"name": "\u041c\u0430\u0442\u0447 \u0411\u043e\u0435\u0446",
|
||||
"serviceTvId": 554,
|
||||
"priority": 100
|
||||
},
|
||||
"terms": [
|
||||
{ "id": 175983, "title": "\u0411\u043e\u043a\u0441\/MMA" },
|
||||
{ "id": 213534, "title": "\u041c\u0430\u0442\u0447! \u0411\u043e\u0435\u0446" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17828753,
|
||||
"title": "\u0411\u043e\u043a\u0441. Bare Knuckle FC. \u041a\u044d\u043c\u0435\u0440\u043e\u043d \u0412\u0430\u043d\u043a\u0430\u043c\u043f \u043f\u0440\u043e\u0442\u0438\u0432 \u0413\u0440\u0435\u0433\u043e\u0440\u0438\u0441\u0430 \u0421\u0438\u0441\u043d\u0435\u0440\u043e\u0441\u0430. \u0410\u0440\u043d\u043e\u043b\u044c\u0434 \u0410\u0434\u0430\u043c\u0441 \u043f\u0440\u043e\u0442\u0438\u0432 \u0421\u0442\u0438\u0432\u0430 \u0411\u044d\u043d\u043a\u0441. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u0421\u0428\u0410 [16+]",
|
||||
"description": "",
|
||||
"start": 1782541800,
|
||||
"finish": 1782550500,
|
||||
"startTime": "2026-06-27T09:30:00+03:00",
|
||||
"finishTime": "2026-06-27T11:55:00+03:00",
|
||||
"ageRating": "16+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17828753-1781880649.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"weight": 9,
|
||||
"alias": "arena",
|
||||
"name": "\u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"description": "\u0421\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u0026nbsp;\u0431\u0438\u0430\u0442\u043b\u043e\u043d\u0443 \u0438\u0026nbsp;\u043b\u044b\u0436\u043d\u044b\u043c \u0433\u043e\u043d\u043a\u0430\u043c, \u0444\u0438\u0433\u0443\u0440\u043d\u043e\u043c\u0443 \u043a\u0430\u0442\u0430\u043d\u0438\u044e \u0438\u0026nbsp;\u043f\u043b\u0430\u0432\u0430\u043d\u0438\u044e, \u043b\u0435\u0433\u043a\u043e\u0439 \u0430\u0442\u043b\u0435\u0442\u0438\u043a\u0435 \u0438\u0026nbsp;\u0430\u0432\u0442\u043e\u0441\u043f\u043e\u0440\u0442\u0443\u0026nbsp;\u2014 \u043d\u0430\u0026nbsp;\u041c\u0430\u0442\u0447 \u0410\u0440\u0435\u043d\u0430 \u0441\u043e\u0431\u0440\u0430\u043d\u044b \u0432\u0438\u0434\u044b, \u0433\u0434\u0435 \u0443\u0441\u043f\u0435\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442\u0026nbsp;\u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0430\u0441\u0442\u0435\u0440\u0441\u0442\u0432\u0430!\u003Cp\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u0421\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e \u0431\u0438\u0430\u0442\u043b\u043e\u043d\u0443 \u0438 \u043b\u044b\u0436\u043d\u044b\u043c \u0433\u043e\u043d\u043a\u0430\u043c, \u0444\u0438\u0433\u0443\u0440\u043d\u043e\u043c\u0443 \u043a\u0430\u0442\u0430\u043d\u0438\u044e \u0438 \u043f\u043b\u0430\u0432\u0430\u043d\u0438\u044e, \u043b\u0435\u0433\u043a\u043e\u0439 \u0430\u0442\u043b\u0435\u0442\u0438\u043a\u0435 \u0438 \u0430\u0432\u0442\u043e\u0441\u043f\u043e\u0440\u0442\u0443 \u2014 \u043d\u0430 \u041c\u0430\u0442\u0447 \u0410\u0440\u0435\u043d\u0430 \u0441\u043e\u0431\u0440\u0430\u043d\u044b \u0432\u0438\u0434\u044b, \u0433\u0434\u0435 \u0443\u0441\u043f\u0435\u0445 \u0437\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043c\u0430\u0441\u0442\u0435\u0440\u0441\u0442\u0432\u0430!",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_a26972ccc9ac0100160dc6dbddf8e692\/17_9a32d7c6e26c38cbf12eac3bdb76849c\/764c339814404f3aec301f92f46f3249\/4934710917?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "9a32d7c6e26c38cbf12eac3bdb76849c",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606429,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0441\u043f\u043e\u0440\u0442\u0438\u0432\u043d\u044b\u0445 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u043f\u043e \u0431\u0438\u0430\u0442\u043b\u043e\u043d\u0443, \u043b\u044b\u0436\u043d\u044b\u043c \u0433\u043e\u043d\u043a\u0430\u043c, \u043b\u0435\u0433\u043a\u043e\u0439 \u0430\u0442\u043b\u0435\u0442\u0438\u043a\u0435 \u0438 \u0434\u0440\u0443\u0433\u0438\u043c \u0432\u0438\u0434\u0430\u043c \u0441\u043f\u043e\u0440\u0442\u0430 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430\u00bb!",
|
||||
"seoKeywords": "\u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/f768\/e9e1\/f768e9e1e6f4580f69c916d5f5a16132.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/44a3\/6689\/44a366893d98e8cfc1c1491ea5bce6de.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/f2f6\/45b7\/f2f645b7cf0962908fdb5bb485bd0cad.svg",
|
||||
"tvChannel": {
|
||||
"id": 137,
|
||||
"name": "\u041c\u0430\u0442\u0447 \u0410\u0440\u0435\u043d\u0430",
|
||||
"serviceTvId": 2944,
|
||||
"priority": 80
|
||||
},
|
||||
"terms": [
|
||||
{ "id": 38, "title": "\u0424\u043e\u0440\u043c\u0443\u043b\u0430-1" },
|
||||
{ "id": 12218, "title": "\u0410\u0432\u0442\u043e\u0441\u043f\u043e\u0440\u0442" },
|
||||
{
|
||||
"id": 160328,
|
||||
"title": "\u0415\u0432\u0440\u043e\u043f\u0435\u0439\u0441\u043a\u0438\u0435 \u0438\u0433\u0440\u044b"
|
||||
},
|
||||
{ "id": 176009, "title": "\u041b\u0435\u0442\u043d\u0438\u0435 \u0432\u0438\u0434\u044b" },
|
||||
{ "id": 213531, "title": "\u041c\u0430\u0442\u0447! \u0410\u0440\u0435\u043d\u0430" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17829580,
|
||||
"title": "\u0421\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u043e\u0435 \u043f\u043b\u0430\u0432\u0430\u043d\u0438\u0435. \u041a\u0443\u0431\u043e\u043a \u043c\u0438\u0440\u0430. \u0414\u0443\u044d\u0442\u044b. \u0422\u0435\u0445\u043d\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0430. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u041a\u0438\u0442\u0430\u044f [6+]",
|
||||
"description": "",
|
||||
"start": 1782540900,
|
||||
"finish": 1782549300,
|
||||
"startTime": "2026-06-27T09:15:00+03:00",
|
||||
"finishTime": "2026-06-27T11:35:00+03:00",
|
||||
"ageRating": "6+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17829580-1781880962.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"weight": 10,
|
||||
"alias": "igra",
|
||||
"name": "\u041c\u0430\u0442\u0447! \u0418\u0433\u0440\u0430 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"description": "\u003Cp\u003E\u042d\u0442\u043e\u0442 \u043a\u0430\u043d\u0430\u043b \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u044b\u043c \u0432\u0438\u0434\u0430\u043c \u0441\u043f\u043e\u0440\u0442\u0430. \u041d\u0430\u0026nbsp;\u00ab\u0418\u0433\u0440\u0435\u00bb\u0026nbsp;\u2014 \u0442\u043e\u043f\u043e\u0432\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b \u043f\u043e\u0026nbsp;\u0431\u0430\u0441\u043a\u0435\u0442\u0431\u043e\u043b\u0443, \u0433\u0430\u043d\u0434\u0431\u043e\u043b\u0443, \u0432\u043e\u043b\u0435\u0439\u0431\u043e\u043b\u0443 \u0438\u0026nbsp;\u043d\u0435\u0026nbsp;\u0442\u043e\u043b\u044c\u043a\u043e. \u0410\u0026nbsp;\u043d\u0430\u0448\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432\u0026nbsp;\u2014 \u0432\u0441\u0435\u0433\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441\u0026nbsp;\u043f\u043e\u043b\u043d\u043e\u0439 \u043e\u0442\u0434\u0430\u0447\u0435\u0439!\u003C\/p\u003E\u003Cp\u003E\u003Cbr\u003E\u003C\/p\u003E",
|
||||
"shortDescription": "\u042d\u0442\u043e\u0442 \u043a\u0430\u043d\u0430\u043b \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u044b\u043c \u0432\u0438\u0434\u0430\u043c \u0441\u043f\u043e\u0440\u0442\u0430. \u041d\u0430 \u00ab\u0418\u0433\u0440\u0435\u00bb \u2014 \u0442\u043e\u043f\u043e\u0432\u044b\u0435 \u0442\u0443\u0440\u043d\u0438\u0440\u044b \u043f\u043e \u0431\u0430\u0441\u043a\u0435\u0442\u0431\u043e\u043b\u0443, \u0433\u0430\u043d\u0434\u0431\u043e\u043b\u0443, \u0432\u043e\u043b\u0435\u0439\u0431\u043e\u043b\u0443 \u0438 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e. \u0410 \u043d\u0430\u0448\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0442\u043e\u0440\u043e\u0432 \u2014 \u0432\u0441\u0435\u0433\u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u043f\u043e\u043b\u043d\u043e\u0439 \u043e\u0442\u0434\u0430\u0447\u0435\u0439!",
|
||||
"videoPlayerCode": "\/\/video.matchtv.ru\/iframe\/feed\/start\/na_5e5f7b66fb3f1deb56f706bef2366edf\/17_76666997491217f4514cc1c9316ab99d\/791e47a7b142fe09d5040cc98caf676e\/4934710916?sr=14\u0026type_id=\u0026width=100%25\u0026height=100%25\u0026iframe_width=100%25\u0026iframe_height=100%25\u0026lang=ru\u0026skin_name=matchtv",
|
||||
"platformVideoId": "76666997491217f4514cc1c9316ab99d",
|
||||
"package": "16",
|
||||
"isNeedAuthorization": false,
|
||||
"webcasterVideoId": 606421,
|
||||
"chatRoomId": null,
|
||||
"seoH1": "\u041c\u0430\u0442\u0447! \u0418\u0433\u0440\u0430 | \u041c! \u041c\u0430\u043a\u0441\u0438\u043c\u0443\u043c",
|
||||
"seoTitle": "\u041a\u0430\u043d\u0430\u043b \u041c\u0430\u0442\u0447! \u0418\u0433\u0440\u0430 - \u043f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u043e\u043d\u043b\u0430\u0439\u043d, \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439 \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435",
|
||||
"seoDescription": "\u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440 \u0444\u0435\u0434\u0435\u0440\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u0431\u0449\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0433\u043e \u0442\u0435\u043b\u0435\u043a\u0430\u043d\u0430\u043b\u0430 \u00ab\u041c\u0430\u0442\u0447! \u0418\u0433\u0440\u0430\u00bb - \u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0431\u0435\u0441\u043f\u043b\u0430\u0442\u043d\u043e \u043e\u043d\u043b\u0430\u0439\u043d \u0432 \u0445\u043e\u0440\u043e\u0448\u0435\u043c \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043d\u0430 \u0441\u0430\u0439\u0442\u0435 \u00ab\u041c\u0430\u0442\u0447 \u0422\u0412\u00bb. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u0438 \u0441\u043e\u0440\u0435\u0432\u043d\u043e\u0432\u0430\u043d\u0438\u0439, \u043d\u043e\u0432\u043e\u0441\u0442\u0438, \u0440\u0435\u043f\u043e\u0440\u0442\u0430\u0436\u0438, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0444\u0438\u043b\u044c\u043c\u044b, \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u043d\u0430 \u043a\u0430\u043d\u0430\u043b\u0435 \u00ab\u041c\u0430\u0442\u0447! \u0418\u0433\u0440\u0430\u00bb!",
|
||||
"seoKeywords": "\u041c\u0430\u0442\u0447! \u0418\u0433\u0440\u0430",
|
||||
"seoMeta": null,
|
||||
"seoText": null,
|
||||
"imageUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/e939\/2d16\/e9392d16bb9fbda00953e29d7a3af284.jpg",
|
||||
"thumbnailUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/6b45\/0490\/6b4504901702d992df910d8072e67eee.png",
|
||||
"iconUrl": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/195d\/f488\/195df488b284a339df19aaa6660a70cd.svg",
|
||||
"tvChannel": {
|
||||
"id": 138,
|
||||
"name": "\u041c\u0430\u0442\u0447 \u0418\u0433\u0440\u0430",
|
||||
"serviceTvId": 2945,
|
||||
"priority": 90
|
||||
},
|
||||
"terms": [
|
||||
{ "id": 45, "title": "\u0411\u0430\u0441\u043a\u0435\u0442\u0431\u043e\u043b" },
|
||||
{ "id": 169, "title": "\u0422\u0435\u043d\u043d\u0438\u0441" },
|
||||
{ "id": 178, "title": "\u0412\u043e\u043b\u0435\u0439\u0431\u043e\u043b" }
|
||||
],
|
||||
"currentTvTransmissions": [
|
||||
{
|
||||
"id": 17829708,
|
||||
"title": "\u0412\u043e\u043b\u0435\u0439\u0431\u043e\u043b. \u041a\u0443\u0431\u043e\u043a \u0420\u043e\u0441\u0441\u0438\u0438. \u0416\u0435\u043d\u0449\u0438\u043d\u044b. \u0424\u0438\u043d\u0430\u043b \u0448\u0435\u0441\u0442\u0438. 1\/2 \u0444\u0438\u043d\u0430\u043b\u0430. \u0022\u0414\u0438\u043d\u0430\u043c\u043e\u0022 (\u041c\u043e\u0441\u043a\u0432\u0430) - \u0022\u0423\u0440\u0430\u043b\u043e\u0447\u043a\u0430-\u041d\u0422\u041c\u041a\u0022 (\u0421\u0432\u0435\u0440\u0434\u043b\u043e\u0432\u0441\u043a\u0430\u044f \u043e\u0431\u043b\u0430\u0441\u0442\u044c). \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u041c\u043e\u0441\u043a\u0432\u044b [6+]",
|
||||
"description": "",
|
||||
"start": 1782544500,
|
||||
"finish": 1782554100,
|
||||
"startTime": "2026-06-27T10:15:00+03:00",
|
||||
"finishTime": "2026-06-27T12:55:00+03:00",
|
||||
"ageRating": "6+",
|
||||
"sourceImagePath": "https:\/\/s-cdn.sportbox.ru\/images\/tv_transmission\/17829708-1782195506.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
115
tests/data/matchtv/test.json
Normal file
115
tests/data/matchtv/test.json
Normal file
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"result": {
|
||||
"info": {
|
||||
"filters": [
|
||||
"\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"\u0445\u043e\u043a\u043a\u0435\u0439",
|
||||
"\u0435\u0434\u0438\u043d\u043e\u0431\u043e\u0440\u0441\u0442\u0432\u0430",
|
||||
"\u0431\u0430\u0441\u043a\u0435\u0442\u0431\u043e\u043b",
|
||||
"\u0431\u0438\u0430\u0442\u043b\u043e\u043d",
|
||||
"\u043b\u044b\u0436\u0438"
|
||||
],
|
||||
"availableDates": { "min": "2026-06-24T00:00:00+03:00", "max": "2026-07-05T00:00:00+03:00" }
|
||||
},
|
||||
"channels": [
|
||||
{
|
||||
"id": 10,
|
||||
"tvChannelId": 130,
|
||||
"alias": "matchtv",
|
||||
"name": "\u041c\u0430\u0442\u0447 \u0422\u0412",
|
||||
"url": "\/channel\/matchtv",
|
||||
"icon": "https:\/\/fb-cdn.matchtv.ru\/files\/default\/upload\/4fbd\/42cc\/4fbd42ccdde063ff630a7970dbce1034.png",
|
||||
"currentTransmissionProgress": 97,
|
||||
"schedule": [
|
||||
{
|
||||
"time": "06:00",
|
||||
"title": "\u0412\u0441\u0435 \u043d\u0430 \u041c\u0430\u0442\u0447! \u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{ "time": "09:00", "title": "\u041d\u043e\u0432\u043e\u0441\u0442\u0438", "genre": "", "current": false },
|
||||
{
|
||||
"time": "09:05",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. 1\/16 \u0444\u0438\u043d\u0430\u043b\u0430. \u041a\u043e\u0442-\u0434\u0027\u0418\u0432\u0443\u0430\u0440 - \u041d\u043e\u0440\u0432\u0435\u0433\u0438\u044f. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u0421\u0428\u0410 [6+]",
|
||||
"genre": "\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "11:20",
|
||||
"title": "\u0423\u043b\u0451\u0442\u043d\u044b\u0439 \u0444\u0443\u0442\u0431\u043e\u043b. \u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "12:10",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. \u041e\u0431\u0437\u043e\u0440 [6+]",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{ "time": "12:40", "title": "\u041d\u043e\u0432\u043e\u0441\u0442\u0438", "genre": "", "current": false },
|
||||
{
|
||||
"time": "12:45",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. 1\/16 \u0444\u0438\u043d\u0430\u043b\u0430. \u041c\u0435\u043a\u0441\u0438\u043a\u0430 - \u042d\u043a\u0432\u0430\u0434\u043e\u0440. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u041c\u0435\u043a\u0441\u0438\u043a\u0438 [6+]",
|
||||
"genre": "\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "15:00",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. 1\/16 \u0444\u0438\u043d\u0430\u043b\u0430. \u0424\u0440\u0430\u043d\u0446\u0438\u044f - \u0428\u0432\u0435\u0446\u0438\u044f. \u0422\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u0421\u0428\u0410 [6+]",
|
||||
"genre": "\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "17:15",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. \u041e\u0431\u0437\u043e\u0440 [6+]",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{ "time": "17:45", "title": "\u041d\u043e\u0432\u043e\u0441\u0442\u0438", "genre": "", "current": false },
|
||||
{
|
||||
"time": "17:50",
|
||||
"title": "\u0412\u0441\u0435 \u043d\u0430 \u041c\u0430\u0442\u0447! \u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "18:30",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. 1\/16 \u0444\u0438\u043d\u0430\u043b\u0430. \u0410\u043d\u0433\u043b\u0438\u044f - \u0414\u0420 \u041a\u043e\u043d\u0433\u043e. \u041f\u0440\u044f\u043c\u0430\u044f \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u0421\u0428\u0410",
|
||||
"genre": "\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"current": true
|
||||
},
|
||||
{
|
||||
"time": "21:05",
|
||||
"title": "\u0412\u0441\u0435 \u043d\u0430 \u041c\u0430\u0442\u0447! \u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "22:00",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. 1\/16 \u0444\u0438\u043d\u0430\u043b\u0430. \u0411\u0435\u043b\u044c\u0433\u0438\u044f - \u0421\u0435\u043d\u0435\u0433\u0430\u043b. \u041f\u0440\u044f\u043c\u0430\u044f \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u0421\u0428\u0410",
|
||||
"genre": "\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "01:05",
|
||||
"title": "\u0412\u0441\u0435 \u043d\u0430 \u041c\u0430\u0442\u0447! \u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"genre": "",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "02:00",
|
||||
"title": "\u0424\u0443\u0442\u0431\u043e\u043b. \u0427\u0435\u043c\u043f\u0438\u043e\u043d\u0430\u0442 \u043c\u0438\u0440\u0430-2026. 1\/16 \u0444\u0438\u043d\u0430\u043b\u0430. \u0421\u0428\u0410 - \u0411\u043e\u0441\u043d\u0438\u044f \u0438 \u0413\u0435\u0440\u0446\u0435\u0433\u043e\u0432\u0438\u043d\u0430. \u041f\u0440\u044f\u043c\u0430\u044f \u0442\u0440\u0430\u043d\u0441\u043b\u044f\u0446\u0438\u044f \u0438\u0437 \u0421\u0428\u0410",
|
||||
"genre": "\u0444\u0443\u0442\u0431\u043e\u043b",
|
||||
"current": false
|
||||
},
|
||||
{
|
||||
"time": "05:05",
|
||||
"title": "\u0412\u0441\u0435 \u043d\u0430 \u041c\u0430\u0442\u0447! \u041f\u0440\u044f\u043c\u043e\u0439 \u044d\u0444\u0438\u0440",
|
||||
"genre": "",
|
||||
"current": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,7 @@ OPENWEATHER_MOCK_SOURCE = MockSource(
|
||||
Path(__file__).parent,
|
||||
{
|
||||
"forecast": "forecast.json",
|
||||
"direct": "direct.json",
|
||||
"reverse": "reverse.json",
|
||||
},
|
||||
)
|
||||
|
||||
254
tests/data/openweather/direct.json
Normal file
254
tests/data/openweather/direct.json
Normal file
@@ -0,0 +1,254 @@
|
||||
[
|
||||
{
|
||||
"name": "London",
|
||||
"local_names": {
|
||||
"fo": "London",
|
||||
"nn": "London",
|
||||
"tg": "Лондон",
|
||||
"uk": "Лондон",
|
||||
"lt": "Londonas",
|
||||
"zu": "ILondon",
|
||||
"eo": "Londono",
|
||||
"os": "Лондон",
|
||||
"pa": "ਲੰਡਨ",
|
||||
"jv": "London",
|
||||
"hu": "London",
|
||||
"gd": "Lunnainn",
|
||||
"hy": "Լոնդոն",
|
||||
"to": "Lonitoni",
|
||||
"sr": "Лондон",
|
||||
"sv": "London",
|
||||
"ku": "London",
|
||||
"te": "లండన్",
|
||||
"tw": "London",
|
||||
"co": "Londra",
|
||||
"eu": "Londres",
|
||||
"et": "London",
|
||||
"ca": "Londres",
|
||||
"nl": "Londen",
|
||||
"kl": "London",
|
||||
"fi": "Lontoo",
|
||||
"az": "London",
|
||||
"mr": "लंडन",
|
||||
"km": "ឡុងដ៍",
|
||||
"af": "Londen",
|
||||
"hi": "लंदन",
|
||||
"gl": "Londres",
|
||||
"ka": "ლონდონი",
|
||||
"ff": "London",
|
||||
"de": "London",
|
||||
"sl": "London",
|
||||
"th": "ลอนดอน",
|
||||
"bn": "লন্ডন",
|
||||
"pl": "Londyn",
|
||||
"an": "Londres",
|
||||
"bg": "Лондон",
|
||||
"ht": "Lonn",
|
||||
"so": "London",
|
||||
"mn": "Лондон",
|
||||
"id": "London",
|
||||
"ko": "런던",
|
||||
"yo": "Lọndọnu",
|
||||
"sa": "लन्डन्",
|
||||
"nv": "Tooh Dineʼé Bikin Haalʼá",
|
||||
"br": "Londrez",
|
||||
"rm": "Londra",
|
||||
"ascii": "London",
|
||||
"en": "London",
|
||||
"ba": "Лондон",
|
||||
"ga": "Londain",
|
||||
"tr": "Londra",
|
||||
"om": "Landan",
|
||||
"bh": "लंदन",
|
||||
"gu": "લંડન",
|
||||
"ee": "London",
|
||||
"se": "London",
|
||||
"vi": "Luân Đôn",
|
||||
"sk": "Londýn",
|
||||
"lb": "London",
|
||||
"cs": "Londýn",
|
||||
"io": "London",
|
||||
"ab": "Лондон",
|
||||
"ug": "لوندۇن",
|
||||
"ha": "Landan",
|
||||
"wo": "Londar",
|
||||
"bi": "London",
|
||||
"st": "London",
|
||||
"fj": "Lodoni",
|
||||
"tt": "Лондон",
|
||||
"mt": "Londra",
|
||||
"vo": "London",
|
||||
"bs": "London",
|
||||
"or": "ଲଣ୍ଡନ",
|
||||
"is": "London",
|
||||
"tk": "London",
|
||||
"he": "לונדון",
|
||||
"ia": "London",
|
||||
"kn": "ಲಂಡನ್",
|
||||
"ie": "London",
|
||||
"es": "Londres",
|
||||
"my": "လန်ဒန်မြို့",
|
||||
"sn": "London",
|
||||
"zh": "伦敦",
|
||||
"wa": "Londe",
|
||||
"fy": "Londen",
|
||||
"sq": "Londra",
|
||||
"be": "Лондан",
|
||||
"oc": "Londres",
|
||||
"hr": "London",
|
||||
"da": "London",
|
||||
"bm": "London",
|
||||
"ru": "Лондон",
|
||||
"ne": "लन्डन",
|
||||
"pt": "Londres",
|
||||
"lo": "ລອນດອນ",
|
||||
"ro": "Londra",
|
||||
"cu": "Лондонъ",
|
||||
"ce": "Лондон",
|
||||
"ny": "London",
|
||||
"am": "ለንደን",
|
||||
"gv": "Lunnin",
|
||||
"no": "London",
|
||||
"it": "Londra",
|
||||
"cv": "Лондон",
|
||||
"ta": "இலண்டன்",
|
||||
"ar": "لندن",
|
||||
"av": "Лондон",
|
||||
"sm": "Lonetona",
|
||||
"ja": "ロンドン",
|
||||
"ky": "Лондон",
|
||||
"si": "ලන්ඩන්",
|
||||
"tl": "Londres",
|
||||
"na": "London",
|
||||
"sw": "London",
|
||||
"mi": "Rānana",
|
||||
"lv": "Londona",
|
||||
"gn": "Lóndyre",
|
||||
"su": "London",
|
||||
"mg": "Lôndôna",
|
||||
"ml": "ലണ്ടൻ",
|
||||
"fr": "Londres",
|
||||
"ur": "علاقہ لندن",
|
||||
"kw": "Loundres",
|
||||
"yi": "לאנדאן",
|
||||
"ig": "London",
|
||||
"sc": "Londra",
|
||||
"ln": "Lóndɛlɛ",
|
||||
"kk": "Лондон",
|
||||
"el": "Λονδίνο",
|
||||
"sd": "لنڊن",
|
||||
"li": "Londe",
|
||||
"cy": "Llundain",
|
||||
"ms": "London",
|
||||
"fa": "لندن",
|
||||
"sh": "London",
|
||||
"feature_name": "London",
|
||||
"uz": "London",
|
||||
"bo": "ལོན་ཊོན།",
|
||||
"mk": "Лондон",
|
||||
"qu": "London",
|
||||
"ay": "London",
|
||||
"kv": "Лондон",
|
||||
"ps": "لندن"
|
||||
},
|
||||
"lat": 51.5073219,
|
||||
"lon": -0.1276474,
|
||||
"country": "GB",
|
||||
"state": "England"
|
||||
},
|
||||
{
|
||||
"name": "City of London",
|
||||
"local_names": {
|
||||
"lt": "Londono Sitis",
|
||||
"zh": "倫敦市",
|
||||
"hi": "सिटी ऑफ़ लंदन",
|
||||
"ko": "시티 오브 런던",
|
||||
"es": "City de Londres",
|
||||
"ur": "لندن شہر",
|
||||
"uk": "Лондонське Сіті",
|
||||
"he": "הסיטי של לונדון",
|
||||
"en": "City of London",
|
||||
"pt": "Cidade de Londres",
|
||||
"fr": "Cité de Londres",
|
||||
"ru": "Сити"
|
||||
},
|
||||
"lat": 51.5156177,
|
||||
"lon": -0.0919983,
|
||||
"country": "GB",
|
||||
"state": "England"
|
||||
},
|
||||
{
|
||||
"name": "London",
|
||||
"local_names": {
|
||||
"ga": "Londain",
|
||||
"ka": "ლონდონი",
|
||||
"yi": "לאנדאן",
|
||||
"cr": "ᓬᐊᐣᑕᐣ",
|
||||
"th": "ลอนดอน",
|
||||
"he": "לונדון",
|
||||
"el": "Λόντον",
|
||||
"lt": "Londonas",
|
||||
"ja": "ロンドン",
|
||||
"fa": "لندن",
|
||||
"bn": "লন্ডন",
|
||||
"iu": "ᓚᓐᑕᓐ",
|
||||
"ru": "Лондон",
|
||||
"oj": "Baketigweyaang",
|
||||
"ug": "لوندۇن",
|
||||
"en": "London",
|
||||
"be": "Лондан",
|
||||
"ar": "لندن",
|
||||
"fr": "London",
|
||||
"lv": "Landona",
|
||||
"ko": "런던",
|
||||
"hy": "Լոնտոն"
|
||||
},
|
||||
"lat": 42.9832406,
|
||||
"lon": -81.243372,
|
||||
"country": "CA",
|
||||
"state": "Ontario"
|
||||
},
|
||||
{
|
||||
"name": "Chelsea",
|
||||
"local_names": {
|
||||
"id": "Chelsea, London",
|
||||
"ru": "Челси",
|
||||
"ga": "Chelsea",
|
||||
"ur": "چیلسی، لندن",
|
||||
"et": "Chelsea",
|
||||
"pl": "Chelsea",
|
||||
"da": "Chelsea",
|
||||
"ar": "تشيلسي",
|
||||
"fa": "چلسی",
|
||||
"ko": "첼시",
|
||||
"sv": "Chelsea, London",
|
||||
"es": "Chelsea",
|
||||
"eu": "Chelsea",
|
||||
"nl": "Chelsea",
|
||||
"sk": "Chelsea",
|
||||
"no": "Chelsea",
|
||||
"af": "Chelsea, Londen",
|
||||
"uk": "Челсі",
|
||||
"he": "צ'לסי",
|
||||
"it": "Chelsea",
|
||||
"hu": "Chelsea",
|
||||
"ja": "チェルシー",
|
||||
"zh": "車路士",
|
||||
"hi": "चेल्सी, लंदन",
|
||||
"pt": "Chelsea",
|
||||
"az": "Çelsi",
|
||||
"el": "Τσέλσι",
|
||||
"vi": "Chelsea, Luân Đôn",
|
||||
"en": "Chelsea",
|
||||
"de": "Chelsea",
|
||||
"tr": "Chelsea, Londra",
|
||||
"fr": "Chelsea",
|
||||
"sh": "Chelsea, London"
|
||||
},
|
||||
"lat": 51.4875167,
|
||||
"lon": -0.1687007,
|
||||
"country": "GB",
|
||||
"state": "England"
|
||||
},
|
||||
{ "name": "London", "lat": 37.1289771, "lon": -84.0832646, "country": "US", "state": "Kentucky" }
|
||||
]
|
||||
18
tests/data/openweather/reverse.json
Normal file
18
tests/data/openweather/reverse.json
Normal file
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "Oryol",
|
||||
"local_names": {
|
||||
"be": "Арол",
|
||||
"ca": "Oriol",
|
||||
"cs": "Orjol",
|
||||
"de": "Orjol",
|
||||
"en": "Oryol",
|
||||
"ru": "Орёл",
|
||||
"sr": "Орел"
|
||||
},
|
||||
"lat": 52.968017149999994,
|
||||
"lon": 36.09949941816104,
|
||||
"country": "RU",
|
||||
"state": "Oryol Oblast"
|
||||
}
|
||||
]
|
||||
@@ -6,5 +6,6 @@ YANDEXTV_MOCK_SOURCE = MockSource(
|
||||
Path(__file__).parent,
|
||||
{
|
||||
"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": "Все результаты"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
@@ -3,7 +3,6 @@ import datetime
|
||||
import pytest
|
||||
|
||||
from gallery.painting.matchtv.api import MatchTvApi
|
||||
from gallery.sketch.schedule.model import ChannelId
|
||||
from tests.data.matchtv import MATCHTV_MOCK_SOURCE
|
||||
|
||||
|
||||
@@ -14,9 +13,12 @@ def matchtv_api_fixture() -> MatchTvApi:
|
||||
return api
|
||||
|
||||
|
||||
async def test_search(matchtv_api: MatchTvApi):
|
||||
result = await matchtv_api.find_channels("матч")
|
||||
assert len(result) == 6
|
||||
|
||||
|
||||
async def test_channel(matchtv_api: MatchTvApi):
|
||||
result = await matchtv_api.get_channel_schedule(
|
||||
ChannelId.TEST, datetime.date.today()
|
||||
)
|
||||
result = await matchtv_api.get_schedule("test", datetime.date.today())
|
||||
assert result is not None
|
||||
assert len(result.values) > 0
|
||||
|
||||
@@ -19,6 +19,11 @@ def openweather_api_fixture() -> OpenWeatherApi:
|
||||
return api
|
||||
|
||||
|
||||
async def test_search(openweather_api: OpenWeatherApi):
|
||||
result = await openweather_api.find_locations("test")
|
||||
assert len(result) == 5
|
||||
|
||||
|
||||
async def test_day(openweather_api: OpenWeatherApi):
|
||||
result = await openweather_api.get_day("52.968498:36.0695", datetime.date(2024, 8, 23))
|
||||
assert len(result.values) == 8
|
||||
|
||||
@@ -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,15 @@ 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("матч")
|
||||
assert len(result) == 4
|
||||
|
||||
|
||||
async def test_channel(yandextv_api: YandexTvApi):
|
||||
result = await yandextv_api.get_channel_schedule(
|
||||
ChannelId.TEST, datetime.date.today()
|
||||
)
|
||||
result = await yandextv_api.get_schedule("test", datetime.date.today())
|
||||
assert result is not None
|
||||
assert len(result.values) > 0
|
||||
|
||||
Reference in New Issue
Block a user