From b8df95e317b4be6f887452df0d328f9fa5901e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Sat, 10 Jan 2026 23:15:09 +0100 Subject: [PATCH] Add /emotes/ --- templates/base.html | 1 + templates/twitch/emote_gallery.html | 14 +++++++++++ twitch/urls.py | 23 +++++++++--------- twitch/views.py | 36 +++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 templates/twitch/emote_gallery.html diff --git a/templates/base.html b/templates/base.html index 09f2956..bb3969f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -162,6 +162,7 @@ Channels | RSS | Debug | + Emotes
diff --git a/templates/twitch/emote_gallery.html b/templates/twitch/emote_gallery.html new file mode 100644 index 0000000..130f345 --- /dev/null +++ b/templates/twitch/emote_gallery.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} +{% block title %}Emotes{% endblock %} +{% block content %} +

Emotes

+ +{% endblock %} diff --git a/twitch/urls.py b/twitch/urls.py index bd80951..e31d0ad 100644 --- a/twitch/urls.py +++ b/twitch/urls.py @@ -18,21 +18,22 @@ app_name = "twitch" urlpatterns: list[URLPattern] = [ path("", views.dashboard, name="dashboard"), - path("search/", views.search_view, name="search"), - path("debug/", views.debug_view, name="debug"), path("campaigns/", views.drop_campaign_list_view, name="campaign_list"), path("campaigns//", views.drop_campaign_detail_view, name="campaign_detail"), - path("games/", views.GamesGridView.as_view(), name="game_list"), - path("games/list/", views.GamesListView.as_view(), name="game_list_simple"), - path("games//", views.GameDetailView.as_view(), name="game_detail"), - path("organizations/", views.org_list_view, name="org_list"), - path("organizations//", views.organization_detail_view, name="organization_detail"), path("channels/", views.ChannelListView.as_view(), name="channel_list"), path("channels//", views.ChannelDetailView.as_view(), name="channel_detail"), - path("rss/organizations/", OrganizationFeed(), name="organization_feed"), - path("rss/organizations//campaigns/", OrganizationCampaignFeed(), name="organization_campaign_feed"), + path("debug/", views.debug_view, name="debug"), + path("docs/rss/", views.docs_rss_view, name="docs_rss"), + path("emotes/", views.emote_gallery_view, name="emote_gallery"), + path("games/", views.GamesGridView.as_view(), name="game_list"), + path("games//", views.GameDetailView.as_view(), name="game_detail"), + path("games/list/", views.GamesListView.as_view(), name="game_list_simple"), + path("organizations/", views.org_list_view, name="org_list"), + path("organizations//", views.organization_detail_view, name="organization_detail"), + path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"), path("rss/games/", GameFeed(), name="game_feed"), path("rss/games//campaigns/", GameCampaignFeed(), name="game_campaign_feed"), - path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"), - path("docs/rss/", views.docs_rss_view, name="docs_rss"), + path("rss/organizations/", OrganizationFeed(), name="organization_feed"), + path("rss/organizations//campaigns/", OrganizationCampaignFeed(), name="organization_campaign_feed"), + path("search/", views.search_view, name="search"), ] diff --git a/twitch/views.py b/twitch/views.py index 2d792ba..3e7b718 100644 --- a/twitch/views.py +++ b/twitch/views.py @@ -49,6 +49,42 @@ MIN_QUERY_LENGTH_FOR_FTS = 3 MIN_SEARCH_RANK = 0.05 +def emote_gallery_view(request: HttpRequest) -> HttpResponse: + """View to display all emote images (distribution_type='EMOTE'), clickable to their campaign. + + Args: + request: The HTTP request. + + Returns: + HttpResponse: The rendered emote gallery page. + """ + emote_benefits: QuerySet[DropBenefit, DropBenefit] = ( + DropBenefit.objects + .filter(distribution_type="EMOTE") + .select_related() + .prefetch_related( + Prefetch( + "drops", + queryset=TimeBasedDrop.objects.select_related("campaign"), + to_attr="_emote_drops", + ), + ) + ) + + emotes: list[dict[str, str | DropCampaign]] = [] + for benefit in emote_benefits: + # Find the first drop with a campaign for this benefit + drop: TimeBasedDrop | None = next((d for d in getattr(benefit, "_emote_drops", []) if d.campaign), None) + if drop and drop.campaign: + emotes.append({ + "image_url": benefit.image_best_url, + "campaign": drop.campaign, + }) + + context: dict[str, list[dict[str, Any]]] = {"emotes": emotes} + return render(request, "twitch/emote_gallery.html", context) + + # MARK: /search/ def search_view(request: HttpRequest) -> HttpResponse: """Search view for all models.