Add site-wide rewards page with RSS/Atom/Discord feeds and tests
All checks were successful
Deploy to Server / deploy (push) Successful in 26s
All checks were successful
Deploy to Server / deploy (push) Successful in 26s
This commit is contained in:
parent
decc8ae5ab
commit
9de0f52af8
8 changed files with 615 additions and 3 deletions
236
twitch/feeds.py
236
twitch/feeds.py
|
|
@ -1987,3 +1987,239 @@ class GameRewardCampaignDiscordFeed(TTVDropsAtomBaseFeed, GameRewardCampaignFeed
|
|||
def feed_url(self, obj: Game) -> str:
|
||||
"""Return the URL to the Discord feed itself."""
|
||||
return reverse("core:game_reward_feed_discord", args=[obj.twitch_id])
|
||||
|
||||
|
||||
# MARK: /rss/sitewide/rewards/
|
||||
class SitewideRewardCampaignFeed(TTVDropsBaseFeed):
|
||||
"""RSS feed for site-wide reward campaigns (no specific game)."""
|
||||
|
||||
title: str = "TTVDrops: Site-wide Rewards"
|
||||
link: str = "/twitch/sitewide/"
|
||||
description: str = "Twitch reward campaigns available site-wide"
|
||||
|
||||
item_guid_is_permalink = True
|
||||
_limit: int | None = None
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
request: HttpRequest,
|
||||
*args: str | int,
|
||||
**kwargs: str | int,
|
||||
) -> HttpResponse:
|
||||
"""Override to capture limit parameter from request.
|
||||
|
||||
Args:
|
||||
request (HttpRequest): The incoming HTTP request, potentially containing a 'limit' query parameter.
|
||||
*args: Additional positional arguments.
|
||||
**kwargs: Additional keyword arguments.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The HTTP response generated by the parent Feed class after processing the request.
|
||||
"""
|
||||
if request.GET.get("limit"):
|
||||
try:
|
||||
self._limit = int(request.GET.get("limit", 200))
|
||||
except ValueError, TypeError:
|
||||
self._limit = None
|
||||
return super().__call__(request, *args, **kwargs)
|
||||
|
||||
def items(self) -> list[RewardCampaign]:
|
||||
"""Return site-wide reward campaigns (default 200, or limited by ?limit query param)."""
|
||||
limit: int = self._limit if self._limit is not None else 200
|
||||
queryset: QuerySet[RewardCampaign] = _active_reward_campaigns(
|
||||
RewardCampaign.objects
|
||||
.filter(is_sitewide=True)
|
||||
.select_related("game")
|
||||
.order_by("-starts_at"),
|
||||
)
|
||||
return list(queryset[:limit])
|
||||
|
||||
def item_title(self, item: RewardCampaign) -> SafeText:
|
||||
"""Return the reward campaign name as the item title."""
|
||||
if item.brand:
|
||||
return SafeText(f"{item.brand}: {item.name}")
|
||||
return SafeText(item.name)
|
||||
|
||||
def item_description(self, item: RewardCampaign) -> SafeText:
|
||||
"""Return a description of the reward campaign."""
|
||||
parts: list = []
|
||||
|
||||
if item.summary:
|
||||
parts.append(format_html("<p>{}</p>", item.summary))
|
||||
|
||||
if item.starts_at or item.ends_at:
|
||||
start_part = (
|
||||
format_html(
|
||||
"Starts: {} ({})",
|
||||
item.starts_at.strftime("%Y-%m-%d %H:%M %Z"),
|
||||
naturaltime(item.starts_at),
|
||||
)
|
||||
if item.starts_at
|
||||
else ""
|
||||
)
|
||||
end_part = (
|
||||
format_html(
|
||||
"Ends: {} ({})",
|
||||
item.ends_at.strftime("%Y-%m-%d %H:%M %Z"),
|
||||
naturaltime(item.ends_at),
|
||||
)
|
||||
if item.ends_at
|
||||
else ""
|
||||
)
|
||||
if start_part and end_part:
|
||||
parts.append(format_html("<p>{}<br />{}</p>", start_part, end_part))
|
||||
elif start_part:
|
||||
parts.append(format_html("<p>{}</p>", start_part))
|
||||
elif end_part:
|
||||
parts.append(format_html("<p>{}</p>", end_part))
|
||||
|
||||
parts.append(
|
||||
SafeText("<p><strong>Site-wide reward campaign</strong></p>"),
|
||||
)
|
||||
|
||||
if item.about_url:
|
||||
parts.append(
|
||||
format_html('<p><a href="{}">Learn more</a></p>', item.about_url),
|
||||
)
|
||||
|
||||
if item.external_url:
|
||||
parts.append(
|
||||
format_html(
|
||||
'<p><a href="{}">Redeem reward</a></p>',
|
||||
item.external_url,
|
||||
),
|
||||
)
|
||||
|
||||
return SafeText("".join(str(p) for p in parts))
|
||||
|
||||
def item_link(self, item: RewardCampaign) -> str:
|
||||
"""Return the link to the reward campaign detail."""
|
||||
return reverse("twitch:reward_campaign_detail", args=[item.twitch_id])
|
||||
|
||||
def item_pubdate(self, item: RewardCampaign) -> datetime.datetime:
|
||||
"""Returns the publication date to the feed item."""
|
||||
if item.starts_at:
|
||||
return item.starts_at
|
||||
if item.added_at:
|
||||
return item.added_at
|
||||
return timezone.now()
|
||||
|
||||
def item_updateddate(self, item: RewardCampaign) -> datetime.datetime:
|
||||
"""Returns the reward campaign's last update time."""
|
||||
return item.updated_at
|
||||
|
||||
def item_categories(self, item: RewardCampaign) -> tuple[str, ...]:
|
||||
"""Returns categories for the feed item."""
|
||||
categories: list[str] = ["twitch", "rewards", "quests", "sitewide"]
|
||||
if item.brand:
|
||||
categories.append(item.brand)
|
||||
return tuple(categories)
|
||||
|
||||
def item_guid(self, item: RewardCampaign) -> str:
|
||||
"""Return a unique identifier for each reward campaign."""
|
||||
return self._absolute_url(
|
||||
reverse("twitch:reward_campaign_detail", args=[item.twitch_id]),
|
||||
)
|
||||
|
||||
def item_author_name(self, item: RewardCampaign) -> str:
|
||||
"""Return the author name for the reward campaign."""
|
||||
return item.brand or "Twitch"
|
||||
|
||||
def item_enclosures(self, item: RewardCampaign) -> list[feedgenerator.Enclosure]:
|
||||
"""Return a list of enclosures for the reward campaign, if available."""
|
||||
image_url: str = getattr(item, "image_best_url", "")
|
||||
if image_url:
|
||||
try:
|
||||
size: int | None = getattr(item, "image_size_bytes", None)
|
||||
length: int = int(size) if size is not None else 0
|
||||
except TypeError, ValueError:
|
||||
length = 0
|
||||
|
||||
if not length:
|
||||
return []
|
||||
|
||||
mime: str = getattr(item, "image_mime_type", "")
|
||||
mime_type: str = mime or "image/jpeg"
|
||||
|
||||
return [
|
||||
feedgenerator.Enclosure(
|
||||
self._absolute_url(image_url),
|
||||
str(length),
|
||||
mime_type,
|
||||
),
|
||||
]
|
||||
return []
|
||||
|
||||
def feed_url(self) -> str:
|
||||
"""Return the URL to the RSS feed itself."""
|
||||
return reverse("core:sitewide_reward_feed")
|
||||
|
||||
|
||||
class SitewideRewardCampaignAtomFeed(TTVDropsAtomBaseFeed, SitewideRewardCampaignFeed):
|
||||
"""Atom feed for site-wide reward campaigns."""
|
||||
|
||||
def feed_url(self) -> str:
|
||||
"""Return the URL to the Atom feed itself."""
|
||||
return reverse("core:sitewide_reward_feed_atom")
|
||||
|
||||
|
||||
class SitewideRewardCampaignDiscordFeed(
|
||||
TTVDropsAtomBaseFeed, SitewideRewardCampaignFeed
|
||||
):
|
||||
"""Discord feed for site-wide reward campaigns with Discord relative timestamps."""
|
||||
|
||||
def item_description(self, item: RewardCampaign) -> SafeText:
|
||||
"""Return a description of the reward campaign with Discord timestamps."""
|
||||
parts: list = []
|
||||
|
||||
if item.summary:
|
||||
parts.append(format_html("<p>{}</p>", item.summary))
|
||||
|
||||
if item.starts_at or item.ends_at:
|
||||
start_part = (
|
||||
format_html(
|
||||
"Starts: {} ({})",
|
||||
item.starts_at.strftime("%Y-%m-%d %H:%M %Z"),
|
||||
discord_timestamp(item.starts_at),
|
||||
)
|
||||
if item.starts_at
|
||||
else ""
|
||||
)
|
||||
end_part = (
|
||||
format_html(
|
||||
"Ends: {} ({})",
|
||||
item.ends_at.strftime("%Y-%m-%d %H:%M %Z"),
|
||||
discord_timestamp(item.ends_at),
|
||||
)
|
||||
if item.ends_at
|
||||
else ""
|
||||
)
|
||||
if start_part and end_part:
|
||||
parts.append(format_html("<p>{}<br />{}</p>", start_part, end_part))
|
||||
elif start_part:
|
||||
parts.append(format_html("<p>{}</p>", start_part))
|
||||
elif end_part:
|
||||
parts.append(format_html("<p>{}</p>", end_part))
|
||||
|
||||
parts.append(
|
||||
SafeText("<p><strong>Site-wide reward campaign</strong></p>"),
|
||||
)
|
||||
|
||||
if item.about_url:
|
||||
parts.append(
|
||||
format_html('<p><a href="{}">Learn more</a></p>', item.about_url),
|
||||
)
|
||||
|
||||
if item.external_url:
|
||||
parts.append(
|
||||
format_html(
|
||||
'<p><a href="{}">Redeem reward</a></p>',
|
||||
item.external_url,
|
||||
),
|
||||
)
|
||||
|
||||
return SafeText("".join(str(p) for p in parts))
|
||||
|
||||
def feed_url(self) -> str:
|
||||
"""Return the URL to the Discord feed itself."""
|
||||
return reverse("core:sitewide_reward_feed_discord")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue