Add support for Atom feeds
This commit is contained in:
parent
bbdcc80334
commit
6c22559fb5
16 changed files with 293 additions and 0 deletions
|
|
@ -23,6 +23,43 @@
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
</title>
|
</title>
|
||||||
{% include "includes/meta_tags.html" %}
|
{% include "includes/meta_tags.html" %}
|
||||||
|
<!-- Feed discovery links -->
|
||||||
|
<!-- Read {% url 'twitch:docs_rss' %} for more details on available feeds -->
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="All campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="All campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:campaign_feed_atom' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Newly added games (RSS)"
|
||||||
|
href="{% url 'twitch:game_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Newly added games (Atom)"
|
||||||
|
href="{% url 'twitch:game_feed_atom' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Newly added organizations (RSS)"
|
||||||
|
href="{% url 'twitch:organization_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Newly added organizations (Atom)"
|
||||||
|
href="{% url 'twitch:organization_feed_atom' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Newly added reward campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:reward_campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Newly added reward campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||||
|
{# Allow child templates to inject page-specific alternates into the head #}
|
||||||
|
{% block extra_head %}
|
||||||
|
{% endblock extra_head %}
|
||||||
<style>
|
<style>
|
||||||
html {
|
html {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ campaign.clean_name }}
|
{{ campaign.clean_name }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
{% if campaign and campaign.game %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="{{ campaign.game.display_name }} campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="{{ campaign.game.display_name }} campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:game_campaign_feed_atom' campaign.game.twitch_id %}" />
|
||||||
|
{% endif %}
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Campaign Title -->
|
<!-- Campaign Title -->
|
||||||
<h1>
|
<h1>
|
||||||
|
|
@ -71,6 +83,8 @@
|
||||||
{% if campaign.game %}
|
{% if campaign.game %}
|
||||||
<a href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}"
|
<a href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}"
|
||||||
title="RSS feed for {{ campaign.game.display_name }} campaigns">[rss]</a>
|
title="RSS feed for {{ campaign.game.display_name }} campaigns">[rss]</a>
|
||||||
|
<a href="{% url 'twitch:game_campaign_feed_atom' campaign.game.twitch_id %}"
|
||||||
|
title="Atom feed for {{ campaign.game.display_name }} campaigns">[atom]</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% if allowed_channels %}
|
{% if allowed_channels %}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,16 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Drop Campaigns - Twitch Drops Tracker
|
Drop Campaigns - Twitch Drops Tracker
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="All campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="All campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:campaign_feed_atom' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
|
|
@ -13,6 +23,8 @@
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:campaign_feed' %}"
|
<a href="{% url 'twitch:campaign_feed' %}"
|
||||||
title="RSS feed for all campaigns">[rss]</a>
|
title="RSS feed for all campaigns">[rss]</a>
|
||||||
|
<a href="{% url 'twitch:campaign_feed_atom' %}"
|
||||||
|
title="Atom feed for all campaigns">[atom]</a>
|
||||||
<a href="{% url 'twitch:export_campaigns_csv' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
|
<a href="{% url 'twitch:export_campaigns_csv' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
|
||||||
title="Export campaigns as CSV">[csv]</a>
|
title="Export campaigns as CSV">[csv]</a>
|
||||||
<a href="{% url 'twitch:export_campaigns_json' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
|
<a href="{% url 'twitch:export_campaigns_json' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Twitch drops
|
Twitch drops
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="All campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="All campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:campaign_feed_atom' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
<h1>Twitch Drops</h1>
|
<h1>Twitch Drops</h1>
|
||||||
|
|
@ -16,6 +26,9 @@ Hover over the end time to see the exact date and time.
|
||||||
<a href="{% url 'twitch:campaign_feed' %}"
|
<a href="{% url 'twitch:campaign_feed' %}"
|
||||||
style="margin-right: 1rem"
|
style="margin-right: 1rem"
|
||||||
title="RSS feed for all campaigns">RSS feed for campaigns</a>
|
title="RSS feed for all campaigns">RSS feed for campaigns</a>
|
||||||
|
|
|
||||||
|
<a href="{% url 'twitch:campaign_feed_atom' %}"
|
||||||
|
title="Atom feed for campaigns">Atom feed for campaigns</a>
|
||||||
</div>
|
</div>
|
||||||
{% if campaigns_by_game %}
|
{% if campaigns_by_game %}
|
||||||
{% for game_id, game_data in campaigns_by_game.items %}
|
{% for game_id, game_data in campaigns_by_game.items %}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,11 @@
|
||||||
<main>
|
<main>
|
||||||
<h1>RSS Feeds Documentation</h1>
|
<h1>RSS Feeds Documentation</h1>
|
||||||
<p>This page lists all available RSS feeds for TTVDrops.</p>
|
<p>This page lists all available RSS feeds for TTVDrops.</p>
|
||||||
|
<p>
|
||||||
|
Note: Atom feeds are also available for the same resources under the
|
||||||
|
<code>/atom/</code> endpoints (links labeled "Atom" are shown next to RSS links).
|
||||||
|
Both RSS and Atom formats are supported and served in parallel for backward compatibility.
|
||||||
|
</p>
|
||||||
<section>
|
<section>
|
||||||
<h2>Global RSS Feeds</h2>
|
<h2>Global RSS Feeds</h2>
|
||||||
<p>These feeds contain all items across the entire site:</p>
|
<p>These feeds contain all items across the entire site:</p>
|
||||||
|
|
@ -17,8 +22,16 @@
|
||||||
<p>{{ feed.description }}</p>
|
<p>{{ feed.description }}</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ feed.url }}">Subscribe to {{ feed.title }} RSS Feed</a>
|
<a href="{{ feed.url }}">Subscribe to {{ feed.title }} RSS Feed</a>
|
||||||
|
{% if feed.atom_url %}
|
||||||
|
|
|
||||||
|
<a href="{{ feed.atom_url }}">Subscribe to {{ feed.title }} Atom Feed</a>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre>
|
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre>
|
||||||
|
{% if feed.example_xml_atom %}
|
||||||
|
<h4>Atom example</h4>
|
||||||
|
<pre><code class="language-xml">{{ feed.example_xml_atom|escape }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
@ -35,13 +48,22 @@
|
||||||
<p>{{ feed.description }}</p>
|
<p>{{ feed.description }}</p>
|
||||||
<p>
|
<p>
|
||||||
Endpoint: <code>{{ feed.url }}</code>
|
Endpoint: <code>{{ feed.url }}</code>
|
||||||
|
{% if feed.atom_url %} | Atom: <code>{{ feed.atom_url }}</code>{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% if feed.has_sample %}
|
{% if feed.has_sample %}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ feed.url }}">View a live example</a>
|
<a href="{{ feed.url }}">View a live example</a>
|
||||||
|
{% if feed.atom_url %}
|
||||||
|
|
|
||||||
|
<a href="{{ feed.atom_url }}">View Atom example</a>
|
||||||
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre>
|
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre>
|
||||||
|
{% if feed.example_xml_atom %}
|
||||||
|
<h4>Atom example</h4>
|
||||||
|
<pre><code class="language-xml">{{ feed.example_xml_atom|escape }}</code></pre>
|
||||||
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,18 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ game.display_name }}
|
{{ game.display_name }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
{% if game %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="{{ game.display_name }} campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:game_campaign_feed' game.twitch_id %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="{{ game.display_name }} campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}" />
|
||||||
|
{% endif %}
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Game Title -->
|
<!-- Game Title -->
|
||||||
<h1>
|
<h1>
|
||||||
|
|
@ -13,6 +25,9 @@
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:game_campaign_feed' game.twitch_id %}"
|
<a href="{% url 'twitch:game_campaign_feed' game.twitch_id %}"
|
||||||
title="RSS feed for {{ game.display_name }} campaigns">RSS feed for {{ game.display_name }} campaigns</a>
|
title="RSS feed for {{ game.display_name }} campaigns">RSS feed for {{ game.display_name }} campaigns</a>
|
||||||
|
|
|
||||||
|
<a href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}"
|
||||||
|
title="Atom feed for {{ game.display_name }} campaigns">Atom feed for {{ game.display_name }} campaigns</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Game image -->
|
<!-- Game image -->
|
||||||
{% if game.box_art_best_url %}
|
{% if game.box_art_best_url %}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,16 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Games - Grid View
|
Games - Grid View
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Newly added games (RSS)"
|
||||||
|
href="{% url 'twitch:game_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Newly added games (Atom)"
|
||||||
|
href="{% url 'twitch:game_feed_atom' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
<header>
|
<header>
|
||||||
|
|
@ -10,6 +20,8 @@
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:games_list' %}" title="View games as list">[list]</a>
|
<a href="{% url 'twitch:games_list' %}" title="View games as list">[list]</a>
|
||||||
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
|
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
|
||||||
|
<a href="{% url 'twitch:game_feed_atom' %}"
|
||||||
|
title="Atom feed for all games">[atom]</a>
|
||||||
<a href="{% url 'twitch:export_games_csv' %}"
|
<a href="{% url 'twitch:export_games_csv' %}"
|
||||||
title="Export all games as CSV">[csv]</a>
|
title="Export all games as CSV">[csv]</a>
|
||||||
<a href="{% url 'twitch:export_games_json' %}"
|
<a href="{% url 'twitch:export_games_json' %}"
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,24 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Games - List View
|
Games - List View
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Newly added games (RSS)"
|
||||||
|
href="{% url 'twitch:game_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Newly added games (Atom)"
|
||||||
|
href="{% url 'twitch:game_feed_atom' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<main>
|
<main>
|
||||||
<h1>Games List</h1>
|
<h1>Games List</h1>
|
||||||
<div>
|
<div>
|
||||||
<a href="{% url 'twitch:games_grid' %}" title="View games as grid">[grid]</a>
|
<a href="{% url 'twitch:games_grid' %}" title="View games as grid">[grid]</a>
|
||||||
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
|
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
|
||||||
|
<a href="{% url 'twitch:game_feed_atom' %}"
|
||||||
|
title="Atom feed for all games">[atom]</a>
|
||||||
<a href="{% url 'twitch:export_games_csv' %}"
|
<a href="{% url 'twitch:export_games_csv' %}"
|
||||||
title="Export all games as CSV">[csv]</a>
|
title="Export all games as CSV">[csv]</a>
|
||||||
<a href="{% url 'twitch:export_games_json' %}"
|
<a href="{% url 'twitch:export_games_json' %}"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
<a href="{% url 'twitch:organization_feed' %}"
|
<a href="{% url 'twitch:organization_feed' %}"
|
||||||
style="margin-right: 1rem"
|
style="margin-right: 1rem"
|
||||||
title="RSS feed for all organizations">RSS feed for organizations</a>
|
title="RSS feed for all organizations">RSS feed for organizations</a>
|
||||||
|
<a href="{% url 'twitch:organization_feed_atom' %}"
|
||||||
|
title="Atom feed for all organizations">[atom]</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Export Options -->
|
<!-- Export Options -->
|
||||||
<div style="margin-bottom: 1rem; display: flex; gap: 1rem;">
|
<div style="margin-bottom: 1rem; display: flex; gap: 1rem;">
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,20 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ organization.name }}
|
{{ organization.name }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
{% if games %}
|
||||||
|
{% for game in games %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="{{ game.display_name|default:game.name|default:game.twitch_id }} campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:game_campaign_feed' game.twitch_id %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="{{ game.display_name|default:game.name|default:game.twitch_id }} campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:game_campaign_feed_atom' game.twitch_id %}" />
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 id="org-name">{{ organization.name }}</h1>
|
<h1 id="org-name">{{ organization.name }}</h1>
|
||||||
<theader>
|
<theader>
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{{ reward_campaign.name }}
|
{{ reward_campaign.name }}
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Reward campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:reward_campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Reward campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<!-- Campaign Title -->
|
<!-- Campaign Title -->
|
||||||
{% if reward_campaign.brand %}
|
{% if reward_campaign.brand %}
|
||||||
|
|
@ -24,6 +34,8 @@
|
||||||
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
||||||
style="margin-right: 1rem"
|
style="margin-right: 1rem"
|
||||||
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
||||||
|
<a href="{% url 'twitch:reward_campaign_feed_atom' %}"
|
||||||
|
title="Atom feed for all reward campaigns">[atom]</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Campaign Summary -->
|
<!-- Campaign Summary -->
|
||||||
{% if reward_campaign.summary %}<p id="campaign-summary">{{ reward_campaign.summary|linebreaksbr }}</p>{% endif %}
|
{% if reward_campaign.summary %}<p id="campaign-summary">{{ reward_campaign.summary|linebreaksbr }}</p>{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,16 @@
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Reward Campaigns - Twitch Drops Tracker
|
Reward Campaigns - Twitch Drops Tracker
|
||||||
{% endblock title %}
|
{% endblock title %}
|
||||||
|
{% block extra_head %}
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/rss+xml"
|
||||||
|
title="Reward campaigns (RSS)"
|
||||||
|
href="{% url 'twitch:reward_campaign_feed' %}" />
|
||||||
|
<link rel="alternate"
|
||||||
|
type="application/atom+xml"
|
||||||
|
title="Reward campaigns (Atom)"
|
||||||
|
href="{% url 'twitch:reward_campaign_feed_atom' %}" />
|
||||||
|
{% endblock extra_head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1 id="page-title">Reward Campaigns (Quest Rewards)</h1>
|
<h1 id="page-title">Reward Campaigns (Quest Rewards)</h1>
|
||||||
<p>Browse all available quest reward campaigns</p>
|
<p>Browse all available quest reward campaigns</p>
|
||||||
|
|
@ -11,6 +21,8 @@
|
||||||
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
<a href="{% url 'twitch:reward_campaign_feed' %}"
|
||||||
style="margin-right: 1rem"
|
style="margin-right: 1rem"
|
||||||
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
title="RSS feed for all reward campaigns">RSS feed for all reward campaigns</a>
|
||||||
|
<a href="{% url 'twitch:reward_campaign_feed_atom' %}"
|
||||||
|
title="Atom feed for all reward campaigns">[atom]</a>
|
||||||
</div>
|
</div>
|
||||||
<!-- Filter Form -->
|
<!-- Filter Form -->
|
||||||
<form id="filter-form"
|
<form id="filter-form"
|
||||||
|
|
|
||||||
|
|
@ -920,3 +920,34 @@ class RewardCampaignFeed(Feed):
|
||||||
def item_author_name(self, item: RewardCampaign) -> str:
|
def item_author_name(self, item: RewardCampaign) -> str:
|
||||||
"""Return the author name for the reward campaign."""
|
"""Return the author name for the reward campaign."""
|
||||||
return item.get_feed_author_name()
|
return item.get_feed_author_name()
|
||||||
|
|
||||||
|
|
||||||
|
# Atom feed variants: reuse existing logic but switch the feed generator to Atom
|
||||||
|
class OrganizationAtomFeed(OrganizationRSSFeed):
|
||||||
|
"""Atom feed for latest organizations (reuses OrganizationRSSFeed)."""
|
||||||
|
|
||||||
|
feed_type = feedgenerator.Atom1Feed
|
||||||
|
|
||||||
|
|
||||||
|
class GameAtomFeed(GameFeed):
|
||||||
|
"""Atom feed for newly added games (reuses GameFeed)."""
|
||||||
|
|
||||||
|
feed_type = feedgenerator.Atom1Feed
|
||||||
|
|
||||||
|
|
||||||
|
class DropCampaignAtomFeed(DropCampaignFeed):
|
||||||
|
"""Atom feed for latest drop campaigns (reuses DropCampaignFeed)."""
|
||||||
|
|
||||||
|
feed_type = feedgenerator.Atom1Feed
|
||||||
|
|
||||||
|
|
||||||
|
class GameCampaignAtomFeed(GameCampaignFeed):
|
||||||
|
"""Atom feed for latest drop campaigns for a specific game (reuses GameCampaignFeed)."""
|
||||||
|
|
||||||
|
feed_type = feedgenerator.Atom1Feed
|
||||||
|
|
||||||
|
|
||||||
|
class RewardCampaignAtomFeed(RewardCampaignFeed):
|
||||||
|
"""Atom feed for latest reward campaigns (reuses RewardCampaignFeed)."""
|
||||||
|
|
||||||
|
feed_type = feedgenerator.Atom1Feed
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,32 @@ class RSSFeedTestCase(TestCase):
|
||||||
assert 'length="42"' in content
|
assert 'length="42"' in content
|
||||||
assert 'type="image/png"' in content
|
assert 'type="image/png"' in content
|
||||||
|
|
||||||
|
def test_organization_atom_feed(self) -> None:
|
||||||
|
"""Test organization Atom feed returns 200 and Atom XML."""
|
||||||
|
url: str = reverse("twitch:organization_feed_atom")
|
||||||
|
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response["Content-Type"] == "application/atom+xml; charset=utf-8"
|
||||||
|
content: str = response.content.decode("utf-8")
|
||||||
|
assert "<feed" in content
|
||||||
|
assert "<entry" in content or "<entry" in content
|
||||||
|
|
||||||
|
def test_game_atom_feed(self) -> None:
|
||||||
|
"""Test game Atom feed returns 200 and contains expected content."""
|
||||||
|
url: str = reverse("twitch:game_feed_atom")
|
||||||
|
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response["Content-Type"] == "application/atom+xml; charset=utf-8"
|
||||||
|
content: str = response.content.decode("utf-8")
|
||||||
|
assert "Owned by Test Organization." in content
|
||||||
|
expected_atom_link: str = reverse(
|
||||||
|
"twitch:game_campaign_feed",
|
||||||
|
args=[self.game.twitch_id],
|
||||||
|
)
|
||||||
|
assert expected_atom_link in content
|
||||||
|
# Atom should include box art URL somewhere in content
|
||||||
|
assert "https://example.com/box.png" in content
|
||||||
|
|
||||||
def test_game_feed_enclosure_helpers(self) -> None:
|
def test_game_feed_enclosure_helpers(self) -> None:
|
||||||
"""Helper methods should return values from model fields."""
|
"""Helper methods should return values from model fields."""
|
||||||
feed = GameFeed()
|
feed = GameFeed()
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,15 @@ from typing import TYPE_CHECKING
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from twitch import views
|
from twitch import views
|
||||||
|
from twitch.feeds import DropCampaignAtomFeed
|
||||||
from twitch.feeds import DropCampaignFeed
|
from twitch.feeds import DropCampaignFeed
|
||||||
|
from twitch.feeds import GameAtomFeed
|
||||||
|
from twitch.feeds import GameCampaignAtomFeed
|
||||||
from twitch.feeds import GameCampaignFeed
|
from twitch.feeds import GameCampaignFeed
|
||||||
from twitch.feeds import GameFeed
|
from twitch.feeds import GameFeed
|
||||||
|
from twitch.feeds import OrganizationAtomFeed
|
||||||
from twitch.feeds import OrganizationRSSFeed
|
from twitch.feeds import OrganizationRSSFeed
|
||||||
|
from twitch.feeds import RewardCampaignAtomFeed
|
||||||
from twitch.feeds import RewardCampaignFeed
|
from twitch.feeds import RewardCampaignFeed
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
@ -105,4 +110,22 @@ urlpatterns: list[URLPattern] = [
|
||||||
RewardCampaignFeed(),
|
RewardCampaignFeed(),
|
||||||
name="reward_campaign_feed",
|
name="reward_campaign_feed",
|
||||||
),
|
),
|
||||||
|
# Atom feeds (added alongside RSS to preserve backward compatibility)
|
||||||
|
path("atom/campaigns/", DropCampaignAtomFeed(), name="campaign_feed_atom"),
|
||||||
|
path("atom/games/", GameAtomFeed(), name="game_feed_atom"),
|
||||||
|
path(
|
||||||
|
"atom/games/<str:twitch_id>/campaigns/",
|
||||||
|
GameCampaignAtomFeed(),
|
||||||
|
name="game_campaign_feed_atom",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"atom/organizations/",
|
||||||
|
OrganizationAtomFeed(),
|
||||||
|
name="organization_feed_atom",
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
"atom/reward-campaigns/",
|
||||||
|
RewardCampaignAtomFeed(),
|
||||||
|
name="reward_campaign_feed_atom",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,15 @@ from pygments import highlight
|
||||||
from pygments.formatters import HtmlFormatter
|
from pygments.formatters import HtmlFormatter
|
||||||
from pygments.lexers.data import JsonLexer
|
from pygments.lexers.data import JsonLexer
|
||||||
|
|
||||||
|
from twitch.feeds import DropCampaignAtomFeed
|
||||||
from twitch.feeds import DropCampaignFeed
|
from twitch.feeds import DropCampaignFeed
|
||||||
|
from twitch.feeds import GameAtomFeed
|
||||||
|
from twitch.feeds import GameCampaignAtomFeed
|
||||||
from twitch.feeds import GameCampaignFeed
|
from twitch.feeds import GameCampaignFeed
|
||||||
from twitch.feeds import GameFeed
|
from twitch.feeds import GameFeed
|
||||||
|
from twitch.feeds import OrganizationAtomFeed
|
||||||
from twitch.feeds import OrganizationRSSFeed
|
from twitch.feeds import OrganizationRSSFeed
|
||||||
|
from twitch.feeds import RewardCampaignAtomFeed
|
||||||
from twitch.feeds import RewardCampaignFeed
|
from twitch.feeds import RewardCampaignFeed
|
||||||
from twitch.models import Channel
|
from twitch.models import Channel
|
||||||
from twitch.models import ChatBadge
|
from twitch.models import ChatBadge
|
||||||
|
|
@ -1808,30 +1813,46 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
|
||||||
)
|
)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
show_atom: bool = bool(request.GET.get("show_atom"))
|
||||||
|
|
||||||
feeds: list[dict[str, str]] = [
|
feeds: list[dict[str, str]] = [
|
||||||
{
|
{
|
||||||
"title": "All Organizations",
|
"title": "All Organizations",
|
||||||
"description": "Latest organizations added to TTVDrops",
|
"description": "Latest organizations added to TTVDrops",
|
||||||
"url": absolute(reverse("twitch:organization_feed")),
|
"url": absolute(reverse("twitch:organization_feed")),
|
||||||
|
"atom_url": absolute(reverse("twitch:organization_feed_atom")),
|
||||||
"example_xml": render_feed(OrganizationRSSFeed()),
|
"example_xml": render_feed(OrganizationRSSFeed()),
|
||||||
|
"example_xml_atom": render_feed(OrganizationAtomFeed())
|
||||||
|
if show_atom
|
||||||
|
else "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "All Games",
|
"title": "All Games",
|
||||||
"description": "Latest games added to TTVDrops",
|
"description": "Latest games added to TTVDrops",
|
||||||
"url": absolute(reverse("twitch:game_feed")),
|
"url": absolute(reverse("twitch:game_feed")),
|
||||||
|
"atom_url": absolute(reverse("twitch:game_feed_atom")),
|
||||||
"example_xml": render_feed(GameFeed()),
|
"example_xml": render_feed(GameFeed()),
|
||||||
|
"example_xml_atom": render_feed(GameAtomFeed()) if show_atom else "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "All Drop Campaigns",
|
"title": "All Drop Campaigns",
|
||||||
"description": "Latest drop campaigns across all games",
|
"description": "Latest drop campaigns across all games",
|
||||||
"url": absolute(reverse("twitch:campaign_feed")),
|
"url": absolute(reverse("twitch:campaign_feed")),
|
||||||
|
"atom_url": absolute(reverse("twitch:campaign_feed_atom")),
|
||||||
"example_xml": render_feed(DropCampaignFeed()),
|
"example_xml": render_feed(DropCampaignFeed()),
|
||||||
|
"example_xml_atom": render_feed(DropCampaignAtomFeed())
|
||||||
|
if show_atom
|
||||||
|
else "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "All Reward Campaigns",
|
"title": "All Reward Campaigns",
|
||||||
"description": "Latest reward campaigns (Quest rewards) on Twitch",
|
"description": "Latest reward campaigns (Quest rewards) on Twitch",
|
||||||
"url": absolute(reverse("twitch:reward_campaign_feed")),
|
"url": absolute(reverse("twitch:reward_campaign_feed")),
|
||||||
|
"atom_url": absolute(reverse("twitch:reward_campaign_feed_atom")),
|
||||||
"example_xml": render_feed(RewardCampaignFeed()),
|
"example_xml": render_feed(RewardCampaignFeed()),
|
||||||
|
"example_xml_atom": render_feed(RewardCampaignAtomFeed())
|
||||||
|
if show_atom
|
||||||
|
else "",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1851,10 +1872,25 @@ def docs_rss_view(request: HttpRequest) -> HttpResponse:
|
||||||
if sample_game
|
if sample_game
|
||||||
else absolute("/rss/games/<game_id>/campaigns/")
|
else absolute("/rss/games/<game_id>/campaigns/")
|
||||||
),
|
),
|
||||||
|
"atom_url": (
|
||||||
|
absolute(
|
||||||
|
reverse(
|
||||||
|
"twitch:game_campaign_feed_atom",
|
||||||
|
args=[sample_game.twitch_id],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if sample_game
|
||||||
|
else absolute("/atom/games/<game_id>/campaigns/")
|
||||||
|
),
|
||||||
"has_sample": bool(sample_game),
|
"has_sample": bool(sample_game),
|
||||||
"example_xml": render_feed(GameCampaignFeed(), sample_game.twitch_id)
|
"example_xml": render_feed(GameCampaignFeed(), sample_game.twitch_id)
|
||||||
if sample_game
|
if sample_game
|
||||||
else "",
|
else "",
|
||||||
|
"example_xml_atom": (
|
||||||
|
render_feed(GameCampaignAtomFeed(), sample_game.twitch_id)
|
||||||
|
if sample_game and show_atom
|
||||||
|
else ""
|
||||||
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue