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('

About this drop

', 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."""