feat(shcedule): add predefined schedule sport group

This commit is contained in:
2026-09-07 23:39:34 +03:00
parent 5db61da32b
commit 1835935eaf
11 changed files with 159 additions and 107 deletions
+3 -1
View File
@@ -40,7 +40,9 @@ class TagUtil:
elif tag.startswith("days-"):
days = int(tag.split("-")[-1])
return Tag(TagType.DAYS, datetime.date.today(), days)
raise ValueError(tag)
else:
date = datetime.date.strptime(tag, "%Y-%m-%d")
return cls.create_tag(TagType.DAY, date)
@classmethod
def create_tag(
@@ -29,8 +29,21 @@ def base_context_processor(request: Request) -> dict:
}
def build_url(request: Request, query: dict | None = None) -> str:
def build_url(
request: Request, *, path: list[str] | None = None, up: int | None = None, query: dict | None = None
) -> str:
print("!", path)
parts = urlsplit(str(request.url))
if up:
path = [".."] * up
if path:
path_parts = parts.path.split("/")
for item in path:
if item == "..":
path_parts = path_parts[:-1]
else:
path_parts.append(str(item))
parts = parts._replace(path="/".join(path_parts))
if query:
query_dict = {**dict(parse_qsl(parts.query)), **query}
parts = parts._replace(query=urlencode(query_dict))
+18
View File
@@ -0,0 +1,18 @@
import datetime
from typing import Annotated
from fastapi import Depends
from ..common.utils.tag import Tag, TagType, TagUtil
def _resolve_tag(date: datetime.date | None = None, tag: str | None = None) -> Tag:
if tag:
result = TagUtil.parse_tag(tag)
else:
date = date or datetime.date.today()
result = TagUtil.create_tag(TagType.DAY, date)
return result
TagDepends = Annotated[Tag, Depends(_resolve_tag)]
+1 -91
View File
@@ -1,91 +1 @@
import datetime
from pathlib import Path
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.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(prefix="/schedule")
@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,
},
)
@router.get("/{provider}/{channel}", response_class=RedirectResponse)
async def get_channel_default(channel: str):
return RedirectResponse(f"{channel}/tag/today")
@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_schedule(channel, tag_value.date)
else:
raise ValueError(tag)
return templates.TemplateResponse(
request=request,
name="channel.html",
context={
"tag_util": TagUtil,
"datetime": datetime,
"response": response,
},
)
@router.get("/group", response_class=HTMLResponse)
async def get_group(request: AppRequest, ids: str, date: datetime.date | None = None, tag: str | None = None):
date = date or datetime.date.today()
if tag:
date = TagUtil.parse_tag(tag).date
channels = [item.split(":") for item in ids.split(" ")]
responses = [
await request.app.state.api.get_api(ScheduleApi, provider).get_schedule(channel_id, date)
for provider, channel_id in channels
]
return templates.TemplateResponse(
request=request,
name="group.html",
context={
"tag_util": TagUtil,
"datetime": datetime,
"responses": responses,
},
)
from .route import router
@@ -0,0 +1,12 @@
GROUP_MAP = {
"sport": [
("matchtv", "matchtv"),
("matchtv", "igra"),
("matchtv", "arena"),
("matchtv", "futbol-1"),
("matchtv", "futbol-2"),
("matchtv", "futbol-3"),
("matchtv", "strana"),
("yandextv", "1799"), # start
]
}
@@ -0,0 +1,90 @@
import datetime
from pathlib import Path
from urllib import parse
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.schedule import ScheduleApiDepends
from gallery.sketch.bundle import ApiBundle
from gallery.sketch.schedule.api import ScheduleApi
from gallery.sketch.schedule.model import Schedule
from ..common.utils.tag import TagType
from ..common.utils.template import build_templates
from ..depends.tag import TagDepends
from .bundle import GROUP_MAP
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(prefix="/schedule")
@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},
)
async def _get_group_schedule(api: ApiBundle, channels: list[tuple[str, str]], date: datetime.date) -> list[Schedule]:
return [
await api.get_api(ScheduleApi, provider).get_schedule(channel_id, date) for provider, channel_id in channels
]
@router.get("/group/{group_id}", response_class=RedirectResponse)
async def get_group_default(group_id: str):
return RedirectResponse(f"{parse.quote(group_id)}/today")
@router.get("/group/{group_id}/{tag}", response_class=HTMLResponse)
async def get_group(request: AppRequest, group_id: str, tag: TagDepends):
channels = GROUP_MAP.get(group_id, [tuple(item.split(":")) for item in group_id.split(",")])
responses = await _get_group_schedule(request.app.state.api, channels, tag.date)
return templates.TemplateResponse(
request=request,
name="group.html",
context={"responses": responses},
)
@router.get("/{provider}/{channel}", response_class=RedirectResponse)
async def get_channel_default(channel: str):
return RedirectResponse(f"{channel}/today")
@router.get("/{provider}/{channel}/{tag}", response_class=HTMLResponse)
async def get_channel_tag(request: AppRequest, schedule_api: ScheduleApiDepends, channel: str, tag: TagDepends):
if tag.type == TagType.DAY:
response = await schedule_api.get_schedule(channel, tag.date)
else:
raise ValueError(tag)
return templates.TemplateResponse(
request=request,
name="channel.html",
context={"response": response},
)
@@ -15,11 +15,11 @@ locale=request.state.language)}}
{% block content %}
<h4 class="mb-4">
<a class="icon-link {{'disabled' if date == datetime.date.today() else ''}}"
href="{{build_url(request, query={'date': date - datetime.timedelta(days=1)})}}">
href="{{build_url(request, path=['..', date - datetime.timedelta(days=1)])}}">
<i class="bi bi-arrow-left-square"></i>
</a>
<a class="icon-link"
href=".">
href="{{build_url(request, up=3)}}">
<i class="bi bi-arrow-up-square"></i>
</a>
<a class="icon-link {{'text-secondary' if not live else ''}}"
@@ -29,7 +29,7 @@ locale=request.state.language)}}
<span class="badge rounded-pill text-bg-secondary fs-6">{{provider}}</span>
<span>{{response.channel.name}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
<a class="icon-link"
href="{{build_url(request, query={'date': date + datetime.timedelta(days=1)})}}">
href="{{build_url(request, path=['..', date + datetime.timedelta(days=1)])}}">
<i class="bi bi-arrow-right-square"></i>
</a>
</h4>
@@ -14,11 +14,11 @@
{% block content %}
<h4 class="mb-4">
<a class="icon-link {{'disabled' if date == datetime.date.today() else ''}}"
href="{{build_url(request, query={'date': date - datetime.timedelta(days=1)})}}">
href="{{build_url(request, path=['..', date - datetime.timedelta(days=1)])}}">
<i class="bi bi-arrow-left-square"></i>
</a>
<a class="icon-link"
href=".">
href="{{build_url(request, up=3)}}">
<i class="bi bi-arrow-up-square"></i>
</a>
<a class="icon-link {{'text-secondary' if not live else ''}}"
@@ -27,7 +27,7 @@
</a>
<span>{{format_date(date, DATE_FORMAT, locale=request.state.language)}}</span>
<a class="icon-link"
href="{{build_url(request, query={'date': date + datetime.timedelta(days=1)})}}">
href="{{build_url(request, path=['..', date + datetime.timedelta(days=1)])}}">
<i class="bi bi-arrow-right-square"></i>
</a>
</h4>
@@ -44,7 +44,10 @@
<hr>
{% endif %}
<ul id="storedChannels"
class="list-group mb-5">
class="list-group mb-4">
</ul>
<ul class="list-group">
<schedule-channel-group group="sport"></schedule-channel-group>
</ul>
<script>
(function () {
+6 -4
View File
@@ -57,8 +57,9 @@ export class ScheduleChannelElement extends HTMLElement {
}
export class ScheduleChannelGroupElement extends HTMLElement {
static observedAttributes = ["channels"];
static observedAttributes = ["group", "channels"];
group: string | null = null;
channels: Channel[] | null = null;
constructor() {
@@ -67,12 +68,13 @@ export class ScheduleChannelGroupElement extends HTMLElement {
connectedCallback() {
this.channels = JSON.parse(this.getAttribute("channels") || "[]");
const ids = this.channels?.map((item) => `${item.provider}:${item.id}`).join("+");
this.group = this.getAttribute("group");
const groupId = this.channels?.map((item) => `${item.provider}:${item.id}`).join(",") || this.group || "n/a";
this.innerHTML = `
<a href="group?ids=${ids}"
<a href="group/${encodeURIComponent(groupId)}"
class="d-flex justify-content-between align-items-start text-decoration-none">
<span>
<span class="text-primary">Open All</span>
<span class="text-primary">${this.group || "Open All"}</span>
</span>
</a>`;
this.classList = "list-group-item list-group-item-action";
+2
View File
@@ -14,12 +14,14 @@ export class ScheduleChannelManager {
element.setAttribute("removable", "");
container.appendChild(element);
}
if (channels.length > 0) {
const groupElement = new ScheduleChannelGroupElement();
groupElement.setAttribute("channels", JSON.stringify(channels));
container.append(groupElement);
}
}
}
}
declare global {
interface Window {