Improve feed explenation; add link to templates

This commit is contained in:
Joakim Hellsén 2026-03-17 05:36:31 +01:00
commit 768e6f2111
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
19 changed files with 200 additions and 279 deletions

View file

@ -2,9 +2,27 @@ from typing import TYPE_CHECKING
from django.urls import path from django.urls import path
from core import views from core.views import dashboard
from core.views import dataset_backup_download_view
from core.views import dataset_backups_view
from core.views import debug_view
from core.views import docs_rss_view
from core.views import search_view
from twitch.feeds import DropCampaignAtomFeed
from twitch.feeds import DropCampaignDiscordFeed
from twitch.feeds import DropCampaignFeed from twitch.feeds import DropCampaignFeed
from twitch.feeds import GameAtomFeed
from twitch.feeds import GameCampaignAtomFeed
from twitch.feeds import GameCampaignDiscordFeed
from twitch.feeds import GameCampaignFeed
from twitch.feeds import GameDiscordFeed
from twitch.feeds import GameFeed from twitch.feeds import GameFeed
from twitch.feeds import OrganizationAtomFeed
from twitch.feeds import OrganizationDiscordFeed
from twitch.feeds import OrganizationRSSFeed
from twitch.feeds import RewardCampaignAtomFeed
from twitch.feeds import RewardCampaignDiscordFeed
from twitch.feeds import RewardCampaignFeed
if TYPE_CHECKING: if TYPE_CHECKING:
from django.urls.resolvers import URLPattern from django.urls.resolvers import URLPattern
@ -15,21 +33,21 @@ app_name = "core"
urlpatterns: list[URLPattern | URLResolver] = [ urlpatterns: list[URLPattern | URLResolver] = [
# / # /
path("", views.dashboard, name="dashboard"), path("", dashboard, name="dashboard"),
# /search/ # /search/
path("search/", views.search_view, name="search"), path("search/", search_view, name="search"),
# /debug/ # /debug/
path("debug/", views.debug_view, name="debug"), path("debug/", debug_view, name="debug"),
# /datasets/ # /datasets/
path("datasets/", views.dataset_backups_view, name="dataset_backups"), path("datasets/", dataset_backups_view, name="dataset_backups"),
# /datasets/download/<relative_path>/ # /datasets/download/<relative_path>/
path( path(
"datasets/download/<path:relative_path>/", "datasets/download/<path:relative_path>/",
views.dataset_backup_download_view, dataset_backup_download_view,
name="dataset_backup_download", name="dataset_backup_download",
), ),
# /docs/rss/ # /docs/rss/
path("docs/rss/", views.docs_rss_view, name="docs_rss"), path("docs/rss/", docs_rss_view, name="docs_rss"),
# RSS feeds # RSS feeds
# /rss/campaigns/ - all active campaigns # /rss/campaigns/ - all active campaigns
path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"), path("rss/campaigns/", DropCampaignFeed(), name="campaign_feed"),
@ -38,59 +56,59 @@ urlpatterns: list[URLPattern | URLResolver] = [
# /rss/games/<twitch_id>/campaigns/ - active campaigns for a specific game # /rss/games/<twitch_id>/campaigns/ - active campaigns for a specific game
path( path(
"rss/games/<str:twitch_id>/campaigns/", "rss/games/<str:twitch_id>/campaigns/",
views.GameCampaignFeed(), GameCampaignFeed(),
name="game_campaign_feed", name="game_campaign_feed",
), ),
# /rss/organizations/ - newly added organizations # /rss/organizations/ - newly added organizations
path( path(
"rss/organizations/", "rss/organizations/",
views.OrganizationRSSFeed(), OrganizationRSSFeed(),
name="organization_feed", name="organization_feed",
), ),
# /rss/reward-campaigns/ - all active reward campaigns # /rss/reward-campaigns/ - all active reward campaigns
path( path(
"rss/reward-campaigns/", "rss/reward-campaigns/",
views.RewardCampaignFeed(), RewardCampaignFeed(),
name="reward_campaign_feed", name="reward_campaign_feed",
), ),
# Atom feeds (added alongside RSS to preserve backward compatibility) # Atom feeds (added alongside RSS to preserve backward compatibility)
path("atom/campaigns/", views.DropCampaignAtomFeed(), name="campaign_feed_atom"), path("atom/campaigns/", DropCampaignAtomFeed(), name="campaign_feed_atom"),
path("atom/games/", views.GameAtomFeed(), name="game_feed_atom"), path("atom/games/", GameAtomFeed(), name="game_feed_atom"),
path( path(
"atom/games/<str:twitch_id>/campaigns/", "atom/games/<str:twitch_id>/campaigns/",
views.GameCampaignAtomFeed(), view=GameCampaignAtomFeed(),
name="game_campaign_feed_atom", name="game_campaign_feed_atom",
), ),
path( path(
"atom/organizations/", "atom/organizations/",
views.OrganizationAtomFeed(), OrganizationAtomFeed(),
name="organization_feed_atom", name="organization_feed_atom",
), ),
path( path(
"atom/reward-campaigns/", "atom/reward-campaigns/",
views.RewardCampaignAtomFeed(), RewardCampaignAtomFeed(),
name="reward_campaign_feed_atom", name="reward_campaign_feed_atom",
), ),
# Discord feeds (Atom feeds with Discord relative timestamps) # Discord feeds (Atom feeds with Discord relative timestamps)
path( path(
"discord/campaigns/", "discord/campaigns/",
views.DropCampaignDiscordFeed(), DropCampaignDiscordFeed(),
name="campaign_feed_discord", name="campaign_feed_discord",
), ),
path("discord/games/", views.GameDiscordFeed(), name="game_feed_discord"), path("discord/games/", GameDiscordFeed(), name="game_feed_discord"),
path( path(
"discord/games/<str:twitch_id>/campaigns/", "discord/games/<str:twitch_id>/campaigns/",
views.GameCampaignDiscordFeed(), GameCampaignDiscordFeed(),
name="game_campaign_feed_discord", name="game_campaign_feed_discord",
), ),
path( path(
"discord/organizations/", "discord/organizations/",
views.OrganizationDiscordFeed(), OrganizationDiscordFeed(),
name="organization_feed_discord", name="organization_feed_discord",
), ),
path( path(
"discord/reward-campaigns/", "discord/reward-campaigns/",
views.RewardCampaignDiscordFeed(), RewardCampaignDiscordFeed(),
name="reward_campaign_feed_discord", name="reward_campaign_feed_discord",
), ),
] ]

View file

@ -3,7 +3,6 @@ import json
import logging import logging
import operator import operator
from collections import OrderedDict from collections import OrderedDict
from copy import copy
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Any from typing import Any
@ -27,21 +26,6 @@ from django.utils import timezone
from kick.models import KickChannel from kick.models import KickChannel
from kick.models import KickDropCampaign from kick.models import KickDropCampaign
from twitch.feeds import DropCampaignAtomFeed
from twitch.feeds import DropCampaignDiscordFeed
from twitch.feeds import DropCampaignFeed
from twitch.feeds import GameAtomFeed
from twitch.feeds import GameCampaignAtomFeed
from twitch.feeds import GameCampaignDiscordFeed
from twitch.feeds import GameCampaignFeed
from twitch.feeds import GameDiscordFeed
from twitch.feeds import GameFeed
from twitch.feeds import OrganizationAtomFeed
from twitch.feeds import OrganizationDiscordFeed
from twitch.feeds import OrganizationRSSFeed
from twitch.feeds import RewardCampaignAtomFeed
from twitch.feeds import RewardCampaignDiscordFeed
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
from twitch.models import ChatBadgeSet from twitch.models import ChatBadgeSet
@ -53,13 +37,11 @@ from twitch.models import RewardCampaign
from twitch.models import TimeBasedDrop from twitch.models import TimeBasedDrop
if TYPE_CHECKING: if TYPE_CHECKING:
from collections.abc import Callable
from os import stat_result from os import stat_result
from pathlib import Path from pathlib import Path
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpRequest from django.http import HttpRequest
from django.http.request import QueryDict
logger: logging.Logger = logging.getLogger("ttvdrops.views") logger: logging.Logger = logging.getLogger("ttvdrops.views")
@ -279,178 +261,33 @@ def sitemap_view(request: HttpRequest) -> HttpResponse: # noqa: PLR0915
# MARK: /docs/rss/ # MARK: /docs/rss/
def docs_rss_view(request: HttpRequest) -> HttpResponse: def docs_rss_view(request: HttpRequest) -> HttpResponse:
"""View for /docs/rss that lists all available RSS feeds. """View for /docs/rss that lists all available feeds and explains how to use them.
Args: Args:
request: The HTTP request object. request: The HTTP request object.
Returns: Returns:
Rendered HTML response with list of RSS feeds. HttpResponse: The rendered documentation page.
""" """
now: datetime.datetime = timezone.now()
def absolute(path: str) -> str: sample_game: Game | None = (
try: Game.objects
return request.build_absolute_uri(path) .filter(drop_campaigns__start_at__lte=now, drop_campaigns__end_at__gte=now)
except Exception: .distinct()
logger.exception("Failed to build absolute URL for %s", path) .first()
return path )
def _pretty_example(xml_str: str, max_items: int = 1) -> str:
try:
trimmed: str = xml_str.strip()
first_item: int = trimmed.find("<item")
if first_item != -1 and max_items == 1:
second_item: int = trimmed.find("<item", first_item + 5)
if second_item != -1:
end_channel: int = trimmed.find("</channel>", second_item)
if end_channel != -1:
trimmed = trimmed[:second_item] + trimmed[end_channel:]
formatted: str = trimmed.replace("><", ">\n<")
return "\n".join(line for line in formatted.splitlines() if line.strip())
except Exception:
logger.exception("Failed to pretty-print RSS example")
return xml_str
def render_feed(feed_view: Callable[..., HttpResponse], *args: object) -> str:
try:
limited_request: HttpRequest = copy(request)
# Add limit=1 to GET parameters
get_data: QueryDict = request.GET.copy()
get_data["limit"] = "1"
limited_request.GET = get_data
response: HttpResponse = feed_view(limited_request, *args)
return _pretty_example(response.content.decode("utf-8"))
except Exception:
logger.exception(
"Failed to render %s for RSS docs",
feed_view.__class__.__name__,
)
return ""
show_atom: bool = bool(request.GET.get("show_atom"))
feeds: list[dict[str, str]] = [
{
"title": "All Organizations",
"description": "Latest organizations added to TTVDrops",
"url": absolute(reverse("core:organization_feed")),
"atom_url": absolute(reverse("core:organization_feed_atom")),
"discord_url": absolute(reverse("core:organization_feed_discord")),
"example_xml": render_feed(OrganizationRSSFeed()),
"example_xml_atom": render_feed(OrganizationAtomFeed())
if show_atom
else "",
"example_xml_discord": render_feed(OrganizationDiscordFeed())
if show_atom
else "",
},
{
"title": "All Games",
"description": "Latest games added to TTVDrops",
"url": absolute(reverse("core:game_feed")),
"atom_url": absolute(reverse("core:game_feed_atom")),
"discord_url": absolute(reverse("core:game_feed_discord")),
"example_xml": render_feed(GameFeed()),
"example_xml_atom": render_feed(GameAtomFeed()) if show_atom else "",
"example_xml_discord": render_feed(GameDiscordFeed()) if show_atom else "",
},
{
"title": "All Drop Campaigns",
"description": "Latest drop campaigns across all games",
"url": absolute(reverse("core:campaign_feed")),
"atom_url": absolute(reverse("core:campaign_feed_atom")),
"discord_url": absolute(reverse("core:campaign_feed_discord")),
"example_xml": render_feed(DropCampaignFeed()),
"example_xml_atom": render_feed(DropCampaignAtomFeed())
if show_atom
else "",
"example_xml_discord": render_feed(DropCampaignDiscordFeed())
if show_atom
else "",
},
{
"title": "All Reward Campaigns",
"description": "Latest reward campaigns (Quest rewards) on Twitch",
"url": absolute(reverse("core:reward_campaign_feed")),
"atom_url": absolute(reverse("core:reward_campaign_feed_atom")),
"discord_url": absolute(reverse("core:reward_campaign_feed_discord")),
"example_xml": render_feed(RewardCampaignFeed()),
"example_xml_atom": render_feed(RewardCampaignAtomFeed())
if show_atom
else "",
"example_xml_discord": render_feed(RewardCampaignDiscordFeed())
if show_atom
else "",
},
]
sample_game: Game | None = Game.objects.order_by("-added_at").first()
sample_org: Organization | None = Organization.objects.order_by("-added_at").first()
if sample_org is None and sample_game is not None:
sample_org = sample_game.owners.order_by("-pk").first()
filtered_feeds: list[dict[str, str | bool]] = [
{
"title": "Campaigns for a Single Game",
"description": "Latest drop campaigns for one game.",
"url": (
absolute(
reverse("core:game_campaign_feed", args=[sample_game.twitch_id]),
)
if sample_game
else absolute("/rss/games/<game_id>/campaigns/")
),
"atom_url": (
absolute(
reverse(
"core:game_campaign_feed_atom",
args=[sample_game.twitch_id],
),
)
if sample_game
else absolute("/atom/games/<game_id>/campaigns/")
),
"discord_url": (
absolute(
reverse(
"core:game_campaign_feed_discord",
args=[sample_game.twitch_id],
),
)
if sample_game
else absolute("/discord/games/<game_id>/campaigns/")
),
"has_sample": bool(sample_game),
"example_xml": render_feed(GameCampaignFeed(), sample_game.twitch_id)
if sample_game
else "",
"example_xml_atom": (
render_feed(GameCampaignAtomFeed(), sample_game.twitch_id)
if sample_game and show_atom
else ""
),
"example_xml_discord": (
render_feed(GameCampaignDiscordFeed(), sample_game.twitch_id)
if sample_game and show_atom
else ""
),
},
]
seo_context: dict[str, Any] = _build_seo_context( seo_context: dict[str, Any] = _build_seo_context(
page_title="Twitch RSS Feeds", page_title="Feed Documentation",
page_description="RSS feeds for Twitch drops.", page_description="Documentation for the RSS feeds available on ttvdrops.lovinator.space, including how to use them and what data they contain.",
page_url=request.build_absolute_uri(reverse("core:docs_rss")), page_url=request.build_absolute_uri(reverse("core:docs_rss")),
) )
return render( return render(
request, request,
"twitch/docs_rss.html", "twitch/docs_rss.html",
{ {
"feeds": feeds, "game": sample_game,
"filtered_feeds": filtered_feeds,
"sample_game": sample_game,
"sample_org": sample_org,
**seo_context, **seo_context,
}, },
) )

View file

@ -48,6 +48,7 @@
title="Atom feed for Twitch campaigns">[atom]</a> title="Atom feed for Twitch campaigns">[atom]</a>
<a href="{% url 'core:campaign_feed_discord' %}" <a href="{% url 'core:campaign_feed_discord' %}"
title="Discord feed for Twitch campaigns">[discord]</a> title="Discord feed for Twitch campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
</header> </header>
{% if campaigns_by_game %} {% if campaigns_by_game %}
@ -223,6 +224,7 @@
title="Atom feed for all Kick campaigns">[atom]</a> title="Atom feed for all Kick campaigns">[atom]</a>
<a href="{% url 'kick:campaign_feed_discord' %}" <a href="{% url 'kick:campaign_feed_discord' %}"
title="Discord feed for all Kick campaigns">[discord]</a> title="Discord feed for all Kick campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
</header> </header>
{% if kick_campaigns_by_game %} {% if kick_campaigns_by_game %}

View file

@ -59,6 +59,7 @@
title="Atom feed for {{ campaign.category.name }} campaigns">[atom]</a> title="Atom feed for {{ campaign.category.name }} campaigns">[atom]</a>
<a href="{% url 'kick:game_campaign_feed_discord' campaign.category.kick_id %}" <a href="{% url 'kick:game_campaign_feed_discord' campaign.category.kick_id %}"
title="Discord feed for {{ campaign.category.name }} campaigns">[discord]</a> title="Discord feed for {{ campaign.category.name }} campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
{% endif %} {% endif %}
<p style="margin: 0.25rem 0; color: #666;"> <p style="margin: 0.25rem 0; color: #666;">

View file

@ -26,6 +26,7 @@
title="Atom feed for all campaigns">[atom]</a> title="Atom feed for all campaigns">[atom]</a>
<a href="{% url 'kick:campaign_feed_discord' %}" <a href="{% url 'kick:campaign_feed_discord' %}"
title="Discord feed for all campaigns">[discord]</a> title="Discord feed for all campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
<form method="get" action="{% url 'kick:campaign_list' %}"> <form method="get" action="{% url 'kick:campaign_list' %}">
<div style="display: flex; <div style="display: flex;

View file

@ -56,6 +56,7 @@
title="Atom feed for {{ category.name }} campaigns">[atom]</a> title="Atom feed for {{ category.name }} campaigns">[atom]</a>
<a href="{% url 'kick:game_campaign_feed_discord' category.kick_id %}" <a href="{% url 'kick:game_campaign_feed_discord' category.kick_id %}"
title="Discord feed for {{ category.name }} campaigns">[discord]</a> title="Discord feed for {{ category.name }} campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
{% if category.kick_url %} {% if category.kick_url %}
<p style="margin: 0.25rem 0;"> <p style="margin: 0.25rem 0;">

View file

@ -25,6 +25,7 @@
title="Atom feed for all games">[atom]</a> title="Atom feed for all games">[atom]</a>
<a href="{% url 'kick:game_feed_discord' %}" <a href="{% url 'kick:game_feed_discord' %}"
title="Discord feed for all games">[discord]</a> title="Discord feed for all games">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
{% if categories %} {% if categories %}
<ul> <ul>

View file

@ -27,6 +27,7 @@
title="Atom feed for all campaigns">[atom]</a> title="Atom feed for all campaigns">[atom]</a>
<a href="{% url 'kick:campaign_feed_discord' %}" <a href="{% url 'kick:campaign_feed_discord' %}"
title="Discord feed for all campaigns">[discord]</a> title="Discord feed for all campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
<hr /> <hr />
{% if active_campaigns %} {% if active_campaigns %}

View file

@ -26,6 +26,7 @@
title="Atom feed for all organizations">[atom]</a> title="Atom feed for all organizations">[atom]</a>
<a href="{% url 'kick:organization_feed_discord' %}" <a href="{% url 'kick:organization_feed_discord' %}"
title="Discord feed for all organizations">[discord]</a> title="Discord feed for all organizations">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
{% if orgs %} {% if orgs %}
<ul> <ul>

View file

@ -104,6 +104,7 @@
title="Atom feed for {{ campaign.game.display_name }} campaigns">[atom]</a> title="Atom feed for {{ campaign.game.display_name }} campaigns">[atom]</a>
<a href="{% url 'core:game_campaign_feed_discord' campaign.game.twitch_id %}" <a href="{% url 'core:game_campaign_feed_discord' campaign.game.twitch_id %}"
title="Discord feed for {{ campaign.game.display_name }} campaigns">[discord]</a> title="Discord feed for {{ campaign.game.display_name }} campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>

View file

@ -30,6 +30,7 @@
title="Atom feed for all campaigns">[atom]</a> title="Atom feed for all campaigns">[atom]</a>
<a href="{% url 'core:campaign_feed_discord' %}" <a href="{% url 'core:campaign_feed_discord' %}"
title="Discord feed for all campaigns">[discord]</a> title="Discord feed for all campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</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 %}"

View file

@ -39,6 +39,7 @@
title="Atom feed for campaigns">[atom]</a> title="Atom feed for campaigns">[atom]</a>
<a href="{% url 'core:campaign_feed_discord' %}" <a href="{% url 'core:campaign_feed_discord' %}"
title="Discord feed for campaigns">[discord]</a> title="Discord feed for campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
<hr /> <hr />
{% if campaigns_by_game %} {% if campaigns_by_game %}

View file

@ -6,99 +6,148 @@
{% block content %} {% block content %}
<main> <main>
<h1>RSS Feeds Documentation</h1> <h1>RSS Feeds Documentation</h1>
<p>This page lists all available RSS feeds for TTVDrops.</p>
<p> <p>
Atom feeds are also available for the same resources under the You have three types of feeds available for Twitch drops data: RSS, Atom, and Discord.
<code>/atom/</code> endpoints. RSS and Atom feeds are similar and can be used in any RSS reader application.
The main difference is that Atom feeds include additional metadata and support for more complex content, while RSS feeds are more widely supported by older applications.
</p> </p>
<p> <p>
Discord feeds are available under the <code>/discord/</code> endpoints. These are Atom feeds Discord feeds are available under the <code>/discord/</code> endpoints. These are Atom feeds
that include Discord relative timestamps (e.g., <code>&lt;t:1773450272:R&gt;</code>) for dates, that include Discord relative timestamps (e.g., <code>&lt;t:1773450272:R&gt;</code>) for dates,
making them ideal for Discord bots and integrations. making them ideal for Discord bots and integrations. Future enhancements may include Discord-specific formatting or content.
</p> </p>
<section> <section>
<h2>Global RSS Feeds</h2> <h2>Global RSS Feeds</h2>
<p>These feeds contain all items across the entire site:</p> <table>
<ul> <thead>
{% for feed in feeds %} <tr>
<li> <th>Description</th>
<h3>{{ feed.title }}</h3> <th>RSS</th>
<p>{{ feed.description }}</p> <th>Atom</th>
<p> <th>Discord</th>
<a href="{{ feed.url }}">Subscribe to {{ feed.title }} RSS Feed</a> </tr>
{% if feed.atom_url %} </thead>
&nbsp;|&nbsp; <tbody>
<a href="{{ feed.atom_url }}">Subscribe to {{ feed.title }} Atom Feed</a> <tr>
{% endif %} <td>New Twitch games</td>
{% if feed.discord_url %} <td>
&nbsp;|&nbsp; <a href="https://ttvdrops.lovinator.space/rss/games/">https://ttvdrops.lovinator.space/rss/games/</a>
<a href="{{ feed.discord_url }}">Subscribe to {{ feed.title }} Discord Feed</a> </td>
{% endif %} <td>
</p> <a href="https://ttvdrops.lovinator.space/atom/games/">https://ttvdrops.lovinator.space/atom/games/</a>
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre> </td>
{% if feed.example_xml_atom %} <td>
<h4>Atom example</h4> <a href="https://ttvdrops.lovinator.space/discord/games/">https://ttvdrops.lovinator.space/discord/games/</a>
<pre><code class="language-xml">{{ feed.example_xml_atom|escape }}</code></pre> </td>
{% endif %} </tr>
{% if feed.example_xml_discord %} <tr>
<h4>Discord example</h4> <td>Latest Twitch drop campaigns</td>
<pre><code class="language-xml">{{ feed.example_xml_discord|escape }}</code></pre> <td>
{% endif %} <a href="https://ttvdrops.lovinator.space/rss/campaigns/">https://ttvdrops.lovinator.space/rss/campaigns/</a>
</li> </td>
{% endfor %} <td>
</ul> <a href="https://ttvdrops.lovinator.space/atom/campaigns/">https://ttvdrops.lovinator.space/atom/campaigns/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/discord/campaigns/">https://ttvdrops.lovinator.space/discord/campaigns/</a>
</td>
</tr>
<tr>
<td>Latest Twitch organizations</td>
<td>
<a href="https://ttvdrops.lovinator.space/rss/organizations/">https://ttvdrops.lovinator.space/rss/organizations/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/atom/organizations/">https://ttvdrops.lovinator.space/atom/organizations/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/discord/organizations/">https://ttvdrops.lovinator.space/discord/organizations/</a>
</td>
</tr>
<tr>
<td>Latest Twitch reward campaigns</td>
<td>
<a href="https://ttvdrops.lovinator.space/rss/reward-campaigns/">https://ttvdrops.lovinator.space/rss/reward-campaigns/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/atom/reward-campaigns/">https://ttvdrops.lovinator.space/atom/reward-campaigns/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/discord/reward-campaigns/">https://ttvdrops.lovinator.space/discord/reward-campaigns/</a>
</td>
</tr>
<tr>
<td>Latest Kick campaigns</td>
<td>
<a href="https://ttvdrops.lovinator.space/kick/rss/campaigns/">https://ttvdrops.lovinator.space/kick/rss/campaigns/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/kick/atom/campaigns/">https://ttvdrops.lovinator.space/kick/atom/campaigns/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/discord/campaigns/">https://ttvdrops.lovinator.space/discord/campaigns/</a>
</td>
</tr>
<tr>
<td>Latest Kick games</td>
<td>
<a href="https://ttvdrops.lovinator.space/kick/rss/games/">https://ttvdrops.lovinator.space/kick/rss/games/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/kick/atom/games/">https://ttvdrops.lovinator.space/kick/atom/games/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/discord/games/">https://ttvdrops.lovinator.space/discord/games/</a>
</td>
</tr>
<tr>
<td>Latest Kick organizations</td>
<td>
<a href="https://ttvdrops.lovinator.space/kick/rss/organizations/">https://ttvdrops.lovinator.space/kick/rss/organizations/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/kick/atom/organizations/">https://ttvdrops.lovinator.space/kick/atom/organizations/</a>
</td>
<td>
<a href="https://ttvdrops.lovinator.space/discord/organizations/">https://ttvdrops.lovinator.space/discord/organizations/</a>
</td>
</tr>
</tbody>
</table>
</section> </section>
<section> <section>
<h2>Filtered RSS Feeds</h2> <h2>Filtered RSS Feeds</h2>
<p> <p>You can subscribe to RSS feeds scoped to a specific game.</p>
You can subscribe to RSS feeds scoped to a specific game or organization. When available, links below point to live examples; otherwise use the endpoint template. <table>
</p> <thead>
<ul> <tr>
{% for feed in filtered_feeds %} <th>Game</th>
<li> <th>RSS</th>
<h3>{{ feed.title }}</h3> <th>Atom</th>
<p>{{ feed.description }}</p> <th>Discord</th>
<p> </tr>
Endpoint: <code>{{ feed.url }}</code> </thead>
{% if feed.atom_url %}&nbsp;|&nbsp; Atom: <code>{{ feed.atom_url }}</code>{% endif %} <tbody>
{% if feed.discord_url %}&nbsp;|&nbsp; Discord: <code>{{ feed.discord_url }}</code>{% endif %} <tr>
</p> <td>{{ game.display_name }}</td>
{% if feed.has_sample %} <td>
<p> <a href="{% url 'core:game_campaign_feed' game.twitch_id %}">
<a href="{{ feed.url }}">View a live example</a> https://ttvdrops.lovinator.space/rss/games/{{ game.twitch_id }}/
{% if feed.atom_url %} </a>
&nbsp;|&nbsp; </td>
<a href="{{ feed.atom_url }}">View Atom example</a> <td>
{% endif %} <a href="{% url 'core:game_campaign_feed_atom' game.twitch_id %}">
{% if feed.discord_url %} https://ttvdrops.lovinator.space/atom/games/{{ game.twitch_id }}/
&nbsp;|&nbsp; </a>
<a href="{{ feed.discord_url }}">View Discord example</a> </td>
{% endif %} <td>
</p> <a href="{% url 'core:game_campaign_feed_discord' game.twitch_id %}">
{% endif %} https://ttvdrops.lovinator.space/discord/games/{{ game.twitch_id }}/
<pre><code class="language-xml">{% if feed.example_xml %}{{ feed.example_xml|escape }}{% else %}No example XML available yet.{% endif %}</code></pre> </a>
{% if feed.example_xml_atom %} </td>
<h4>Atom example</h4> </tr>
<pre><code class="language-xml">{{ feed.example_xml_atom|escape }}</code></pre> </tbody>
{% endif %} </table>
{% if feed.example_xml_discord %}
<h4>Discord example</h4>
<pre><code class="language-xml">{{ feed.example_xml_discord|escape }}</code></pre>
{% endif %}
</li>
{% endfor %}
</ul>
</section>
<section>
<h2>How to Use RSS Feeds</h2>
<p>
RSS feeds allow you to stay updated with new content. You can use any RSS reader application to subscribe to these feeds.
</p>
<ul>
<li>Copy the feed URL</li>
<li>Paste it into your favorite RSS reader (Feedly, Inoreader, NetNewsWire, etc.)</li>
<li>Get automatic updates when new content is added</li>
</ul>
</section> </section>
</main> </main>
{% endblock content %} {% endblock content %}

View file

@ -63,6 +63,7 @@
title="Atom feed for {{ game.display_name }} campaigns">[atom]</a> title="Atom feed for {{ game.display_name }} campaigns">[atom]</a>
<a href="{% url 'core:game_campaign_feed_discord' game.twitch_id %}" <a href="{% url 'core:game_campaign_feed_discord' game.twitch_id %}"
title="Discord feed for {{ game.display_name }} campaigns">[discord]</a> title="Discord feed for {{ game.display_name }} campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
</div> </div>
</div> </div>

View file

@ -28,6 +28,7 @@
title="Atom feed for all games">[atom]</a> title="Atom feed for all games">[atom]</a>
<a href="{% url 'core:game_feed_discord' %}" <a href="{% url 'core:game_feed_discord' %}"
title="Discord feed for all games">[discord]</a> title="Discord feed for all games">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</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' %}"

View file

@ -26,6 +26,7 @@
title="Atom feed for all games">[atom]</a> title="Atom feed for all games">[atom]</a>
<a href="{% url 'core:game_feed_discord' %}" <a href="{% url 'core:game_feed_discord' %}"
title="Discord feed for all games">[discord]</a> title="Discord feed for all games">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</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' %}"

View file

@ -11,6 +11,7 @@
title="Atom feed for all organizations">[atom]</a> title="Atom feed for all organizations">[atom]</a>
<a href="{% url 'core:organization_feed_discord' %}" <a href="{% url 'core:organization_feed_discord' %}"
title="Discord feed for all organizations">[discord]</a> title="Discord feed for all organizations">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
<a href="{% url 'twitch:export_organizations_csv' %}" <a href="{% url 'twitch:export_organizations_csv' %}"
title="Export all organizations as CSV">[csv]</a> title="Export all organizations as CSV">[csv]</a>
<a href="{% url 'twitch:export_organizations_json' %}" <a href="{% url 'twitch:export_organizations_json' %}"

View file

@ -37,11 +37,12 @@
<div style="margin-bottom: 1rem;"> <div style="margin-bottom: 1rem;">
<a href="{% url 'core:reward_campaign_feed' %}" <a href="{% url 'core: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]</a>
<a href="{% url 'core:reward_campaign_feed_atom' %}" <a href="{% url 'core:reward_campaign_feed_atom' %}"
title="Atom feed for all reward campaigns">[atom]</a> title="Atom feed for all reward campaigns">[atom]</a>
<a href="{% url 'core:reward_campaign_feed_discord' %}" <a href="{% url 'core:reward_campaign_feed_discord' %}"
title="Discord feed for all reward campaigns">[discord]</a> title="Discord feed for all reward campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</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 %}

View file

@ -27,6 +27,7 @@
title="Atom feed for all reward campaigns">[atom]</a> title="Atom feed for all reward campaigns">[atom]</a>
<a href="{% url 'core:reward_campaign_feed_discord' %}" <a href="{% url 'core:reward_campaign_feed_discord' %}"
title="Discord feed for all reward campaigns">[discord]</a> title="Discord feed for all reward campaigns">[discord]</a>
<a href="{% url 'core:docs_rss' %}" title="RSS feed documentation">[explain]</a>
</div> </div>
<p>This is an archive of old Twitch reward campaigns because we do not monitor them.</p> <p>This is an archive of old Twitch reward campaigns because we do not monitor them.</p>
<p> <p>