diff --git a/twitch/feeds.py b/twitch/feeds.py
index a1670e4..eb088ca 100644
--- a/twitch/feeds.py
+++ b/twitch/feeds.py
@@ -1,10 +1,16 @@
from __future__ import annotations
+from typing import TYPE_CHECKING
+
from django.contrib.syndication.views import Feed
from django.urls import reverse
+from django.utils.html import format_html
from twitch.models import DropCampaign, Game, Organization
+if TYPE_CHECKING:
+ import datetime
+
class OrganizationFeed(Feed):
"""RSS feed for latest organizations."""
@@ -57,22 +63,86 @@ class GameFeed(Feed):
class DropCampaignFeed(Feed):
"""RSS feed for latest drop campaigns."""
- title = "TTVDrops Drop Campaigns"
+ title = "Twitch Drop Campaigns"
link = "/campaigns/"
- description = "Latest drop campaigns on TTVDrops"
+ description = "Latest Twitch drop campaigns"
+ feed_url = "/rss/campaigns/"
+ feed_copyright = "Information wants to be free."
def items(self) -> list[DropCampaign]:
"""Return the latest 100 drop campaigns."""
- return list(DropCampaign.objects.order_by("-added_at")[:100])
+ return list(DropCampaign.objects.select_related("game").order_by("-added_at")[:100])
def item_title(self, item: DropCampaign) -> str:
"""Return the campaign name as the item title."""
- return item.name
+ return f"{item.game.display_name}: {item.clean_name}"
def item_description(self, item: DropCampaign) -> str:
"""Return a description of the campaign."""
- return item.description or f"Campaign {item.name}"
+ description = ""
+
+ # Include the campaign image if available
+ if item.image_url:
+ description += format_html(
+ '
',
+ item.image_url,
+ item.name,
+ )
+
+ # Add the campaign description text
+ description += format_html("
{}
", item.description) if item.description else "" + + # Add start and end dates for clarity + if item.start_at: + description += f"Starts: {item.start_at.strftime('%Y-%m-%d %H:%M %Z')}
" + if item.end_at: + description += f"Ends: {item.end_at.strftime('%Y-%m-%d %H:%M %Z')}
" + + # Add a clear link to the campaign details page + if item.details_url: + description += format_html('', item.details_url) + + return f"{description}" def item_link(self, item: DropCampaign) -> str: """Return the link to the campaign detail.""" return reverse("twitch:campaign_detail", args=[item.pk]) + + def item_pubdate(self, item: DropCampaign) -> datetime.datetime: + """Returns the publication date to the feed item.""" + return item.start_at + + def item_updateddate(self, item: DropCampaign) -> datetime.datetime: + """Returns the campaign's last update time.""" + return item.updated_at + + def item_categories(self, item: DropCampaign) -> tuple[str, ...]: + """Returns the associated game's name as a category.""" + if item.game: + return ( + "twitch", + item.game.get_game_name, + ) + return () + + def item_guid(self, item: DropCampaign) -> str: + """Return a unique identifier for each campaign.""" + return item.id + + def item_author_name(self, item: DropCampaign) -> str: + """Return the author name for the campaign, typically the game name.""" + if item.game and item.game.display_name: + return item.game.display_name + return "Twitch" + + def item_enclosure_url(self, item: DropCampaign) -> str: + """Returns the URL of the campaign image for enclosure.""" + return item.image_url + + def item_enclosure_length(self, item: DropCampaign) -> int: # noqa: ARG002 + """Returns the length of the enclosure. Currently not tracked, so return 0.""" + return 0 + + def item_enclosure_mime_type(self, item: DropCampaign) -> str: # noqa: ARG002 + """Returns the MIME type of the enclosure.""" + return "image/jpeg" diff --git a/twitch/models.py b/twitch/models.py index bfd3ebc..47cf818 100644 --- a/twitch/models.py +++ b/twitch/models.py @@ -161,6 +161,17 @@ class Game(models.Model): ) return urlunsplit((parts.scheme, parts.netloc, path, "", "")) + @property + def get_game_name(self) -> str: + """Return the best available name for the game.""" + if self.display_name: + return self.display_name + if self.name: + return self.name + if self.slug: + return self.slug + return self.id + class DropCampaign(models.Model): """Represents a Twitch drop campaign."""