WIP for moving everything into models

This commit is contained in:
2024-09-01 23:08:15 +02:00
parent 8f68b38116
commit 717adb2df9
5 changed files with 210 additions and 284 deletions

View File

@ -1,12 +1,11 @@
from django.contrib import admin from django.contrib import admin
from .models import Benefit, Channel, DropCampaign, Game, Owner, Reward, RewardCampaign, TimeBasedDrop from .models import Benefit, DropCampaign, Game, Owner, Reward, RewardCampaign, TimeBasedDrop
admin.site.register(Game) admin.site.register(Game)
admin.site.register(Owner) admin.site.register(Owner)
admin.site.register(RewardCampaign) admin.site.register(RewardCampaign)
admin.site.register(DropCampaign) admin.site.register(DropCampaign)
admin.site.register(Channel)
admin.site.register(TimeBasedDrop) admin.site.register(TimeBasedDrop)
admin.site.register(Benefit) admin.site.register(Benefit)
admin.site.register(Reward) admin.site.register(Reward)

View File

@ -10,7 +10,7 @@ from platformdirs import user_data_dir
from playwright.async_api import Playwright, async_playwright from playwright.async_api import Playwright, async_playwright
from playwright.async_api._generated import Response from playwright.async_api._generated import Response
from core.models import Benefit, Channel, DropCampaign, Game, Owner, Reward, RewardCampaign, TimeBasedDrop from core.models import Benefit, DropCampaign, Game, Owner, Reward, RewardCampaign, TimeBasedDrop
if TYPE_CHECKING: if TYPE_CHECKING:
from playwright.async_api._generated import BrowserContext, Page from playwright.async_api._generated import BrowserContext, Page
@ -78,197 +78,17 @@ async def add_reward_campaign(campaign: dict | None) -> None:
return return
if "data" in campaign and "rewardCampaignsAvailableToUser" in campaign["data"]: if "data" in campaign and "rewardCampaignsAvailableToUser" in campaign["data"]:
for reward_campaign in campaign["data"]["rewardCampaignsAvailableToUser"]: for reward_campaign in campaign["data"]["rewardCampaignsAvailableToUser"]:
our_reward_campaign: RewardCampaign = await handle_reward_campaign(reward_campaign) our_reward_campaign, created = await RewardCampaign.objects.aupdate_or_create(id=reward_campaign["id"])
await our_reward_campaign.import_json(reward_campaign)
if created:
logger.info("Added reward campaign %s", our_reward_campaign)
if "rewards" in reward_campaign: if "rewards" in reward_campaign:
for reward in reward_campaign["rewards"]: for reward in reward_campaign["rewards"]:
await handle_rewards(reward, our_reward_campaign) reward_instance, created = await Reward.objects.aupdate_or_create(id=reward["id"])
await reward_instance.import_json(reward, our_reward_campaign)
async def handle_rewards(reward: dict, reward_campaign: RewardCampaign | None) -> None:
"""Add or update a reward in the database.
Args:
reward (dict): The JSON from Twitch.
reward_campaign (RewardCampaign | None): The reward campaign the reward belongs to.
"""
mappings: dict[str, str] = {
"name": "name",
"earnableUntil": "earnable_until",
"redemptionInstructions": "redemption_instructions",
"redemptionURL": "redemption_url",
}
defaults: dict = {new_key: reward[key] for key, new_key in mappings.items() if reward.get(key)}
if reward_campaign:
defaults["campaign"] = reward_campaign
if reward.get("bannerImage"):
defaults["banner_image_url"] = reward["bannerImage"]["image1xURL"]
if reward.get("thumbnailImage"):
defaults["thumbnail_image_url"] = reward["thumbnailImage"]["image1xURL"]
reward_instance, created = await Reward.objects.aupdate_or_create(id=reward["id"], defaults=defaults)
if created: if created:
logger.info("Added reward %s", reward_instance.id) logger.info("Added reward %s", reward_instance)
async def handle_reward_campaign(reward_campaign: dict) -> RewardCampaign:
"""Add or update a reward campaign in the database.
Args:
reward_campaign (dict): The reward campaign JSON from Twitch.
Returns:
RewardCampaign: The reward campaign that was added or updated.
"""
mappings: dict[str, str] = {
"name": "name",
"brand": "brand",
"createdAt": "created_at",
"startsAt": "starts_at",
"endsAt": "ends_at",
"status": "status",
"summary": "summary",
"instructions": "instructions",
"rewardValueURLParam": "reward_value_url_param",
"externalURL": "external_url",
"aboutURL": "about_url",
"isSitewide": "is_site_wide",
}
defaults: dict = {new_key: reward_campaign[key] for key, new_key in mappings.items() if reward_campaign.get(key)}
unlock_requirements: dict = reward_campaign.get("unlockRequirements", {})
if unlock_requirements.get("subsGoal"):
defaults["sub_goal"] = unlock_requirements["subsGoal"]
if unlock_requirements.get("minuteWatchedGoal"):
defaults["minute_watched_goal"] = unlock_requirements["minuteWatchedGoal"]
if reward_campaign.get("image"):
defaults["image_url"] = reward_campaign["image"]["image1xURL"]
reward_campaign_instance, created = await RewardCampaign.objects.aupdate_or_create(
id=reward_campaign["id"],
defaults=defaults,
)
if created:
logger.info("Added reward campaign %s", reward_campaign_instance.id)
if reward_campaign["game"]:
# TODO(TheLovinator): Add game to reward campaign # noqa: TD003
# TODO(TheLovinator): Send JSON to Discord # noqa: TD003
logger.error("Not implemented: Add game to reward campaign, JSON: %s", reward_campaign["game"])
return reward_campaign_instance
async def add_or_update_game(game_json: dict | None, owner: Owner | None) -> Game | None:
"""Add or update a game in the database.
Args:
game_json (dict): The game to add or update.
owner (Owner): The owner of the game.
Returns:
Game: The game that was added or updated.
"""
if not game_json:
return None
mappings: dict[str, str] = {
"slug": "slug",
"displayName": "name",
"boxArtURL": "box_art_url",
}
defaults: dict = {new_key: game_json[key] for key, new_key in mappings.items() if game_json.get(key)}
if game_json.get("slug"):
defaults["game_url"] = f"https://www.twitch.tv/directory/game/{game_json["slug"]}"
our_game, created = await Game.objects.aupdate_or_create(twitch_id=game_json["id"], defaults=defaults)
if created:
logger.info("Added game %s", our_game.twitch_id)
if owner:
await owner.games.aadd(our_game) # type: ignore # noqa: PGH003
return our_game
async def add_or_update_owner(owner_json: dict | None) -> Owner | None:
"""Add or update an owner in the database.
Args:
owner_json (dict): The owner to add or update.
Returns:
Owner: The owner that was added or updated.
"""
if not owner_json:
return None
defaults: dict[str, str] = {}
if owner_json.get("name"):
defaults["name"] = owner_json["name"]
our_owner, created = await Owner.objects.aupdate_or_create(id=owner_json.get("id"), defaults=defaults)
if created:
logger.info("Added owner %s", our_owner.id)
return our_owner
async def add_or_update_channels(channels_json: list[dict]) -> list[Channel] | None:
"""Add or update channels in the database.
Args:
channels_json (list[dict]): The channels to add or update.
Returns:
list[Channel]: The channels that were added or updated.
"""
if not channels_json:
return None
channels: list[Channel] = []
for channel_json in channels_json:
defaults: dict[str, str] = {}
if channel_json.get("displayName"):
defaults["display_name"] = channel_json["displayName"]
if channel_json.get("name"):
defaults["name"] = channel_json["name"]
defaults["twitch_url"] = f'https://www.twitch.tv/{channel_json["name"]}'
our_channel, created = await Channel.objects.aupdate_or_create(twitch_id=channel_json["id"], defaults=defaults)
if created:
logger.info("Added channel %s", our_channel.twitch_id)
channels.append(our_channel)
return channels
async def add_benefit(benefit: dict, time_based_drop: TimeBasedDrop) -> None:
"""Add a benefit to the database.
Args:
benefit (dict): The benefit to add.
time_based_drop (TimeBasedDrop): The time-based drop the benefit belongs to.
"""
mappings: dict[str, str] = {
"createdAt": "twitch_created_at",
"entitlementLimit": "entitlement_limit",
"imageAssetURL": "image_url",
"isIosAvailable": "is_ios_available",
"name": "name",
}
defaults: dict[str, str] = {new_key: benefit[key] for key, new_key in mappings.items() if benefit.get(key)}
our_benefit, created = await Benefit.objects.aupdate_or_create(id=benefit["id"], defaults=defaults)
if created:
logger.info("Added benefit %s", our_benefit.id)
if time_based_drop:
await time_based_drop.benefits.aadd(our_benefit) # type: ignore # noqa: PGH003
async def add_drop_campaign(drop_campaign: dict | None) -> None: async def add_drop_campaign(drop_campaign: dict | None) -> None:
@ -280,60 +100,24 @@ async def add_drop_campaign(drop_campaign: dict | None) -> None:
if not drop_campaign: if not drop_campaign:
return return
defaults: dict[str, str | Game | Owner] = {}
owner: Owner | None = await get_owner(drop_campaign)
if drop_campaign.get("game"): if drop_campaign.get("game"):
game: Game | None = await add_or_update_game(drop_campaign["game"], owner) owner, created = await Owner.objects.aupdate_or_create(id=drop_campaign["owner"]["id"])
if game: owner.import_json(drop_campaign["owner"])
defaults["game"] = game
mappings: dict[str, str] = { game, created = await Game.objects.aupdate_or_create(id=drop_campaign["game"]["id"])
"accountLinkURL": "account_link_url", await game.import_json(drop_campaign["game"], owner)
"description": "description", if created:
"detailsURL": "details_url", logger.info("Added game %s", game)
"endAt": "ends_at",
"startAt": "starts_at", our_drop_campaign, created = await DropCampaign.objects.aupdate_or_create(id=drop_campaign["id"])
"imageURL": "image_url", await our_drop_campaign.import_json(drop_campaign, game)
"name": "name",
"status": "status",
}
for key, new_key in mappings.items():
if drop_campaign.get(key):
defaults[new_key] = drop_campaign[key]
our_drop_campaign, created = await DropCampaign.objects.aupdate_or_create(
id=drop_campaign["id"],
defaults=defaults,
)
if created: if created:
logger.info("Added drop campaign %s", our_drop_campaign.id) logger.info("Added drop campaign %s", our_drop_campaign.id)
if drop_campaign.get("allow") and drop_campaign["allow"].get("channels"):
channels: list[Channel] | None = await add_or_update_channels(drop_campaign["allow"]["channels"])
if channels:
for channel in channels:
await channel.drop_campaigns.aadd(our_drop_campaign)
await add_time_based_drops(drop_campaign, our_drop_campaign) await add_time_based_drops(drop_campaign, our_drop_campaign)
async def get_owner(drop_campaign: dict) -> Owner | None:
"""Get the owner of the drop campaign.
Args:
drop_campaign (dict): The drop campaign containing the owner.
Returns:
Owner: The owner of the drop campaign.
"""
owner = None
if drop_campaign.get("owner"):
owner: Owner | None = await add_or_update_owner(drop_campaign["owner"])
return owner
async def add_time_based_drops(drop_campaign: dict, our_drop_campaign: DropCampaign) -> None: async def add_time_based_drops(drop_campaign: dict, our_drop_campaign: DropCampaign) -> None:
"""Add time-based drops to the database. """Add time-based drops to the database.
@ -346,31 +130,21 @@ async def add_time_based_drops(drop_campaign: dict, our_drop_campaign: DropCampa
if time_based_drop.get("preconditionDrops"): if time_based_drop.get("preconditionDrops"):
# TODO(TheLovinator): Add precondition drops to time-based drop # noqa: TD003 # TODO(TheLovinator): Add precondition drops to time-based drop # noqa: TD003
# TODO(TheLovinator): Send JSON to Discord # noqa: TD003 # TODO(TheLovinator): Send JSON to Discord # noqa: TD003
logger.error("Not implemented: Add precondition drops to time-based drop, JSON: %s", time_based_drop) msg = "Not implemented: Add precondition drops to time-based drop"
raise NotImplementedError(msg)
mappings: dict[str, str] = { our_time_based_drop, created = await TimeBasedDrop.objects.aupdate_or_create(id=time_based_drop["id"])
"requiredSubs": "required_subs", await our_time_based_drop.import_json(time_based_drop, our_drop_campaign)
"endAt": "ends_at",
"name": "name",
"requiredMinutesWatched": "required_minutes_watched",
"startAt": "starts_at",
}
defaults: dict[str, str | DropCampaign] = {
new_key: time_based_drop[key] for key, new_key in mappings.items() if time_based_drop.get(key)
}
if our_drop_campaign:
defaults["drop_campaign"] = our_drop_campaign
our_time_based_drop, created = await TimeBasedDrop.objects.aupdate_or_create(
id=time_based_drop["id"],
defaults=defaults,
)
if created: if created:
logger.info("Added time-based drop %s", our_time_based_drop.id) logger.info("Added time-based drop %s", our_time_based_drop.id)
if time_based_drop.get("benefitEdges") and our_time_based_drop: if our_time_based_drop and time_based_drop.get("benefitEdges"):
for benefit_edge in time_based_drop["benefitEdges"]: for benefit_edge in time_based_drop["benefitEdges"]:
await add_benefit(benefit_edge["benefit"], our_time_based_drop) benefit, created = await Benefit.objects.aupdate_or_create(id=benefit_edge["benefit"])
await benefit.import_json(benefit_edge["benefit"], our_time_based_drop)
if created:
logger.info("Added benefit %s", benefit.id)
async def process_json_data(num: int, campaign: dict | None) -> None: async def process_json_data(num: int, campaign: dict | None) -> None:

View File

@ -0,0 +1,15 @@
# Generated by Django 5.1 on 2024-09-01 02:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.DeleteModel(
name="Channel",
),
]

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import ClassVar from typing import ClassVar, Self
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
@ -24,6 +24,15 @@ class Owner(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Owner name unknown" return self.name or "Owner name unknown"
def import_json(self, data: dict | None) -> Self:
if not data:
return self
self.name = data.get("name", self.name)
self.save()
return self
class Game(models.Model): class Game(models.Model):
"""This is the game we will see on the front end.""" """This is the game we will see on the front end."""
@ -32,59 +41,97 @@ class Game(models.Model):
# "https://www.twitch.tv/directory/category/halo-infinite" # "https://www.twitch.tv/directory/category/halo-infinite"
game_url = models.URLField(null=True, default="https://www.twitch.tv/") game_url = models.URLField(null=True, default="https://www.twitch.tv/")
name = models.TextField(null=True, default="Game name unknown") # "Halo Infinite"
# "Halo Infinite"
name = models.TextField(null=True, default="Game name unknown")
# "https://static-cdn.jtvnw.net/ttv-boxart/Halo%20Infinite.jpg" # "https://static-cdn.jtvnw.net/ttv-boxart/Halo%20Infinite.jpg"
box_art_url = models.URLField(null=True, default="https://static-cdn.jtvnw.net/ttv-static/404_boxart.jpg") box_art_url = models.URLField(null=True, default="https://static-cdn.jtvnw.net/ttv-static/404_boxart.jpg")
slug = models.TextField(null=True) # "halo-infinite"
# "halo-infinite"
slug = models.TextField(null=True)
org = models.ForeignKey(Owner, on_delete=models.CASCADE, related_name="games", null=True) org = models.ForeignKey(Owner, on_delete=models.CASCADE, related_name="games", null=True)
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Game name unknown" return self.name or self.twitch_id
async def import_json(self, data: dict | None, owner: Owner | None) -> Self:
if not data:
logger.error("No data provided for %s.", self)
return self
self.name = data.get("name", self.name)
self.box_art_url = data.get("boxArtURL", self.box_art_url)
self.slug = data.get("slug", self.slug)
if data.get("slug"):
self.game_url = f"https://www.twitch.tv/directory/game/{data["slug"]}"
if owner:
await owner.games.aadd(self) # type: ignore # noqa: PGH003
self.save()
return self
class DropCampaign(models.Model): class DropCampaign(models.Model):
"""This is the drop campaign we will see on the front end.""" """This is the drop campaign we will see on the front end."""
id = models.TextField(primary_key=True) # "f257ce6e-502a-11ef-816e-0a58a9feac02" # "f257ce6e-502a-11ef-816e-0a58a9feac02"
created_at = models.DateTimeField(null=True, auto_created=True) # "2024-08-11T00:00:00Z" id = models.TextField(primary_key=True)
modified_at = models.DateTimeField(null=True, auto_now=True) # "2024-08-12T00:00:00Z" created_at = models.DateTimeField(null=True, auto_created=True)
modified_at = models.DateTimeField(null=True, auto_now=True)
account_link_url = models.URLField(null=True) # "https://www.halowaypoint.com/settings/linked-accounts" # "https://www.halowaypoint.com/settings/linked-accounts"
account_link_url = models.URLField(null=True)
# "Tune into this HCS Grassroots event to earn Halo Infinite in-game content!" # "Tune into this HCS Grassroots event to earn Halo Infinite in-game content!"
description = models.TextField(null=True) description = models.TextField(null=True)
details_url = models.URLField(null=True) # "https://www.halowaypoint.com"
ends_at = models.DateTimeField(null=True) # "2024-08-12T05:59:59.999Z" # "https://www.halowaypoint.com"
starts_at = models.DateTimeField(null=True) # "2024-08-11T11:00:00Z"" details_url = models.URLField(null=True)
# "2024-08-12T05:59:59.999Z"
ends_at = models.DateTimeField(null=True)
# "2024-08-11T11:00:00Z""
starts_at = models.DateTimeField(null=True)
game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="drop_campaigns", null=True) game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="drop_campaigns", null=True)
# "https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/c8e02666-8b86-471f-bf38-7ece29a758e4.png" # "https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/c8e02666-8b86-471f-bf38-7ece29a758e4.png"
image_url = models.URLField(null=True) image_url = models.URLField(null=True)
name = models.TextField(null=True) # "HCS Open Series - Week 1 - DAY 2 - AUG11" # "HCS Open Series - Week 1 - DAY 2 - AUG11"
status = models.TextField(null=True) # "ACTIVE" name = models.TextField(null=True, default="Unknown")
# "ACTIVE"
status = models.TextField(null=True)
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Drop campaign name unknown" return self.name or self.id
async def import_json(self, data: dict | None, game: Game) -> Self:
if not data:
logger.error("No data provided for %s.", self)
return self
class Channel(models.Model): self.name = data.get("name", self.name)
"""This is the channel we will see on the front end.""" self.account_link_url = data.get("accountLinkURL", self.account_link_url)
self.description = data.get("description", self.description)
self.details_url = data.get("detailsURL", self.details_url)
self.ends_at = data.get("endAt", self.ends_at)
self.starts_at = data.get("startAt", self.starts_at)
self.status = data.get("status", self.status)
self.image_url = data.get("imageURL", self.image_url)
twitch_id = models.TextField(primary_key=True) # "222719079" if game:
display_name = models.TextField(null=True, default="Channel name unknown") # "LVTHalo" self.game = game
name = models.TextField(null=True) # "lvthalo"
twitch_url = models.URLField(null=True, default="https://www.twitch.tv/") # "https://www.twitch.tv/lvthalo"
live = models.BooleanField(default=False) # "True"
drop_campaigns = models.ManyToManyField(DropCampaign, related_name="channels") self.save()
def __str__(self) -> str: return self
return self.display_name or "Channel name unknown"
class TimeBasedDrop(models.Model): class TimeBasedDrop(models.Model):
@ -105,6 +152,24 @@ class TimeBasedDrop(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Drop name unknown" return self.name or "Drop name unknown"
async def import_json(self, data: dict | None, drop_campaign: DropCampaign) -> Self:
if not data:
logger.error("No data provided for %s.", self)
return self
self.name = data.get("name", self.name)
self.required_subs = data.get("requiredSubs", self.required_subs)
self.required_minutes_watched = data.get("requiredMinutesWatched", self.required_minutes_watched)
self.starts_at = data.get("startAt", self.starts_at)
self.ends_at = data.get("endAt", self.ends_at)
if drop_campaign:
self.drop_campaign = drop_campaign
self.save()
return self
class Benefit(models.Model): class Benefit(models.Model):
"""Benefits are the rewards for the drops.""" """Benefits are the rewards for the drops."""
@ -134,6 +199,24 @@ class Benefit(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Benefit name unknown" return self.name or "Benefit name unknown"
async def import_json(self, data: dict | None, time_based_drop: TimeBasedDrop) -> Self:
if not data:
logger.error("No data provided for %s.", self)
return self
self.name = data.get("name", self.name)
self.entitlement_limit = data.get("entitlementLimit", self.entitlement_limit)
self.is_ios_available = data.get("isIosAvailable", self.is_ios_available)
self.image_url = data.get("imageAssetURL", self.image_url)
self.twitch_created_at = data.get("createdAt", self.twitch_created_at)
if time_based_drop:
await time_based_drop.benefits.aadd(self) # type: ignore # noqa: PGH003
self.save()
return self
class RewardCampaign(models.Model): class RewardCampaign(models.Model):
"""Buy subscriptions to earn rewards.""" """Buy subscriptions to earn rewards."""
@ -164,6 +247,41 @@ class RewardCampaign(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Reward campaign name unknown" return self.name or "Reward campaign name unknown"
async def import_json(self, data: dict | None) -> Self:
if not data:
logger.error("No data provided for %s.", self)
return self
self.name = data.get("name", self.name)
self.brand = data.get("brand", self.brand)
self.starts_at = data.get("startsAt", self.starts_at)
self.ends_at = data.get("endsAt", self.ends_at)
self.status = data.get("status", self.status)
self.summary = data.get("summary", self.summary)
self.instructions = data.get("instructions", self.instructions)
self.reward_value_url_param = data.get("rewardValueURLParam", self.reward_value_url_param)
self.external_url = data.get("externalURL", self.external_url)
self.about_url = data.get("aboutURL", self.about_url)
self.is_site_wide = data.get("isSiteWide", self.is_site_wide)
unlock_requirements: dict = data.get("unlockRequirements", {})
if unlock_requirements:
self.sub_goal = unlock_requirements.get("subsGoal", self.sub_goal)
self.minute_watched_goal = unlock_requirements.get("minuteWatchedGoal", self.minute_watched_goal)
image = data.get("image", {})
if image:
self.image_url = image.get("image1xURL", self.image_url)
if data.get("game"):
game: Game | None = Game.objects.filter(twitch_id=data["game"]["id"]).first()
if game:
await game.reward_campaigns.aadd(self) # type: ignore # noqa: PGH003
self.save()
return self
class Reward(models.Model): class Reward(models.Model):
"""This from the RewardCampaign.""" """This from the RewardCampaign."""
@ -185,6 +303,31 @@ class Reward(models.Model):
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Reward name unknown" return self.name or "Reward name unknown"
async def import_json(self, data: dict | None, reward_campaign: RewardCampaign) -> Self:
if not data:
logger.error("No data provided for %s.", self)
return self
self.name = data.get("name", self.name)
self.earnable_until = data.get("earnableUntil", self.earnable_until)
self.redemption_instructions = data.get("redemptionInstructions", self.redemption_instructions)
self.redemption_url = data.get("redemptionURL", self.redemption_url)
banner_image = data.get("bannerImage", {})
if banner_image:
self.banner_image_url = banner_image.get("image1xURL", self.banner_image_url)
thumbnail_image = data.get("thumbnailImage", {})
if thumbnail_image:
self.thumbnail_image_url = thumbnail_image.get("image1xURL", self.thumbnail_image_url)
if reward_campaign:
self.campaign = reward_campaign
self.save()
return self
class Webhook(models.Model): class Webhook(models.Model):
"""Discord webhook.""" """Discord webhook."""

View File

@ -63,8 +63,6 @@
<hr> <hr>
<div> <div>
<h3 class="mb-2">{{ drop.name }}</h3> <h3 class="mb-2">{{ drop.name }}</h3>
{% if drop.benefits.all %}
{% for benefit in drop.benefits.all %} {% for benefit in drop.benefits.all %}
<img src="{{ benefit.image_url }}" alt="{{ benefit.name }} image" <img src="{{ benefit.image_url }}" alt="{{ benefit.name }} image"
class="img-fluid rounded mb-2"> class="img-fluid rounded mb-2">
@ -79,9 +77,6 @@
{% empty %} {% empty %}
<div>No benefits available for this drop.</div> <div>No benefits available for this drop.</div>
{% endfor %} {% endfor %}
{% else %}
<p>No benefits available for this drop.</p>
{% endif %}
</div> </div>
{% empty %} {% empty %}
<div>No time-based drops available for this campaign.</div> <div>No time-based drops available for this campaign.</div>