feat(schedule): implement multiple schedule providers
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.sketch.api import API
|
||||
|
||||
|
||||
def api_resolver(api_type: type[API]):
|
||||
def get_api(request: AppRequest, provider: str | None = None) -> API:
|
||||
return request.app.state.api.get_api(api_type, provider)
|
||||
|
||||
return get_api
|
||||
@@ -0,0 +1,9 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from gallery.sketch.schedule.api import ScheduleApi
|
||||
|
||||
from .api import api_resolver
|
||||
|
||||
ScheduleApiDepends = Annotated[ScheduleApi, Depends(api_resolver(ScheduleApi))]
|
||||
@@ -2,12 +2,8 @@ from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.sketch.weather.api import WeatherApi
|
||||
|
||||
from .api import api_resolver
|
||||
|
||||
async def get_weather_api(request: AppRequest, provider: str | None = None) -> WeatherApi:
|
||||
return request.app.state.api.get_weather(provider)
|
||||
|
||||
|
||||
WeatherApiDepends = Annotated[WeatherApi, Depends(get_weather_api)]
|
||||
WeatherApiDepends = Annotated[WeatherApi, Depends(api_resolver(WeatherApi))]
|
||||
|
||||
@@ -3,13 +3,13 @@ import datetime
|
||||
from fastapi import APIRouter
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.sketch.schedule.model import ChannelId, Schedule
|
||||
from gallery.sketch.schedule.model import Channel, Schedule
|
||||
|
||||
router = APIRouter(prefix="/schedule")
|
||||
|
||||
|
||||
@router.get("/channels")
|
||||
async def get_api_schedule_channels(request: AppRequest) -> list[ChannelId]:
|
||||
async def get_api_schedule_channels(request: AppRequest) -> list[Channel]:
|
||||
schedule_api = request.app.state.api.schedule
|
||||
return await schedule_api.get_channels()
|
||||
|
||||
|
||||
@@ -1,26 +1,18 @@
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
|
||||
from gallery.easel.core import AppRequest
|
||||
from gallery.easel.depends.schedule import ScheduleApiDepends
|
||||
from gallery.sketch.schedule.api import ScheduleApi
|
||||
from gallery.sketch.schedule.catalog import BUNDLE
|
||||
|
||||
from ..common.utils.tag import TagType, TagUtil
|
||||
from ..common.utils.template import build_templates
|
||||
from .filters import timedelta_format
|
||||
|
||||
|
||||
async def get_schedule_api(request: AppRequest, provider: str | None = None) -> ScheduleApi:
|
||||
return request.app.state.api.get_schedule(provider)
|
||||
|
||||
|
||||
ScheduleApiDepends = Annotated[ScheduleApi, Depends(get_schedule_api)]
|
||||
|
||||
|
||||
def context_procesor(request: AppRequest) -> dict:
|
||||
return {
|
||||
"providers": request.app.state.api.get_api_providers(ScheduleApi.TYPE),
|
||||
@@ -32,51 +24,34 @@ templates = build_templates(
|
||||
{
|
||||
"timedelta_format": timedelta_format,
|
||||
},
|
||||
context_procesor,
|
||||
)
|
||||
|
||||
router = APIRouter(prefix="/schedule")
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse)
|
||||
async def get_schedule_list(request: AppRequest, schedule_api: ScheduleApiDepends):
|
||||
channels = await schedule_api.get_channels()
|
||||
channels_data = BUNDLE.select_items(channels)
|
||||
async def get_schedule_index(request: AppRequest, schedule_api: ScheduleApiDepends, query: str | None = None):
|
||||
channels = (await schedule_api.find_channels(query)) if query else []
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="index.html",
|
||||
context={
|
||||
"channels": channels_data,
|
||||
"channels": channels,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_schedule_tag(request: AppRequest, schedule_api: ScheduleApiDepends, tag: str, live: bool = False):
|
||||
tag_value = TagUtil.parse_tag(tag)
|
||||
results = await schedule_api.get_all_schedules(tag_value.date)
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="schedule.html",
|
||||
context={
|
||||
"tag_util": TagUtil,
|
||||
"datetime": datetime,
|
||||
"response": results[0],
|
||||
"responses": results,
|
||||
"live": live,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{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("/{channel}/tag/{tag}", response_class=HTMLResponse)
|
||||
@router.get("/{provider}/{channel}/tag/{tag}", response_class=HTMLResponse)
|
||||
async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: str):
|
||||
tag_value = TagUtil.parse_tag(tag)
|
||||
if tag_value.type == TagType.DAY:
|
||||
response = await schedule_api.get_channel_schedule(channel, tag_value.date)
|
||||
response = await schedule_api.get_schedule(channel, tag_value.date)
|
||||
else:
|
||||
raise ValueError(tag)
|
||||
return templates.TemplateResponse(
|
||||
|
||||
@@ -16,7 +16,7 @@ locale=request.state.language)}}
|
||||
<i class="bi bi-arrow-left-square"></i>
|
||||
</a>
|
||||
<a class="icon-link"
|
||||
href="../..">
|
||||
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>
|
||||
@@ -30,14 +30,15 @@ locale=request.state.language)}}
|
||||
<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="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="{{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,59 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}
|
||||
{{_("Live broadcasts") if live else _("TV program")}} | {{format_date(response.date, DATE_FORMAT,
|
||||
locale=request.state.language)}}
|
||||
{% endblock %}
|
||||
|
||||
{% block header %}
|
||||
<app-link href="/schedule"
|
||||
icon="tv">{{_("TV program")}}</app-link>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h4>
|
||||
<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>{{_("Live broadcasts") if live else _("TV program")}} | {{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>
|
||||
<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 %}
|
||||
@@ -1,10 +1,11 @@
|
||||
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,18 +15,48 @@ 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:
|
||||
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 = []
|
||||
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 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:
|
||||
current_day += datetime.timedelta(days=1)
|
||||
item_date += datetime.timedelta(days=1)
|
||||
live = "Прямая трансляция" in title
|
||||
value = ScheduleValue(start=item_date, end=end, label=title, live=live)
|
||||
values.append(value)
|
||||
if prev_value is not None:
|
||||
prev_value.end = item_date
|
||||
prev_value = value
|
||||
return Schedule(
|
||||
channel=channel,
|
||||
date=date,
|
||||
values=values,
|
||||
)
|
||||
|
||||
async def get_schedule_legacy(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||
endpoint = f"tvguide/{channel_id}?date={date:%Y%m%d}"
|
||||
data = await self.SOURCE.request(endpoint)
|
||||
soup = BeautifulSoup(data, features="html.parser")
|
||||
@@ -50,4 +81,8 @@ class MatchTvApi(ScheduleApi):
|
||||
if prev_value is not None:
|
||||
prev_value.end = item_date
|
||||
prev_value = value
|
||||
return Schedule(channel=Channel(id=channel_id, name=channel_name), date=date, values=values)
|
||||
return Schedule(
|
||||
channel=Channel(id=channel_id, name=channel_name, provider=self.provider),
|
||||
date=date,
|
||||
values=values,
|
||||
)
|
||||
|
||||
@@ -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,18 @@ class YandexTvApi(ScheduleApi):
|
||||
PROVIDER = "yandextv"
|
||||
SOURCE = ApiSource("https://tv.yandex.ru", headers=HEADERS)
|
||||
|
||||
async def get_channels(self) -> list[ChannelId]:
|
||||
return list(CHANNELS_MAP.keys())
|
||||
async def find_channels(self, query: str) -> list[Channel]:
|
||||
url = f"https://suggest-multi.yandex.ru/suggest-tv2?v=4&uil=ru&lr=10&count_channels=4&count_programs=4&sn=50&part={query}"
|
||||
_, values = json.loads(await self.SOURCE.request(url))
|
||||
result = []
|
||||
for _, name, content in values:
|
||||
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 +80,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,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
from .api import API, Api
|
||||
from .schedule.api import ScheduleApi
|
||||
from .weather.api import WeatherApi
|
||||
|
||||
|
||||
class ApiBundle:
|
||||
@@ -21,9 +19,3 @@ class ApiBundle:
|
||||
if provider is None or provider == value.provider:
|
||||
return value
|
||||
raise ValueError(api_type, provider)
|
||||
|
||||
def get_weather(self, provider: str | None = None) -> WeatherApi:
|
||||
return self.get_api(WeatherApi, provider)
|
||||
|
||||
def get_schedule(self, provider: str | None = None) -> ScheduleApi:
|
||||
return self.get_api(ScheduleApi, provider)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class CatalogBundle(Generic[T]):
|
||||
def __init__(self, items: list[T]) -> None:
|
||||
self._items_by_id = {item.id: item for item in items}
|
||||
|
||||
def get_item(self, item_id: str) -> T:
|
||||
return self._items_by_id[item_id]
|
||||
|
||||
def select_items(self, ids: list[str]) -> list[T]:
|
||||
return [self._items_by_id[id_] for id_ in ids]
|
||||
@@ -1,25 +1,14 @@
|
||||
import asyncio
|
||||
import datetime
|
||||
|
||||
from ..api import Api
|
||||
from .model import ChannelId, Schedule
|
||||
from .model import Channel, Schedule
|
||||
|
||||
|
||||
class ScheduleApi(Api):
|
||||
TYPE = "schedule"
|
||||
INTERVAL: float = 0.5
|
||||
|
||||
async def get_channels(self) -> list[ChannelId]:
|
||||
async def find_channels(self, query: str) -> list[Channel]:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_channel_schedule(self, channel_id: ChannelId, date: datetime.date) -> Schedule:
|
||||
async def get_schedule(self, channel_id: str, date: datetime.date) -> Schedule:
|
||||
raise NotImplementedError
|
||||
|
||||
async def get_all_schedules(self, date: datetime.date) -> list[Schedule]:
|
||||
channels = await self.get_channels()
|
||||
results = []
|
||||
for channel in channels:
|
||||
results.append(await self.get_channel_schedule(channel_id=channel, date=date))
|
||||
if self.INTERVAL > 0:
|
||||
await asyncio.sleep(self.INTERVAL)
|
||||
return results
|
||||
|
||||
@@ -6,7 +6,7 @@ from gallery.sketch.cached import CachedApi, CachePreset
|
||||
from gallery.util import TimeUnit
|
||||
|
||||
from .api import ScheduleApi
|
||||
from .model import ChannelId, Schedule
|
||||
from .model import Channel, Schedule
|
||||
|
||||
CACHE_PRESET = CachePreset(ttl=TimeUnit.HOUR * 6)
|
||||
|
||||
@@ -15,11 +15,11 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
||||
CACHE_KEY = ScheduleApi.TYPE
|
||||
|
||||
@cached(
|
||||
key_builder=lambda fun, self: f"api.{self.CACHE_KEY}.{self.provider}.channels",
|
||||
key_builder=lambda fun, self, query: f"api.{self.CACHE_KEY}.{self.provider}.find.{query}",
|
||||
**CACHE_PRESET._asdict(),
|
||||
)
|
||||
async def get_channels(self) -> list[ChannelId]:
|
||||
return await self._api.get_channels()
|
||||
async def find_channels(self, query: str) -> list[Channel]:
|
||||
return await self._api.find_channels(query)
|
||||
|
||||
@cached(
|
||||
key_builder=lambda fun, self, channel_id, date: (
|
||||
@@ -27,12 +27,5 @@ class CachedScheduleApi(ScheduleApi, CachedApi[ScheduleApi]):
|
||||
),
|
||||
**CACHE_PRESET._asdict(),
|
||||
)
|
||||
async def get_channel_schedule(self, channel_id: ChannelId, date: datetime.date) -> Schedule:
|
||||
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(
|
||||
|
||||
Reference in New Issue
Block a user