feat(shcedule): add predefined schedule sport group
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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 +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 () {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -14,9 +14,11 @@ export class ScheduleChannelManager {
|
||||
element.setAttribute("removable", "");
|
||||
container.appendChild(element);
|
||||
}
|
||||
const groupElement = new ScheduleChannelGroupElement();
|
||||
groupElement.setAttribute("channels", JSON.stringify(channels));
|
||||
container.append(groupElement);
|
||||
if (channels.length > 0) {
|
||||
const groupElement = new ScheduleChannelGroupElement();
|
||||
groupElement.setAttribute("channels", JSON.stringify(channels));
|
||||
container.append(groupElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user