From 37c1390100ddf0131e3fdd238a04fe1a7b91f6ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Hells=C3=A9n?= Date: Sun, 14 Jun 2026 20:48:46 +0200 Subject: [PATCH] Add rewards to more pages as we have support for them now --- core/urls.py | 19 ++ templates/base.html | 3 +- templates/chzzk/campaign_detail.html | 135 +++++----- templates/core/dashboard.html | 71 +++--- templates/core/dataset_backups.html | 8 + templates/core/docs_rss.html | 33 ++- templates/kick/campaign_detail.html | 115 +++++---- templates/kick/category_detail.html | 44 ++-- templates/kick/organization_detail.html | 27 +- templates/twitch/channel_detail.html | 40 +-- templates/twitch/game_detail.html | 184 ++++++++++++-- templates/twitch/organization_detail.html | 22 +- twitch/api.py | 11 + twitch/feeds.py | 292 ++++++++++++++++++++++ twitch/tests/test_feeds.py | 164 ++++++++++++ twitch/views.py | 24 ++ 16 files changed, 971 insertions(+), 221 deletions(-) diff --git a/core/urls.py b/core/urls.py index bee3161..2045611 100644 --- a/core/urls.py +++ b/core/urls.py @@ -17,6 +17,9 @@ from twitch.feeds import GameCampaignDiscordFeed from twitch.feeds import GameCampaignFeed from twitch.feeds import GameDiscordFeed from twitch.feeds import GameFeed +from twitch.feeds import GameRewardCampaignAtomFeed +from twitch.feeds import GameRewardCampaignDiscordFeed +from twitch.feeds import GameRewardCampaignFeed from twitch.feeds import OrganizationAtomFeed from twitch.feeds import OrganizationDiscordFeed from twitch.feeds import OrganizationRSSFeed @@ -93,6 +96,12 @@ urlpatterns: list[URLPattern | URLResolver] = [ view=OrganizationRSSFeed(), name="organization_feed", ), + # /rss/games//rewards/ - active reward campaigns for a specific game + path( + route="rss/games//rewards/", + view=GameRewardCampaignFeed(), + name="game_reward_feed", + ), # /rss/reward-campaigns/ - all active reward campaigns path( route="rss/reward-campaigns/", @@ -120,6 +129,11 @@ urlpatterns: list[URLPattern | URLResolver] = [ view=OrganizationAtomFeed(), name="organization_feed_atom", ), + path( + route="atom/games//rewards/", + view=GameRewardCampaignAtomFeed(), + name="game_reward_feed_atom", + ), path( route="atom/reward-campaigns/", view=RewardCampaignAtomFeed(), @@ -146,6 +160,11 @@ urlpatterns: list[URLPattern | URLResolver] = [ view=OrganizationDiscordFeed(), name="organization_feed_discord", ), + path( + route="discord/games//rewards/", + view=GameRewardCampaignDiscordFeed(), + name="game_reward_feed_discord", + ), path( route="discord/reward-campaigns/", view=RewardCampaignDiscordFeed(), diff --git a/templates/base.html b/templates/base.html index d558273..9fe04e5 100644 --- a/templates/base.html +++ b/templates/base.html @@ -263,7 +263,8 @@
CC0; Information wants to be free.
- Data retrival possible because of DevilXD/TwitchDropsMiner. + Data retrival possible because of DevilXD/TwitchDropsMiner + and SunkwiBOT/twitch-drops-api.
Data fetched at :01, :16, :31, and :46. diff --git a/templates/chzzk/campaign_detail.html b/templates/chzzk/campaign_detail.html index 51a36d5..baaf1a6 100644 --- a/templates/chzzk/campaign_detail.html +++ b/templates/chzzk/campaign_detail.html @@ -38,72 +38,81 @@

{{ campaign.description|linebreaksbr }}

-
- Published - {% if campaign.scraped_at %} - ({{ campaign.scraped_at|timesince }} ago) - {% elif campaign.start_date %} - ({{ campaign.start_date|timesince }} ago) - {% else %} - unknown - {% endif %} -
-
- Last updated - {% if campaign.scraped_at %} - ({{ campaign.scraped_at|timesince }} ago) - {% elif campaign.updated_at %} - ({{ campaign.updated_at|timesince }} ago) - {% else %} - unknown - {% endif %} -
- -
- {% if campaign.end_date %} - {% if campaign.end_date < now %} - Ended - ({{ campaign.end_date|timesince }} ago) - {% else %} - Ends in - (in {{ campaign.end_date|timeuntil }}) - {% endif %} - {% else %} - Ends unknown - {% endif %} -
- -
- {% if campaign.start_date %} - {% if campaign.start_date > now %} - Starts in - (in {{ campaign.start_date|timeuntil }}) - {% else %} - Started +
+
Published
+
+ {% if campaign.scraped_at %} + ({{ campaign.scraped_at|timesince }} ago) + {% elif campaign.start_date %} ({{ campaign.start_date|timesince }} ago) + {% else %} + unknown {% endif %} - {% else %} - Starts unknown - {% endif %} -
- -
- Duration is - {% if campaign.start_date and campaign.end_date %} - - {% else %} - unknown - {% endif %} -
+ +
Last updated
+
+ {% if campaign.scraped_at %} + ({{ campaign.scraped_at|timesince }} ago) + {% elif campaign.updated_at %} + ({{ campaign.updated_at|timesince }} ago) + {% else %} + unknown + {% endif %} +
+
+ {% if campaign.end_date and campaign.end_date < now %} + Ended + {% else %} + Ends + {% endif %} +
+
+ {% if campaign.end_date %} + + {% if campaign.end_date < now %} + ({{ campaign.end_date|timesince }} ago) + {% else %} + (in {{ campaign.end_date|timeuntil }}) + {% endif %} + {% else %} + unknown + {% endif %} +
+
+ {% if campaign.start_date and campaign.start_date > now %} + Starts + {% else %} + Started + {% endif %} +
+
+ {% if campaign.start_date %} + + {% if campaign.start_date > now %} + (in {{ campaign.start_date|timeuntil }}) + {% else %} + ({{ campaign.start_date|timesince }} ago) + {% endif %} + {% else %} + unknown + {% endif %} +
+
Duration
+
+ {% if campaign.start_date and campaign.end_date %} + + {% else %} + unknown + {% endif %} +
+
{% if campaign.pc_link_url %}[details]{% endif %} diff --git a/templates/core/dashboard.html b/templates/core/dashboard.html index 317eaf7..67d42b7 100644 --- a/templates/core/dashboard.html +++ b/templates/core/dashboard.html @@ -31,11 +31,10 @@ {% endblock extra_head %} {% block content %}
-

Active Drops Dashboard

+

Open Drop Campaigns

- A combined overview of currently active Twitch and Kick drops campaigns. -
- Click any campaign to open details. + Some Drops campaigns may not be available in your region. + Learn more about Drops.


@@ -173,32 +172,39 @@ padding-top: 1rem">

- Twitch Reward Campaigns (Quest Rewards) + Open Reward Campaigns

+

+ Rewards are limited-time offers from top brands that you can unlock by + supporting your favorite Twitch creators. Visit your inventory + to access unlocked rewards. +

-
+
+
{% endif %}

Note that some drops has missing or incomplete data due to Twitch API limitations.

+

+ Reward campaign data is sourced from + SunkwiBOT/twitch-drops-api. + Raw JSON exports are available in the repository: + drops.json + and + rewards.json. +

Need a special format for your workflow or research pipeline? Contact me via GitHub issues diff --git a/templates/core/docs_rss.html b/templates/core/docs_rss.html index 69ed1de..92737be 100644 --- a/templates/core/docs_rss.html +++ b/templates/core/docs_rss.html @@ -127,11 +127,15 @@ {% if game %}

Filtered RSS Feeds

-

You can subscribe to RSS feeds scoped to a specific game.

+

+ You can subscribe to RSS feeds scoped to a specific game. + The Rewards feed will be empty if the game has no active reward campaigns. +

+ @@ -139,20 +143,39 @@ - + + + + + + + + diff --git a/templates/kick/campaign_detail.html b/templates/kick/campaign_detail.html index 524607a..4c3dde3 100644 --- a/templates/kick/campaign_detail.html +++ b/templates/kick/campaign_detail.html @@ -51,61 +51,74 @@ Campaigns > {{ campaign.name }} -
+
{% if campaign.category %} - Category: - {{ campaign.category.name }} +
Category
+
+ {{ campaign.category.name }} +
{% endif %} -
-
{% if campaign.organization %} - Organization: - {{ campaign.organization.name }} +
Organization
+
+ {{ campaign.organization.name }} +
{% endif %} -
- {% if campaign.ends_at %} -
- {% if campaign.ends_at > now %} - Ends - {% else %} - Ended at - {% endif %} - - {% if campaign.ends_at < now %} - (ended {{ campaign.ends_at|timesince }} ago) - {% else %} - (in {{ campaign.ends_at|timeuntil }}) - {% endif %} -
- {% endif %} - {% if campaign.starts_at %} -
- {% if campaign.starts_at > now %} - Starts - {% else %} - Started at - {% endif %} - - {% if campaign.starts_at < now %} - (started {{ campaign.starts_at|timesince }} ago) - {% else %} - (in {{ campaign.starts_at|timeuntil }}) - {% endif %} -
- {% endif %} - {% if campaign.duration %}
Duration: {{ campaign.duration }}
{% endif %} -
- Added - ({{ campaign.added_at|timesince }} ago) -
-
- Updated - ({{ campaign.updated_at|timesince }} ago) -
+ {% if campaign.ends_at %} +
+ {% if campaign.ends_at > now %} + Ends + {% else %} + Ended at + {% endif %} +
+
+ + {% if campaign.ends_at < now %} + (ended {{ campaign.ends_at|timesince }} ago) + {% else %} + (in {{ campaign.ends_at|timeuntil }}) + {% endif %} +
+ {% endif %} + {% if campaign.starts_at %} +
+ {% if campaign.starts_at > now %} + Starts + {% else %} + Started at + {% endif %} +
+
+ + {% if campaign.starts_at < now %} + (started {{ campaign.starts_at|timesince }} ago) + {% else %} + (in {{ campaign.starts_at|timeuntil }}) + {% endif %} +
+ {% endif %} + {% if campaign.duration %} +
Duration
+
+ {{ campaign.duration }} +
+ {% endif %} +
Added
+
+ + ({{ campaign.added_at|timesince }} ago) +
+
Updated
+
+ + ({{ campaign.updated_at|timesince }} ago) +
+
{% if campaign.channels.all %} Participating channels: diff --git a/templates/kick/category_detail.html b/templates/kick/category_detail.html index d77e751..97e9dcd 100644 --- a/templates/kick/category_detail.html +++ b/templates/kick/category_detail.html @@ -63,20 +63,36 @@ {{ category.kick_url }}

{% endif %} -

- ID: {{ category.kick_id }} - - Added: - ({{ category.added_at|timesince }} ago) - - Updated: - ({{ category.updated_at|timesince }} ago) -

-

- Active: {{ active_campaigns|length }} - - Upcoming: {{ upcoming_campaigns|length }} - - Expired: {{ expired_campaigns|length }} -

+
+
ID
+
+ {{ category.kick_id }} +
+
Added
+
+ + ({{ category.added_at|timesince }} ago) +
+
Updated
+
+ + ({{ category.updated_at|timesince }} ago) +
+
Active
+
+ {{ active_campaigns|length }} +
+
Upcoming
+
+ {{ upcoming_campaigns|length }} +
+
Expired
+
+ {{ expired_campaigns|length }} +
+

diff --git a/templates/kick/organization_detail.html b/templates/kick/organization_detail.html index 46562dd..d269703 100644 --- a/templates/kick/organization_detail.html +++ b/templates/kick/organization_detail.html @@ -31,15 +31,24 @@ {{ org.url }}

{% endif %} -

- ID: {{ org.kick_id }} - - Added: - ({{ org.added_at|timesince }} ago) - - Updated: - ({{ org.updated_at|timesince }} ago) -

+
+
ID
+
+ {{ org.kick_id }} +
+
Added
+
+ + ({{ org.added_at|timesince }} ago) +
+
Updated
+
+ + ({{ org.updated_at|timesince }} ago) +
+

diff --git a/templates/twitch/channel_detail.html b/templates/twitch/channel_detail.html index 1673c94..9110fad 100644 --- a/templates/twitch/channel_detail.html +++ b/templates/twitch/channel_detail.html @@ -9,23 +9,29 @@
Twitch > Channels > {{ channel.display_name }}
- {% if channel.display_name != channel.name %} -
- Username: {{ channel.name }} -
- {% endif %} - -
Channel ID: {{ channel.twitch_id }}
-
- Published - ({{ channel.added_at|timesince }} ago) -
-
- Last updated - ({{ channel.updated_at|timesince }} ago) -
+ +
+ {% if channel.display_name != channel.name %} +
Username
+
+ {{ channel.name }} +
+ {% endif %} +
Channel ID
+
+ {{ channel.twitch_id }} +
+
Published
+
+ ({{ channel.added_at|timesince }} ago) +
+
Last updated
+
+ ({{ channel.updated_at|timesince }} ago) +
+
GameFeed Type RSS Atom Discord
{{ game.display_name }}{{ game.display_name }}Campaigns - https://ttvdrops.lovinator.space/rss/games/{{ game.twitch_id }}/ + https://ttvdrops.lovinator.space/rss/games/{{ game.twitch_id }}/campaigns/ - https://ttvdrops.lovinator.space/atom/games/{{ game.twitch_id }}/ + https://ttvdrops.lovinator.space/atom/games/{{ game.twitch_id }}/campaigns/ - https://ttvdrops.lovinator.space/discord/games/{{ game.twitch_id }}/ + https://ttvdrops.lovinator.space/discord/games/{{ game.twitch_id }}/campaigns/ + +
Rewards + + https://ttvdrops.lovinator.space/rss/games/{{ game.twitch_id }}/rewards/ + + + + https://ttvdrops.lovinator.space/atom/games/{{ game.twitch_id }}/rewards/ + + + + https://ttvdrops.lovinator.space/discord/games/{{ game.twitch_id }}/rewards/