feat(shcedule): add predefined schedule sport group
This commit is contained in:
@@ -40,7 +40,9 @@ class TagUtil:
|
|||||||
elif tag.startswith("days-"):
|
elif tag.startswith("days-"):
|
||||||
days = int(tag.split("-")[-1])
|
days = int(tag.split("-")[-1])
|
||||||
return Tag(TagType.DAYS, datetime.date.today(), days)
|
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
|
@classmethod
|
||||||
def create_tag(
|
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))
|
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:
|
if query:
|
||||||
query_dict = {**dict(parse_qsl(parts.query)), **query}
|
query_dict = {**dict(parse_qsl(parts.query)), **query}
|
||||||
parts = parts._replace(query=urlencode(query_dict))
|
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 .route import router
|
||||||
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,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -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 %}
|
{% block content %}
|
||||||
<h4 class="mb-4">
|
<h4 class="mb-4">
|
||||||
<a class="icon-link {{'disabled' if date == datetime.date.today() else ''}}"
|
<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>
|
<i class="bi bi-arrow-left-square"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="icon-link"
|
<a class="icon-link"
|
||||||
href=".">
|
href="{{build_url(request, up=3)}}">
|
||||||
<i class="bi bi-arrow-up-square"></i>
|
<i class="bi bi-arrow-up-square"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="icon-link {{'text-secondary' if not live else ''}}"
|
<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 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>
|
<span>{{response.channel.name}} | {{format_date(response.date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||||
<a class="icon-link"
|
<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>
|
<i class="bi bi-arrow-right-square"></i>
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h4 class="mb-4">
|
<h4 class="mb-4">
|
||||||
<a class="icon-link {{'disabled' if date == datetime.date.today() else ''}}"
|
<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>
|
<i class="bi bi-arrow-left-square"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="icon-link"
|
<a class="icon-link"
|
||||||
href=".">
|
href="{{build_url(request, up=3)}}">
|
||||||
<i class="bi bi-arrow-up-square"></i>
|
<i class="bi bi-arrow-up-square"></i>
|
||||||
</a>
|
</a>
|
||||||
<a class="icon-link {{'text-secondary' if not live else ''}}"
|
<a class="icon-link {{'text-secondary' if not live else ''}}"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<span>{{format_date(date, DATE_FORMAT, locale=request.state.language)}}</span>
|
<span>{{format_date(date, DATE_FORMAT, locale=request.state.language)}}</span>
|
||||||
<a class="icon-link"
|
<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>
|
<i class="bi bi-arrow-right-square"></i>
|
||||||
</a>
|
</a>
|
||||||
</h4>
|
</h4>
|
||||||
|
|||||||
@@ -44,7 +44,10 @@
|
|||||||
<hr>
|
<hr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<ul id="storedChannels"
|
<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>
|
</ul>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ export class ScheduleChannelElement extends HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ScheduleChannelGroupElement extends HTMLElement {
|
export class ScheduleChannelGroupElement extends HTMLElement {
|
||||||
static observedAttributes = ["channels"];
|
static observedAttributes = ["group", "channels"];
|
||||||
|
|
||||||
|
group: string | null = null;
|
||||||
channels: Channel[] | null = null;
|
channels: Channel[] | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -67,12 +68,13 @@ export class ScheduleChannelGroupElement extends HTMLElement {
|
|||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this.channels = JSON.parse(this.getAttribute("channels") || "[]");
|
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 = `
|
this.innerHTML = `
|
||||||
<a href="group?ids=${ids}"
|
<a href="group/${encodeURIComponent(groupId)}"
|
||||||
class="d-flex justify-content-between align-items-start text-decoration-none">
|
class="d-flex justify-content-between align-items-start text-decoration-none">
|
||||||
<span>
|
<span>
|
||||||
<span class="text-primary">Open All</span>
|
<span class="text-primary">${this.group || "Open All"}</span>
|
||||||
</span>
|
</span>
|
||||||
</a>`;
|
</a>`;
|
||||||
this.classList = "list-group-item list-group-item-action";
|
this.classList = "list-group-item list-group-item-action";
|
||||||
|
|||||||
@@ -14,12 +14,14 @@ export class ScheduleChannelManager {
|
|||||||
element.setAttribute("removable", "");
|
element.setAttribute("removable", "");
|
||||||
container.appendChild(element);
|
container.appendChild(element);
|
||||||
}
|
}
|
||||||
|
if (channels.length > 0) {
|
||||||
const groupElement = new ScheduleChannelGroupElement();
|
const groupElement = new ScheduleChannelGroupElement();
|
||||||
groupElement.setAttribute("channels", JSON.stringify(channels));
|
groupElement.setAttribute("channels", JSON.stringify(channels));
|
||||||
container.append(groupElement);
|
container.append(groupElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
|
|||||||
Reference in New Issue
Block a user