This commit is contained in:
2024-08-02 05:10:23 +02:00
parent 9252e41a15
commit 09b21d4f43
13 changed files with 940 additions and 74 deletions

View File

@ -1,6 +1,9 @@
{% extends "base.html" %}
{% block content %}
<div class="container mt-4">
<div class="row">
<div class="col-lg-3">{{ toc|safe }}</div>
<div class="col-lg-9">
<div class="row">
{% for game in games %}
<div class="col-xl-3 col-lg-4 col-md-6 col-sm-12 mb-4">
@ -26,4 +29,6 @@
{% endfor %}
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -3,7 +3,7 @@
{% block content %}
<div class="container mt-4">
<div class="row">
<div class="col-lg-3">{% include "partials/toc.html" %}</div>
<div class="col-lg-3">{{ toc|safe }}</div>
<div class="col-lg-9">
{% include "partials/info_box.html" %}
{% include "partials/news.html" %}

View File

@ -7,15 +7,15 @@
<li class="nav-item">
<a class="nav-link" href='{% url "core:games" %}'>Games</a>
</li>
<li>
<a class="nav-link" href='{% url "core:reward_campaigns" %}'>Reward campaigns</a>
</li>
<li class="nav-item">
<a class="nav-link" href=''>API</a>
</li>
<li class="nav-item d-none d-sm-block">
<a class="nav-link" href="https://github.com/sponsors/TheLovinator1">Donate</a>
</li>
<li>
<a class="nav-link" href='{% url "core:reward_campaigns" %}'>Reward campaigns</a>
</li>
</ul>
</nav>
</header>

View File

@ -1,12 +0,0 @@
<div class="position-sticky d-none d-lg-block toc">
<div class="card">
<div class="card-body">
<div id="toc-list" class="list-group">
{% for campaign in reward_campaigns %}
<a class="list-group-item list-group-item-action plain-text-item"
href="#reward-{{ campaign.id }}">{{ campaign }}</a>
{% endfor %}
</div>
</div>
</div>
</div>

View File

@ -1,19 +0,0 @@
<div class="position-sticky d-none d-lg-block toc">
<div class="card">
<div class="card-body">
<div id="toc-list" class="list-group">
<a class="list-group-item list-group-item-action plain-text-item"
href="#info-box">Information</a>
<a class="list-group-item list-group-item-action plain-text-item"
href="#game-list">Site news</a>
{% for org in orgs %}
{{ org }}
{% for game in org.games.all %}
<a class="list-group-item list-group-item-action plain-text-item"
href="#game-{{ game.game_id }}">{{ game.display_name }}</a>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
</div>

View File

@ -3,7 +3,7 @@
{% block content %}
<div class="container mt-4">
<div class="row">
<div class="col-lg-3">{% include "partials/reward_campaigns_toc.html" %}</div>
<div class="col-lg-3">{{ toc }}</div>
<div class="col-lg-9">
<h2>Reward Campaigns</h2>
<div>

View File

@ -2,7 +2,7 @@ from __future__ import annotations
from django.urls import URLPattern, URLResolver, path
from core.views import GameView, RewardCampaignView, index
from core.views import game_view, index, reward_campaign_view
app_name: str = "core"
@ -10,12 +10,12 @@ urlpatterns: list[URLPattern | URLResolver] = [
path(route="", view=index, name="index"),
path(
route="games/",
view=GameView.as_view(),
view=game_view,
name="games",
),
path(
route="reward_campaigns/",
view=RewardCampaignView.as_view(),
view=reward_campaign_view,
name="reward_campaigns",
),
]

View File

@ -1,12 +1,13 @@
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING
import hishel
from django.conf import settings
from django.db.models.manager import BaseManager
from django.template.response import TemplateResponse
from django.views.generic import ListView
from core.data import WebhookData
from twitch_app.models import Game, RewardCampaign
@ -58,26 +59,59 @@ def get_webhook_data(webhook: str) -> WebhookData:
)
@dataclass
class TOCItem:
"""Table of contents item."""
name: str
toc_id: str
def build_toc(list_of_things: list[TOCItem]) -> str:
"""Build the table of contents."""
html: str = """
<div class="position-sticky d-none d-lg-block toc">
<div class="card">
<div class="card-body">
<div id="toc-list" class="list-group">
"""
for item in list_of_things:
html += (
f'<a class="list-group-item list-group-item-action plain-text-item" href="#{item.toc_id}">{item.name}</a>'
)
html += """</div></div></div></div>"""
return html
def index(request: HttpRequest) -> HttpResponse:
"""Render the index page."""
reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
return TemplateResponse(
request=request,
template="index.html",
context={"reward_campaigns": reward_campaigns},
)
toc: str = build_toc([
TOCItem(name="Information", toc_id="#info-box"),
TOCItem(name="Games", toc_id="#games"),
])
context: dict[str, BaseManager[RewardCampaign] | str] = {"reward_campaigns": reward_campaigns, "toc": toc}
return TemplateResponse(request=request, template="index.html", context=context)
class GameView(ListView):
model = Game
template_name: str = "games.html"
context_object_name: str = "games"
paginate_by = 100
def game_view(request: HttpRequest) -> HttpResponse:
"""Render the game view page."""
games: BaseManager[Game] = Game.objects.all()
tocs: list[TOCItem] = [
TOCItem(name=game.display_name, toc_id=game.slug) for game in games if game.display_name and game.slug
]
toc: str = build_toc(tocs)
context: dict[str, BaseManager[Game] | str] = {"games": games, "toc": toc}
return TemplateResponse(request=request, template="games.html", context=context)
class RewardCampaignView(ListView):
model = RewardCampaign
template_name: str = "reward_campaigns.html"
context_object_name: str = "reward_campaigns"
paginate_by = 100
def reward_campaign_view(request: HttpRequest) -> HttpResponse:
"""Render the reward campaign view page."""
reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
context: dict[str, BaseManager[RewardCampaign]] = {"reward_campaigns": reward_campaigns}
return TemplateResponse(request=request, template="reward_campaigns.html", context=context)

380
testboi.py Normal file
View File

