Update web app manifest; fix type errors
All checks were successful
Deploy to Server / deploy (push) Successful in 18s

This commit is contained in:
Joakim Hellsén 2026-03-19 19:39:27 +01:00
commit 79fb9b09c1
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
12 changed files with 135 additions and 72 deletions

View file

@ -30,6 +30,9 @@ def get_urls_from_sitemap(sitemap_url: str, list_of_urls: list[str]) -> list[str
if parser.has_sitemaps(): if parser.has_sitemaps():
sitemaps: SitemapIndex = parser.get_sitemaps() sitemaps: SitemapIndex = parser.get_sitemaps()
for sitemap in sitemaps: for sitemap in sitemaps:
if not sitemap.loc:
continue
list_of_urls.extend( list_of_urls.extend(
get_urls_from_sitemap( get_urls_from_sitemap(
sitemap_url=sitemap.loc, sitemap_url=sitemap.loc,
@ -39,7 +42,7 @@ def get_urls_from_sitemap(sitemap_url: str, list_of_urls: list[str]) -> list[str
elif parser.has_urls(): elif parser.has_urls():
urls: UrlSet = parser.get_urls() urls: UrlSet = parser.get_urls()
list_of_urls.extend(url.loc for url in urls) list_of_urls.extend(url.loc for url in urls if url.loc)
return list_of_urls return list_of_urls
@ -99,6 +102,7 @@ class Command(BaseCommand):
api_key_location=api_key_location, api_key_location=api_key_location,
) )
status_code: int = 0
urls: list[str] = get_urls_from_sitemap(sitemap_url=sitemap, list_of_urls=[]) urls: list[str] = get_urls_from_sitemap(sitemap_url=sitemap, list_of_urls=[])
chucked_urls: list[list[str]] = get_chucked_urls(urls=urls) chucked_urls: list[list[str]] = get_chucked_urls(urls=urls)
for chunk in chucked_urls: for chunk in chucked_urls:

View file

@ -121,7 +121,7 @@ def _build_seo_context( # noqa: PLR0913, PLR0917
def _render_urlset_xml( def _render_urlset_xml(
url_entries: list[dict[str, str | dict[str, str]]], url_entries: list[dict[str, str]],
) -> str: ) -> str:
"""Render a <urlset> sitemap XML string from URL entries. """Render a <urlset> sitemap XML string from URL entries.
@ -184,20 +184,24 @@ def sitemap_view(request: HttpRequest) -> HttpResponse:
# Compute last modified per-section so search engines can more intelligently crawl. # Compute last modified per-section so search engines can more intelligently crawl.
# Do not fabricate a lastmod date if the section has no data. # Do not fabricate a lastmod date if the section has no data.
twitch_channels_lastmod = Channel.objects.aggregate(max=Max("updated_at"))["max"] twitch_channels_lastmod: datetime.datetime | None = Channel.objects.aggregate(
twitch_drops_lastmod = max( max=Max("updated_at"),
[ )["max"]
twitch_drops_lastmod: datetime.datetime | None = max(
(
dt dt
for dt in [ for dt in [
DropCampaign.objects.aggregate(max=Max("updated_at"))["max"], DropCampaign.objects.aggregate(max=Max("updated_at"))["max"],
RewardCampaign.objects.aggregate(max=Max("updated_at"))["max"], RewardCampaign.objects.aggregate(max=Max("updated_at"))["max"],
] ]
if dt is not None if dt is not None
] ),
or [None], default=None,
) )
twitch_others_lastmod = max(
[ twitch_others_lastmod: datetime.datetime | None = max(
(
dt dt
for dt in [ for dt in [
Game.objects.aggregate(max=Max("updated_at"))["max"], Game.objects.aggregate(max=Max("updated_at"))["max"],
@ -205,10 +209,13 @@ def sitemap_view(request: HttpRequest) -> HttpResponse:
ChatBadgeSet.objects.aggregate(max=Max("updated_at"))["max"], ChatBadgeSet.objects.aggregate(max=Max("updated_at"))["max"],
] ]
if dt is not None if dt is not None
] ),
or [None], default=None,
) )
kick_lastmod = KickDropCampaign.objects.aggregate(max=Max("updated_at"))["max"]
kick_lastmod: datetime.datetime | None = KickDropCampaign.objects.aggregate(
max=Max("updated_at"),
)["max"]
sitemap_entries: list[dict[str, str]] = [ sitemap_entries: list[dict[str, str]] = [
{"loc": f"{base_url}/sitemap-static.xml"}, {"loc": f"{base_url}/sitemap-static.xml"},
@ -297,7 +304,7 @@ def sitemap_static_view(request: HttpRequest) -> HttpResponse:
"youtube:index", "youtube:index",
] ]
sitemap_urls: list[dict[str, str | dict[str, str]]] = [] sitemap_urls: list[dict[str, str]] = []
for route_name in static_route_names: for route_name in static_route_names:
url = reverse(route_name) url = reverse(route_name)
sitemap_urls.append({"url": f"{base_url}{url}"}) sitemap_urls.append({"url": f"{base_url}{url}"})
@ -317,13 +324,13 @@ def sitemap_twitch_channels_view(request: HttpRequest) -> HttpResponse:
HttpResponse: The rendered sitemap XML. HttpResponse: The rendered sitemap XML.
""" """
base_url: str = _build_base_url(request) base_url: str = _build_base_url(request)
sitemap_urls: list[dict[str, str | dict[str, str]]] = [] sitemap_urls: list[dict[str, str]] = []
channels: QuerySet[Channel] = Channel.objects.all() channels: QuerySet[Channel] = Channel.objects.all()
for channel in channels: for channel in channels:
resource_url: str = reverse("twitch:channel_detail", args=[channel.twitch_id]) resource_url: str = reverse("twitch:channel_detail", args=[channel.twitch_id])
full_url: str = f"{base_url}{resource_url}" full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str | dict[str, str]] = {"url": full_url} entry: dict[str, str] = {"url": full_url}
if channel.updated_at: if channel.updated_at:
entry["lastmod"] = channel.updated_at.isoformat() entry["lastmod"] = channel.updated_at.isoformat()
sitemap_urls.append(entry) sitemap_urls.append(entry)
@ -343,16 +350,16 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
HttpResponse: The rendered sitemap XML. HttpResponse: The rendered sitemap XML.
""" """
base_url: str = _build_base_url(request) base_url: str = _build_base_url(request)
sitemap_urls: list[dict[str, str | dict[str, str]]] = [] sitemap_urls: list[dict[str, str]] = []
campaigns: QuerySet[DropCampaign] = DropCampaign.objects.all() campaigns: QuerySet[DropCampaign] = DropCampaign.objects.all()
for campaign in campaigns: for campaign in campaigns:
resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id]) resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id])
full_url: str = f"{base_url}{resource_url}" full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str] = {"url": full_url} campaign_url_entry: dict[str, str] = {"url": full_url}
if campaign.updated_at: if campaign.updated_at:
entry["lastmod"] = campaign.updated_at.isoformat() campaign_url_entry["lastmod"] = campaign.updated_at.isoformat()
sitemap_urls.append(entry) sitemap_urls.append(campaign_url_entry)
reward_campaigns: QuerySet[RewardCampaign] = RewardCampaign.objects.all() reward_campaigns: QuerySet[RewardCampaign] = RewardCampaign.objects.all()
for reward_campaign in reward_campaigns: for reward_campaign in reward_campaigns:
@ -362,11 +369,13 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse:
) )
full_url: str = f"{base_url}{resource_url}" full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str | dict[str, str]] = {"url": full_url} reward_campaign_url_entry: dict[str, str] = {"url": full_url}
if reward_campaign.updated_at: if reward_campaign.updated_at:
entry["lastmod"] = reward_campaign.updated_at.isoformat() reward_campaign_url_entry["lastmod"] = (
reward_campaign.updated_at.isoformat()
)
sitemap_urls.append(entry) sitemap_urls.append(reward_campaign_url_entry)
xml_content: str = _render_urlset_xml(sitemap_urls) xml_content: str = _render_urlset_xml(sitemap_urls)
return HttpResponse(xml_content, content_type="application/xml") return HttpResponse(xml_content, content_type="application/xml")
@ -383,13 +392,13 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
HttpResponse: The rendered sitemap XML. HttpResponse: The rendered sitemap XML.
""" """
base_url: str = _build_base_url(request) base_url: str = _build_base_url(request)
sitemap_urls: list[dict[str, str | dict[str, str]]] = [] sitemap_urls: list[dict[str, str]] = []
games: QuerySet[Game] = Game.objects.all() games: QuerySet[Game] = Game.objects.all()
for game in games: for game in games:
resource_url: str = reverse("twitch:game_detail", args=[game.twitch_id]) resource_url: str = reverse("twitch:game_detail", args=[game.twitch_id])
full_url: str = f"{base_url}{resource_url}" full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str | dict[str, str]] = {"url": full_url} entry: dict[str, str] = {"url": full_url}
if game.updated_at: if game.updated_at:
entry["lastmod"] = game.updated_at.isoformat() entry["lastmod"] = game.updated_at.isoformat()
@ -400,7 +409,7 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse:
for org in orgs: for org in orgs:
resource_url: str = reverse("twitch:organization_detail", args=[org.twitch_id]) resource_url: str = reverse("twitch:organization_detail", args=[org.twitch_id])
full_url: str = f"{base_url}{resource_url}" full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str | dict[str, str]] = {"url": full_url} entry: dict[str, str] = {"url": full_url}
if org.updated_at: if org.updated_at:
entry["lastmod"] = org.updated_at.isoformat() entry["lastmod"] = org.updated_at.isoformat()
@ -431,13 +440,13 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse:
HttpResponse: The rendered sitemap XML. HttpResponse: The rendered sitemap XML.
""" """
base_url: str = _build_base_url(request) base_url: str = _build_base_url(request)
sitemap_urls: list[dict[str, str | dict[str, str]]] = [] sitemap_urls: list[dict[str, str]] = []
kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.all() kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.all()
for campaign in kick_campaigns: for campaign in kick_campaigns:
resource_url: str = reverse("kick:campaign_detail", args=[campaign.kick_id]) resource_url: str = reverse("kick:campaign_detail", args=[campaign.kick_id])
full_url: str = f"{base_url}{resource_url}" full_url: str = f"{base_url}{resource_url}"
entry: dict[str, str | dict[str, str]] = {"url": full_url} entry: dict[str, str] = {"url": full_url}
if campaign.updated_at: if campaign.updated_at:
entry["lastmod"] = campaign.updated_at.isoformat() entry["lastmod"] = campaign.updated_at.isoformat()
@ -466,7 +475,7 @@ def sitemap_youtube_view(request: HttpRequest) -> HttpResponse:
""" """
base_url: str = _build_base_url(request) base_url: str = _build_base_url(request)
sitemap_urls: list[dict[str, str | dict[str, str]]] = [ sitemap_urls: list[dict[str, str]] = [
{"url": f"{base_url}{reverse('youtube:index')}"}, {"url": f"{base_url}{reverse('youtube:index')}"},
] ]
@ -980,7 +989,7 @@ def dashboard(request: HttpRequest) -> HttpResponse:
kick_campaigns_by_game[game_key]["campaigns"].append({ kick_campaigns_by_game[game_key]["campaigns"].append({
"campaign": campaign, "campaign": campaign,
"channels": list(campaign.channels.all()), "channels": list(campaign.channels.all()),
"rewards": list(campaign.rewards.all()), "rewards": list(campaign.rewards.all()), # pyright: ignore[reportAttributeAccessIssue]
}) })
active_reward_campaigns: QuerySet[RewardCampaign] = ( active_reward_campaigns: QuerySet[RewardCampaign] = (

View file

@ -56,18 +56,29 @@ def _build_seo_context(
"robots_directive": "index, follow", "robots_directive": "index, follow",
} }
if seo_meta: if seo_meta:
if seo_meta.get("page_url"): page_url: str | None = seo_meta.get("page_url")
context["page_url"] = seo_meta["page_url"] if page_url:
if seo_meta.get("og_type"): context["page_url"] = page_url
context["og_type"] = seo_meta["og_type"]
if seo_meta.get("robots_directive"): og_type: str | None = seo_meta.get("og_type")
context["robots_directive"] = seo_meta["robots_directive"] if og_type:
if seo_meta.get("schema_data"): context["og_type"] = og_type
context["schema_data"] = json.dumps(seo_meta["schema_data"])
if seo_meta.get("published_date"): robots_directive: str | None = seo_meta.get("robots_directive")
context["published_date"] = seo_meta["published_date"] if robots_directive:
if seo_meta.get("modified_date"): context["robots_directive"] = robots_directive
context["modified_date"] = seo_meta["modified_date"]
schema_data: dict[str, Any] | None = seo_meta.get("schema_data")
if schema_data:
context["schema_data"] = json.dumps(schema_data)
published_date: str | None = seo_meta.get("published_date")
if published_date:
context["published_date"] = published_date
modified_date: str | None = seo_meta.get("modified_date")
if modified_date:
context["modified_date"] = modified_date
return context return context

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Before After
Before After

BIN
static/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before After
Before After

1
static/favicon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 31 KiB

View file

@ -1,19 +1,28 @@
{ {
"name": "ttvdrops", "name": "ttvdrops.lovinator.space",
"short_name": "ttvdrops", "short_name": "TTVDrops",
"description": "Track Twitch and Kick drops.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"categories": [
"games",
"utilities"
],
"theme_color": "#0c227f",
"background_color": "#000000",
"icons": [ "icons": [
{ {
"src": "/android-chrome-192x192.png", "src": "web-app-manifest-192x192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png",
"purpose": "maskable"
}, },
{ {
"src": "/android-chrome-512x512.png", "src": "web-app-manifest-512x512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png",
"purpose": "maskable"
} }
], ]
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -5,6 +5,16 @@
<link rel="apple-touch-icon" <link rel="apple-touch-icon"
sizes="180x180" sizes="180x180"
href="{% static 'apple-touch-icon.png' %}" /> href="{% static 'apple-touch-icon.png' %}" />
<meta name="apple-mobile-web-app-title" content="TTVDrops" />
<link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}" />
<link rel="shortcut icon"
type="image/x-icon"
href="{% static 'favicon.ico' %}" />
<link rel="icon" type="image/svg+xml" href="{% static 'favicon.svg' %}" />
<link rel="icon"
type="image/png"
sizes="96x96"
href="{% static 'favicon-96x96.png' %}" />
<link rel="icon" <link rel="icon"
type="image/png" type="image/png"
sizes="32x32" sizes="32x32"

View file

@ -147,27 +147,46 @@ def _build_seo_context(
"robots_directive": "index, follow", "robots_directive": "index, follow",
} }
if seo_meta: if seo_meta:
if seo_meta.get("page_url"): page_url = seo_meta.get("page_url")
context["page_url"] = seo_meta["page_url"] if page_url:
if seo_meta.get("og_type"): context["page_url"] = page_url
context["og_type"] = seo_meta["og_type"]
if seo_meta.get("robots_directive"): og_type = seo_meta.get("og_type")
context["robots_directive"] = seo_meta["robots_directive"] if og_type:
if seo_meta.get("page_image"): context["og_type"] = og_type
context["page_image"] = seo_meta["page_image"]
if seo_meta.get("page_image_width") and seo_meta.get("page_image_height"): robots_directive = seo_meta.get("robots_directive")
context["page_image_width"] = seo_meta["page_image_width"] if robots_directive:
context["page_image_height"] = seo_meta["page_image_height"] context["robots_directive"] = robots_directive
if seo_meta.get("schema_data"):
context["schema_data"] = json.dumps(seo_meta["schema_data"]) page_image = seo_meta.get("page_image")
if seo_meta.get("breadcrumb_schema"): if page_image:
context["breadcrumb_schema"] = json.dumps(seo_meta["breadcrumb_schema"]) context["page_image"] = page_image
if seo_meta.get("pagination_info"): page_image_width = seo_meta.get("page_image_width")
context["pagination_info"] = seo_meta["pagination_info"] page_image_height = seo_meta.get("page_image_height")
if seo_meta.get("published_date"): if page_image_width and page_image_height:
context["published_date"] = seo_meta["published_date"] context["page_image_width"] = page_image_width
if seo_meta.get("modified_date"): context["page_image_height"] = page_image_height
context["modified_date"] = seo_meta["modified_date"]
schema_data = seo_meta.get("schema_data")
if schema_data:
context["schema_data"] = json.dumps(schema_data)
breadcrumb_schema = seo_meta.get("breadcrumb_schema")
if breadcrumb_schema:
context["breadcrumb_schema"] = json.dumps(breadcrumb_schema)
pagination_info = seo_meta.get("pagination_info")
if pagination_info:
context["pagination_info"] = pagination_info
published_date = seo_meta.get("published_date")
if published_date:
context["published_date"] = published_date
modified_date = seo_meta.get("modified_date")
if modified_date:
context["modified_date"] = modified_date
return context return context