diff --git a/gallery/easel/__init__.py b/gallery/easel/__init__.py
index 79bc2e3..6782fcf 100644
--- a/gallery/easel/__init__.py
+++ b/gallery/easel/__init__.py
@@ -8,6 +8,7 @@ from gallery.sketch.weather.api import WeatherApi
from .route import doc
from .route.api import schedule as schedule_api_route
from .route.api import weather as weather_api_route
+from .route.view import common as common_view_route
from .route.view import schedule as schedule_view_route
from .route.view import weather as weather_view_route
@@ -26,6 +27,7 @@ def build_app(
doc.mount(app)
weather_api_route.mount(app)
schedule_api_route.mount(app)
+ common_view_route.mount(app)
weather_view_route.mount(app)
schedule_view_route.mount(app)
return app
diff --git a/gallery/easel/route/view/common/__init__.py b/gallery/easel/route/view/common/__init__.py
new file mode 100644
index 0000000..2e78bc5
--- /dev/null
+++ b/gallery/easel/route/view/common/__init__.py
@@ -0,0 +1,9 @@
+from pathlib import Path
+
+from fastapi import FastAPI
+from fastapi.staticfiles import StaticFiles
+
+
+def mount(app: FastAPI):
+ base_dir = Path(__file__).parent
+ app.mount("/static/common", StaticFiles(directory=base_dir / "static"))
diff --git a/gallery/easel/route/view/schedule/static/favicon.ico b/gallery/easel/route/view/common/static/favicon.ico
similarity index 100%
rename from gallery/easel/route/view/schedule/static/favicon.ico
rename to gallery/easel/route/view/common/static/favicon.ico
diff --git a/gallery/easel/route/view/common/static/style.css b/gallery/easel/route/view/common/static/style.css
new file mode 100644
index 0000000..ce3160a
--- /dev/null
+++ b/gallery/easel/route/view/common/static/style.css
@@ -0,0 +1,76 @@
+/*
+base
+*/
+body {
+ font-size: 1.5rem;
+}
+
+h3 {
+ margin: 0.5rem 0;
+}
+
+/*
+table
+*/
+table {
+ table-layout: fixed;
+ border-collapse: collapse;
+}
+
+table,
+th,
+td {
+ text-align: center;
+}
+
+td {
+ padding: 0.1rem 0.4rem;
+}
+
+/*
+a.button
+*/
+a.button {
+ text-decoration: none;
+ color: inherit;
+}
+
+.button.disabled {
+ pointer-events: none;
+ cursor: default;
+ color: gray;
+ filter: grayscale(100%);
+}
+
+/*
+app
+*/
+.app-container {
+ display: flex;
+ flex-direction: column;
+ flex-wrap: nowrap;
+ align-items: center;
+}
+
+ul.app-list {
+ list-style: none;
+}
+
+ul.app-list > li {
+ border: 1px solid lightgrey;
+}
+
+ul.app-list > li > a {
+ display: block;
+ padding: 0.5rem 2rem;
+ text-decoration: none;
+ color: inherit;
+}
+
+ul.app-list > li:hover {
+ border-color: blue;
+}
+
+ul.app-list > li:hover > a {
+ color: blue;
+}
diff --git a/gallery/easel/route/view/weather/util.py b/gallery/easel/route/view/common/util.py
similarity index 100%
rename from gallery/easel/route/view/weather/util.py
rename to gallery/easel/route/view/common/util.py
diff --git a/gallery/easel/route/view/schedule/__init__.py b/gallery/easel/route/view/schedule/__init__.py
index 9d23d11..16e8c38 100644
--- a/gallery/easel/route/view/schedule/__init__.py
+++ b/gallery/easel/route/view/schedule/__init__.py
@@ -6,16 +6,18 @@ from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
-from gallery.easel.route.view.weather.util import TagType, TagUtil
from gallery.sketch.schedule.api import ScheduleApi
+from gallery.version import __version__
+
+from ..common.util import TagType, TagUtil
+from .filters import timedelta_format
def mount(app: FastAPI):
base_dir = Path(__file__).parent
- app.mount(
- "/schedule/static", StaticFiles(directory=base_dir / "static"), name="static"
- )
+ app.mount("/static/schedule", StaticFiles(directory=base_dir / "static"))
templates = Jinja2Templates(directory=base_dir / "templates")
+ templates.env.filters["timedelta_format"] = timedelta_format
@app.get("/schedule", response_class=HTMLResponse)
async def get_schedule_list(request: Request):
@@ -25,6 +27,7 @@ def mount(app: FastAPI):
request=request,
name="index.html",
context={
+ "version": __version__,
"channels": channels,
},
)
@@ -45,6 +48,7 @@ def mount(app: FastAPI):
request=request,
name="schedule.html",
context={
+ "version": __version__,
"tag_util": TagUtil,
"datetime": datetime,
"response": response,
diff --git a/gallery/easel/route/view/schedule/filters.py b/gallery/easel/route/view/schedule/filters.py
new file mode 100644
index 0000000..5a13680
--- /dev/null
+++ b/gallery/easel/route/view/schedule/filters.py
@@ -0,0 +1,5 @@
+import datetime
+
+
+def timedelta_format(value: datetime.timedelta) -> str:
+ return ":".join(str(value).split(":")[:2])
diff --git a/gallery/easel/route/view/schedule/static/index.js b/gallery/easel/route/view/schedule/static/index.js
deleted file mode 100644
index e69de29..0000000
diff --git a/gallery/easel/route/view/schedule/static/style.css b/gallery/easel/route/view/schedule/static/style.css
index e435974..7dff605 100644
--- a/gallery/easel/route/view/schedule/static/style.css
+++ b/gallery/easel/route/view/schedule/static/style.css
@@ -1,21 +1,7 @@
-body {
- font-size: 1.5rem;
-}
-
-.app-container {
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- align-items: center;
-}
-
-table,
-th,
td {
- /* border: 1px solid rgba(0, 0, 0, 0.2); */
text-align: left;
}
-td {
- padding: 0.1rem 0.4rem;
+tr.live {
+ font-weight: bold;
}
diff --git a/gallery/easel/route/view/schedule/templates/index.html b/gallery/easel/route/view/schedule/templates/index.html
index e6acc56..b9c64fa 100644
--- a/gallery/easel/route/view/schedule/templates/index.html
+++ b/gallery/easel/route/view/schedule/templates/index.html
@@ -9,14 +9,16 @@
content="ie=edge">
ТВ
+ href="/static/common/style.css?v={{version}}">
+
-
+
{% for channel in channels %}
- {{channel}}
{% endfor %}
diff --git a/gallery/easel/route/view/schedule/templates/schedule.html b/gallery/easel/route/view/schedule/templates/schedule.html
index 491ddfb..11d53c1 100644
--- a/gallery/easel/route/view/schedule/templates/schedule.html
+++ b/gallery/easel/route/view/schedule/templates/schedule.html
@@ -9,28 +9,38 @@
content="ie=edge">
Программа | {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}}
+ href="/static/common/style.css?v={{version}}">
+
- {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}}
+ ⬅️
+ ⬆️
+ {{response.channel.name}} | {{response.date.strftime('%a, %d %B %Y')}}
+ ➡️
- | TIME |
- NAME |
+ |
+ |
+ |
{% for value in response.values %}
-
+
| {{value.start.strftime('%H:%M')}} |
+ {{(value.end - value.start) | timedelta_format}} |
{{value.label}} |
{% endfor %}
diff --git a/gallery/easel/route/view/weather/__init__.py b/gallery/easel/route/view/weather/__init__.py
index 1b7252c..a5e7788 100644
--- a/gallery/easel/route/view/weather/__init__.py
+++ b/gallery/easel/route/view/weather/__init__.py
@@ -9,16 +9,15 @@ from fastapi.templating import Jinja2Templates
from gallery.sketch.weather.api import WeatherApi
from gallery.sketch.weather.mock import WEATHER_MOCK_DATA
from gallery.sketch.weather.model import WeatherResponse
+from gallery.version import __version__
+from ..common.util import TagType, TagUtil
from .filters import cloudness_icon, wind_direction_icon
-from .util import TagType, TagUtil
def mount(app: FastAPI):
base_dir = Path(__file__).parent
- app.mount(
- "/weather/static", StaticFiles(directory=base_dir / "static"), name="static"
- )
+ app.mount("/static/weather", StaticFiles(directory=base_dir / "static"))
templates = Jinja2Templates(directory=base_dir / "templates")
templates.env.filters["wind_direction_icon"] = wind_direction_icon
templates.env.filters["cloudness_icon"] = cloudness_icon
@@ -28,6 +27,7 @@ def mount(app: FastAPI):
request=request,
name="weather.html",
context={
+ "version": __version__,
"tag_util": TagUtil,
"datetime": datetime,
"response": response,
@@ -42,6 +42,7 @@ def mount(app: FastAPI):
request=request,
name="index.html",
context={
+ "version": __version__,
"locations": locations,
},
)
diff --git a/gallery/easel/route/view/weather/static/favicon.ico b/gallery/easel/route/view/weather/static/favicon.ico
deleted file mode 100644
index a283ecb..0000000
Binary files a/gallery/easel/route/view/weather/static/favicon.ico and /dev/null differ
diff --git a/gallery/easel/route/view/weather/static/style.css b/gallery/easel/route/view/weather/static/style.css
index 0548542..af87dda 100644
--- a/gallery/easel/route/view/weather/static/style.css
+++ b/gallery/easel/route/view/weather/static/style.css
@@ -1,47 +1,3 @@
-body {
- font-size: 1.5rem;
-}
-
-.app-container {
- display: flex;
- flex-direction: column;
- flex-wrap: nowrap;
- align-items: center;
-}
-
-h3 {
- margin: 0.5rem 0;
-}
-
-a.button {
- text-decoration: none;
- color: inherit;
-}
-
-.button.disabled {
- pointer-events: none;
- cursor: default;
- color: gray;
- filter: grayscale(100%);
-}
-
-table {
- /* width: 100%; */
- table-layout: fixed;
- border-collapse: collapse;
-}
-
-table,
-th,
-td {
- /* border: 1px solid rgba(0, 0, 0, 0.2); */
- text-align: center;
-}
-
-td {
- padding: 0.1rem 0.4rem;
-}
-
.header {
font-size: 1rem;
text-align: left;
diff --git a/gallery/easel/route/view/weather/templates/index.html b/gallery/easel/route/view/weather/templates/index.html
index 87b6138..7530eb6 100644
--- a/gallery/easel/route/view/weather/templates/index.html
+++ b/gallery/easel/route/view/weather/templates/index.html
@@ -9,14 +9,16 @@
content="ie=edge">
Погода
+ href="/static/common/style.css?v={{version}}">
+
-
+
{% for location in locations %}
- {{location}}
{% endfor %}
diff --git a/gallery/easel/route/view/weather/templates/weather.html b/gallery/easel/route/view/weather/templates/weather.html
index d523b72..97b8e80 100644
--- a/gallery/easel/route/view/weather/templates/weather.html
+++ b/gallery/easel/route/view/weather/templates/weather.html
@@ -9,9 +9,11 @@
content="ie=edge">
Погода | {{response.location}} | {{response.date.strftime('%a, %d %B %Y')}}
+ href="/static/common/style.css?v={{version}}">
+
diff --git a/gallery/painting/matchtv/api.py b/gallery/painting/matchtv/api.py
index 12e9fd4..96d5d48 100644
--- a/gallery/painting/matchtv/api.py
+++ b/gallery/painting/matchtv/api.py
@@ -2,6 +2,7 @@ import datetime
import logging
import aiohttp
+from aiocache import cached
from bs4 import BeautifulSoup
from gallery.sketch.schedule.api import ScheduleApi
@@ -24,6 +25,7 @@ CHANNEL_LIST = [
class MatchTvApi(ScheduleApi):
BASE_URL = "https://matchtv.ru"
+ CACHE_TTL = 30 * 60
USER_AGENT = (
"Mozilla/5.0 (X11; Linux x86_64) "
@@ -47,6 +49,7 @@ class MatchTvApi(ScheduleApi):
async def get_channels(self) -> list[str]:
return CHANNEL_LIST
+ @cached(ttl=CACHE_TTL)
async def get_channel_schedule(
self, channel_id: str, date: datetime.date
) -> Schedule:
@@ -55,18 +58,25 @@ class MatchTvApi(ScheduleApi):
soup = BeautifulSoup(data, features="html.parser")
values = []
channel_name = soup.select_one(".caption__heading").text.split("|")[0].strip()
- current_date = datetime.datetime.combine(
+ 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(".teleprogram-schedule .teleprogram-schedule__item"):
title = item.select_one(".teleprogram-item__title").text.strip()
time_str = item.select_one(".teleprogram-item__time").text.strip()
hours, minutes = map(int, time_str.split(":"))
- item_date = datetime.datetime.combine(
- date, datetime.time(hour=hours, minute=minutes)
- )
- values.append(ScheduleValue(start=current_date, end=item_date, label=title))
- current_date = item_date
+ 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(id=channel_id, name=channel_name), date=date, values=values
)
diff --git a/gallery/sketch/schedule/model.py b/gallery/sketch/schedule/model.py
index 1c554ae..6d3cf62 100644
--- a/gallery/sketch/schedule/model.py
+++ b/gallery/sketch/schedule/model.py
@@ -18,6 +18,7 @@ class ScheduleValue(Model):
end: datetime.datetime
label: str
category: str | None = None
+ live: bool = False
class Schedule(Model):
diff --git a/gallery/version.py b/gallery/version.py
new file mode 100644
index 0000000..3dc1f76
--- /dev/null
+++ b/gallery/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"