@ -0,0 +1,380 @@
from datetime import datetime
from enum import Enum
from typing import Any
from uuid import UUID
class ChannelTypename(Enum):
CHANNEL = "Channel"
GAME = "Game"
ORGANIZATION = "Organization"
class Channel:
id: int
display_name: str
name: str
typename: ChannelTypename
def __init__(self, id: int, display_name: str, name: str, typename: ChannelTypename) -> None:
self.id = id
self.display_name = display_name
self.name = name
self.typename = typename
class AllowTypename(Enum):
DROP_CAMPAIGN_ACL = "DropCampaignACL"
class Allow:
channels: list[Channel] | None
is_enabled: bool
typename: AllowTypename
def __init__(self, channels: list[Channel] | None, is_enabled: bool, typename: AllowTypename) -> None:
self.channels = channels
self.is_enabled = is_enabled
self.typename = typename
class SelfTypename(Enum):
DROP_CAMPAIGN_SELF_EDGE = "DropCampaignSelfEdge"
class Self:
is_account_connected: bool
typename: SelfTypename
def __init__(self, is_account_connected: bool, typename: SelfTypename) -> None:
self.is_account_connected = is_account_connected
self.typename = typename
class Game:
id: int
slug: str
display_name: str
typename: ChannelTypename
def __init__(self, id: int, slug: str, display_name: str, typename: ChannelTypename) -> None:
self.id = id
self.slug = slug
self.display_name = display_name
self.typename = typename
class PurpleOwner:
id: UUID | int
name: str | None
typename: ChannelTypename
display_name: str | None
slug: str | None
def __init__(
self,
id: UUID | int,
name: str | None,
typename: ChannelTypename,
display_name: str | None,
slug: str | None,
) -> None:
self.id = id
self.name = name
self.typename = typename
self.display_name = display_name
self.slug = slug
class Status(Enum):
ACTIVE = "ACTIVE"
EXPIRED = "EXPIRED"
class GameClass:
id: UUID | int
name: str
typename: ChannelTypename
def __init__(self, id: UUID | int, name: str, typename: ChannelTypename) -> None:
self.id = id
self.name = name
self.typename = typename
class BenefitTypename(Enum):
DROP_BENEFIT = "DropBenefit"
class Benefit:
id: str
created_at: datetime
entitlement_limit: int
game: GameClass
image_asset_url: str
is_ios_available: bool
name: str
owner_organization: GameClass
typename: BenefitTypename
def __init__(
self,
id: str,
created_at: datetime,
entitlement_limit: int,
game: GameClass,
image_asset_url: str,
is_ios_available: bool,
name: str,
owner_organization: GameClass,
typename: BenefitTypename,
) -> None:
self.id = id
self.created_at = created_at
self.entitlement_limit = entitlement_limit
self.game = game
self.image_asset_url = image_asset_url
self.is_ios_available = is_ios_available
self.name = name
self.owner_organization = owner_organization
self.typename = typename
class BenefitEdgeTypename(Enum):
DROP_BENEFIT_EDGE = "DropBenefitEdge"
class BenefitEdge:
benefit: Benefit
entitlement_limit: int
typename: BenefitEdgeTypename
def __init__(self, benefit: Benefit, entitlement_limit: int, typename: BenefitEdgeTypename) -> None:
self.benefit = benefit
self.entitlement_limit = entitlement_limit
self.typename = typename
class TimeBasedDropTypename(Enum):
TIME_BASED_DROP = "TimeBasedDrop"
class TimeBasedDrop:
id: UUID
required_subs: int
benefit_edges: list[BenefitEdge]
end_at: datetime
name: str
precondition_drops: None
required_minutes_watched: int
start_at: datetime
typename: TimeBasedDropTypename
def __init__(
self,
id: UUID,
required_subs: int,
benefit_edges: list[BenefitEdge],
end_at: datetime,
name: str,
precondition_drops: None,
required_minutes_watched: int,
start_at: datetime,
typename: TimeBasedDropTypename,
) -> None:
self.id = id
self.required_subs = required_subs
self.benefit_edges = benefit_edges
self.end_at = end_at
self.name = name
self.precondition_drops = precondition_drops
self.required_minutes_watched = required_minutes_watched
self.start_at = start_at
self.typename = typename
class DropCampaignTypename(Enum):
DROP_CAMPAIGN = "DropCampaign"
class PurpleDropCampaign:
id: UUID
drop_campaign_self: Self
allow: Allow
account_link_url: str
description: str
details_url: str
end_at: datetime
event_based_drops: list[Any]
game: Game
image_url: str
name: str
owner: PurpleOwner
start_at: datetime
status: Status
time_based_drops: list[TimeBasedDrop]
typename: DropCampaignTypename
def __init__(
self,
id: UUID,
drop_campaign_self: Self,
allow: Allow,
account_link_url: str,
description: str,
details_url: str,
end_at: datetime,
event_based_drops: list[Any],
game: Game,
image_url: str,
name: str,
owner: PurpleOwner,
start_at: datetime,
status: Status,
time_based_drops: list[TimeBasedDrop],
typename: DropCampaignTypename,
) -> None:
self.id = id
self.drop_campaign_self = drop_campaign_self
self.allow = allow
self.account_link_url = account_link_url
self.description = description
self.details_url = details_url
self.end_at = end_at
self.event_based_drops = event_based_drops
self.game = game
self.image_url = image_url
self.name = name
self.owner = owner
self.start_at = start_at
self.status = status
self.time_based_drops = time_based_drops
self.typename = typename
class UserTypename(Enum):
USER = "User"
class PurpleUser:
id: int
drop_campaign: PurpleDropCampaign
typename: UserTypename
def __init__(self, id: int, drop_campaign: PurpleDropCampaign, typename: UserTypename) -> None:
self.id = id
self.drop_campaign = drop_campaign
self.typename = typename
class DropCampaign100_Data:
user: PurpleUser
def __init__(self, user: PurpleUser) -> None:
self.user = user
class OperationName(Enum):
DROP_CAMPAIGN_DETAILS = "DropCampaignDetails"
class Extensions:
duration_milliseconds: int
operation_name: OperationName
request_id: str
def __init__(self, duration_milliseconds: int, operation_name: OperationName, request_id: str) -> None:
self.duration_milliseconds = duration_milliseconds
self.operation_name = operation_name
self.request_id = request_id
class DropCampaign99:
data: DropCampaign100_Data
extensions: Extensions
def __init__(self, data: DropCampaign100_Data, extensions: Extensions) -> None:
self.data = data
self.extensions = extensions
class FluffyDropCampaign:
id: UUID
drop_campaign_self: Self
allow: Allow
account_link_url: str
description: str
details_url: str
end_at: datetime
event_based_drops: list[Any]
game: Game
image_url: str
name: str
owner: GameClass
start_at: datetime
status: Status
time_based_drops: list[TimeBasedDrop]
typename: DropCampaignTypename
def __init__(
self,
id: UUID,
drop_campaign_self: Self,
allow: Allow,
account_link_url: str,
description: str,
details_url: str,
end_at: datetime,
event_based_drops: list[Any],
game: Game,
image_url: str,
name: str,
owner: GameClass,
start_at: datetime,
status: Status,
time_based_drops: list[TimeBasedDrop],
typename: DropCampaignTypename,
) -> None:
self.id = id
self.drop_campaign_self = drop_campaign_self
self.allow = allow
self.account_link_url = account_link_url
self.description = description
self.details_url = details_url
self.end_at = end_at
self.event_based_drops = event_based_drops
self.game = game
self.image_url = image_url
self.name = name
self.owner = owner
self.start_at = start_at
self.status = status
self.time_based_drops = time_based_drops
self.typename = typename
class FluffyUser:
id: int
drop_campaign: FluffyDropCampaign
typename: UserTypename
def __init__(self, id: int, drop_campaign: FluffyDropCampaign, typename: UserTypename) -> None:
self.id = id
self.drop_campaign = drop_campaign
self.typename = typename
class DropCampaign109_Data:
user: FluffyUser
def __init__(self, user: FluffyUser) -> None:
self.user = user
class DropCampaign149:
data: DropCampaign109_Data
extensions: Extensions
def __init__(self, data: DropCampaign109_Data, extensions: Extensions) -> None:
self.data = data
self.extensions = extensions

252
testboi2.py Normal file
View File

@ -0,0 +1,252 @@
from datetime import datetime
from enum import Enum
from uuid import UUID
class SelfTypename(Enum):
DROP_CAMPAIGN_SELF_EDGE = "DropCampaignSelfEdge"
class Self:
is_account_connected: bool
typename: SelfTypename
def __init__(self, is_account_connected: bool, typename: SelfTypename) -> None:
self.is_account_connected = is_account_connected
self.typename = typename
class GameTypename(Enum):
GAME = "Game"
class Game:
id: int
display_name: str
box_art_url: str
typename: GameTypename
def __init__(self, id: int, display_name: str, box_art_url: str, typename: GameTypename) -> None:
self.id = id
self.display_name = display_name
self.box_art_url = box_art_url
self.typename = typename
class OwnerTypename(Enum):
ORGANIZATION = "Organization"
class Owner:
id: UUID
name: str
typename: OwnerTypename
def __init__(self, id: UUID, name: str, typename: OwnerTypename) -> None:
self.id = id
self.name = name
self.typename = typename
class Status(Enum):
ACTIVE = "ACTIVE"
EXPIRED = "EXPIRED"
class DropCampaignTypename(Enum):
DROP_CAMPAIGN = "DropCampaign"
class DropCampaign:
id: UUID
name: str
owner: Owner
game: Game
status: Status
start_at: datetime
end_at: datetime
details_url: str
account_link_url: str
drop_campaign_self: Self
typename: DropCampaignTypename
def __init__(
self,
id: UUID,
name: str,
owner: Owner,
game: Game,
status: Status,
start_at: datetime,
end_at: datetime,
details_url: str,
account_link_url: str,
drop_campaign_self: Self,
typename: DropCampaignTypename,
) -> None:
self.id = id
self.name = name
self.owner = owner
self.game = game
self.status = status
self.start_at = start_at
self.end_at = end_at
self.details_url = details_url
self.account_link_url = account_link_url
self.drop_campaign_self = drop_campaign_self
self.typename = typename
class CurrentUser:
id: int
login: str
drop_campaigns: list[DropCampaign]
typename: str
def __init__(self, id: int, login: str, drop_campaigns: list[DropCampaign], typename: str) -> None:
self.id = id
self.login = login
self.drop_campaigns = drop_campaigns
self.typename = typename
class Image:
image1_x_url: str
typename: str
def __init__(self, image1_x_url: str, typename: str) -> None:
self.image1_x_url = image1_x_url
self.typename = typename
class Reward:
id: UUID
name: str
banner_image: Image
thumbnail_image: Image
earnable_until: datetime
redemption_instructions: str
redemption_url: str
typename: str
def __init__(
self,
id: UUID,
name: str,
banner_image: Image,
thumbnail_image: Image,
earnable_until: datetime,
redemption_instructions: str,
redemption_url: str,
typename: str,
) -> None:
self.id = id
self.name = name
self.banner_image = banner_image
self.thumbnail_image = thumbnail_image
self.earnable_until = earnable_until
self.redemption_instructions = redemption_instructions
self.redemption_url = redemption_url
self.typename = typename
class UnlockRequirements:
subs_goal: int
minute_watched_goal: int
typename: str
def __init__(self, subs_goal: int, minute_watched_goal: int, typename: str) -> None:
self.subs_goal = subs_goal
self.minute_watched_goal = minute_watched_goal
self.typename = typename
class RewardCampaignsAvailableToUser:
id: UUID
name: str
brand: str
starts_at: datetime
ends_at: datetime
status: str
summary: str
instructions: str
external_url: str
reward_value_url_param: str
about_url: str
is_sitewide: bool
game: None
unlock_requirements: UnlockRequirements
image: Image
rewards: list[Reward]
typename: str
def __init__(
self,
id: UUID,
name: str,
brand: str,
starts_at: datetime,
ends_at: datetime,
status: str,
summary: str,
instructions: str,
external_url: str,
reward_value_url_param: str,
about_url: str,
is_sitewide: bool,
game: None,
unlock_requirements: UnlockRequirements,
image: Image,
rewards: list[Reward],
typename: str,
) -> None:
self.id = id
self.name = name
self.brand = brand
self.starts_at = starts_at
self.ends_at = ends_at
self.status = status
self.summary = summary
self.instructions = instructions
self.external_url = external_url
self.reward_value_url_param = reward_value_url_param
self.about_url = about_url
self.is_sitewide = is_sitewide
self.game = game
self.unlock_requirements = unlock_requirements
self.image = image
self.rewards = rewards
self.typename = typename
class Data:
current_user: CurrentUser
reward_campaigns_available_to_user: list[RewardCampaignsAvailableToUser]
def __init__(
self,
current_user: CurrentUser,
reward_campaigns_available_to_user: list[RewardCampaignsAvailableToUser],
) -> None:
self.current_user = current_user
self.reward_campaigns_available_to_user = reward_campaigns_available_to_user
class Extensions:
duration_milliseconds: int
operation_name: str
request_id: str
def __init__(self, duration_milliseconds: int, operation_name: str, request_id: str) -> None:
self.duration_milliseconds = duration_milliseconds
self.operation_name = operation_name
self.request_id = request_id
class RewardCampaign11:
data: Data
extensions: Extensions
def __init__(self, data: Data, extensions: Extensions) -> None:
self.data = data
self.extensions = extensions

View File

@ -28,6 +28,7 @@ from twitch_app.models import (
if TYPE_CHECKING:
from playwright.async_api._generated import BrowserContext, Page
import json
# Where to store the Chrome profile
data_dir = Path(
@ -66,6 +67,7 @@ async def add_or_get_game(json_data: dict, name: str) -> tuple[Game | None, bool
"slug": json_data.get("slug"),
"display_name": json_data.get("displayName"),
"typename": json_data.get("__typename"),
"box_art_url": json_data.get("boxArtURL"), # Only for RewardCampaigns
},
)
@ -204,6 +206,9 @@ async def add_or_get_drop_campaign(
logger.warning("No drop campaign data found")
return None, False
if drop_campaign_data.get("__typename") != "Game":
logger.error("__typename is not 'Game' for %s", drop_campaign_data.get("name", "Unknown Drop Campaign"))
drop_campaign, _ = await DropCampaign.objects.aupdate_or_create(
id=drop_campaign_data["id"],
defaults={
@ -546,16 +551,35 @@ class Command(BaseCommand):
continue
if "rewardCampaignsAvailableToUser" in campaign["data"]:
# Save to folder named "reward_campaigns"
dir_name: Path = Path("reward_campaigns")
dir_name.mkdir(parents=True, exist_ok=True)
with open(file=Path(dir_name / f"reward_campaign_{num}.json"), mode="w", encoding="utf-8") as f:
json.dump(campaign, f, indent=4)
await add_reward_campaign(campaign)
if "dropCampaign" in campaign.get("data", {}).get("user", {}):
if not campaign["data"]["user"]["dropCampaign"]:
logger.warning("No drop campaign found")
continue
# Save to folder named "drop_campaign"
dir_name: Path = Path("drop_campaign")
dir_name.mkdir(parents=True, exist_ok=True)
with open(file=Path(dir_name / f"drop_campaign_{num}.json"), mode="w", encoding="utf-8") as f:
json.dump(campaign, f, indent=4)
await add_drop_campaign(campaign)
if "dropCampaigns" in campaign.get("data", {}).get("user", {}):
for drop_campaign in campaign["data"]["user"]["dropCampaigns"]:
# Save to folder named "drop_campaigns"
dir_name: Path = Path("drop_campaigns")
dir_name.mkdir(parents=True, exist_ok=True)
with open(file=Path(dir_name / f"drop_campaign_{num}.json"), mode="w", encoding="utf-8") as f:
json.dump(drop_campaign, f, indent=4)
await add_drop_campaign(drop_campaign)
return json_data

View File

@ -0,0 +1,135 @@
# Generated by Django 5.1rc1 on 2024-08-02 01:20
import django.db.models.deletion
import django.db.models.manager
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("twitch_app", "0009_alter_benefit_entitlement_limit_and_more"),
]
operations = [
migrations.CreateModel(
name="FrontEndChannel",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("name", models.TextField(blank=True, null=True)),
("twitch_url", models.URLField(blank=True, null=True)),
("live", models.BooleanField(default=False)),
],
options={
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.CreateModel(
name="FrontEndGame",
fields=[
("twitch_id", models.TextField(primary_key=True, serialize=False)),
("game_url", models.URLField(blank=True, null=True)),
("display_name", models.TextField(blank=True, null=True)),
],
options={
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.CreateModel(
name="FrontEndOrg",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("name", models.TextField(blank=True, null=True)),
("url", models.TextField(blank=True, null=True)),
],
options={
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AddField(
model_name="game",
name="box_art_url",
field=models.URLField(blank=True, null=True),
),
migrations.CreateModel(
name="FrontEndDropCampaign",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("account_link_url", models.URLField(blank=True, null=True)),
("about_url", models.URLField(blank=True, null=True)),
("ends_at", models.DateTimeField(null=True)),
("starts_at", models.DateTimeField(null=True)),
("channels", models.ManyToManyField(related_name="drop_campaigns", to="twitch_app.frontendchannel")),
(
"game",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.frontendgame",
),
),
],
options={
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.CreateModel(
name="FrontEndDrop",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("created_at", models.DateTimeField(null=True)),
("name", models.TextField(blank=True, null=True)),
("image_url", models.URLField(blank=True, null=True)),
("limit", models.PositiveBigIntegerField(null=True)),
("is_ios_available", models.BooleanField(null=True)),
("minutes_watched", models.PositiveBigIntegerField(null=True)),
(
"drop_campaign",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drops",
to="twitch_app.frontenddropcampaign",
),
),
],
options={
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AddField(
model_name="frontendgame",
name="org",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="games",
to="twitch_app.frontendorg",
),
),
]

View File

@ -1,3 +1,5 @@
import typing
import auto_prefetch
from django.db import models
@ -25,6 +27,7 @@ class Game(auto_prefetch.Model):
id = models.AutoField(primary_key=True)
slug = models.TextField(null=True, blank=True)
display_name = models.TextField(null=True, blank=True)
box_art_url = models.URLField(null=True, blank=True)
typename = models.TextField(null=True, blank=True)
def __str__(self) -> str:
@ -502,6 +505,11 @@ class DropCampaign(auto_prefetch.Model):
typename (str): The type name of the object, typically "DropCampaign".
"""
STATUS_CHOICES: typing.ClassVar[list[tuple[str, str]]] = [
("ACTIVE", "Active"),
("EXPIRED", "Expired"),
]
id = models.TextField(primary_key=True)
allow = auto_prefetch.ForeignKey(Allow, on_delete=models.CASCADE, related_name="drop_campaigns", null=True)
account_link_url = models.URLField(null=True, blank=True)
@ -514,9 +522,68 @@ class DropCampaign(auto_prefetch.Model):
name = models.TextField(null=True, blank=True)
owner = auto_prefetch.ForeignKey(Owner, on_delete=models.CASCADE, related_name="drop_campaigns", null=True)
starts_at = models.DateTimeField(null=True)
status = models.TextField(null=True, blank=True)
status = models.TextField(choices=STATUS_CHOICES, null=True, blank=True)
time_based_drops = models.ManyToManyField(TimeBasedDrop, related_name="drop_campaigns")
typename = models.TextField(null=True, blank=True)
def __str__(self) -> str:
return self.name or "Unknown"
class FrontEndChannel(auto_prefetch.Model):
"""This is the channel we will see on the front end."""
name = models.TextField(null=True, blank=True)
twitch_url = models.URLField(null=True, blank=True)
live = models.BooleanField(default=False)
class FrontEndOrg(auto_prefetch.Model):
"""Drops are group by organization -> by game -> by drop campaign."""
id = models.TextField(primary_key=True)
name = models.TextField(null=True, blank=True)
url = models.TextField(null=True, blank=True)
class FrontEndGame(auto_prefetch.Model):
"""This is the game we will see on the front end."""
twitch_id = models.TextField(primary_key=True)
game_url = models.URLField(null=True, blank=True)
display_name = models.TextField(null=True, blank=True)
org = models.ForeignKey(FrontEndOrg, on_delete=models.CASCADE, related_name="games", null=True)
def __str__(self) -> str:
return self.display_name or "Unknown"
class FrontEndDropCampaign(auto_prefetch.Model):
"""This is the drop campaign we will see on the front end."""
account_link_url = models.URLField(null=True, blank=True)
about_url = models.URLField(null=True, blank=True)
ends_at = models.DateTimeField(null=True)
starts_at = models.DateTimeField(null=True)
game = models.ForeignKey(FrontEndGame, on_delete=models.CASCADE, related_name="drop_campaigns", null=True)
channels = models.ManyToManyField(FrontEndChannel, related_name="drop_campaigns")
class FrontEndDrop(auto_prefetch.Model):
"""This is the drop we will see on the front end."""
id = models.TextField(primary_key=True)
created_at = models.DateTimeField(null=True)
name = models.TextField(null=True, blank=True)
image_url = models.URLField(null=True, blank=True)
drop_campaign = models.ForeignKey(FrontEndDropCampaign, on_delete=models.CASCADE, related_name="drops", null=True)
limit = models.PositiveBigIntegerField(null=True)
is_ios_available = models.BooleanField(null=True)
minutes_watched = models.PositiveBigIntegerField(null=True)