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

View File

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

View File

@ -7,15 +7,15 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href='{% url "core:games" %}'>Games</a> <a class="nav-link" href='{% url "core:games" %}'>Games</a>
</li> </li>
<li>
<a class="nav-link" href='{% url "core:reward_campaigns" %}'>Reward campaigns</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href=''>API</a> <a class="nav-link" href=''>API</a>
</li> </li>
<li class="nav-item d-none d-sm-block"> <li class="nav-item d-none d-sm-block">
<a class="nav-link" href="https://github.com/sponsors/TheLovinator1">Donate</a> <a class="nav-link" href="https://github.com/sponsors/TheLovinator1">Donate</a>
</li> </li>
<li>
<a class="nav-link" href='{% url "core:reward_campaigns" %}'>Reward campaigns</a>
</li>
</ul> </ul>
</nav> </nav>
</header> </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 %} {% block content %}
<div class="container mt-4"> <div class="container mt-4">
<div class="row"> <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"> <div class="col-lg-9">
<h2>Reward Campaigns</h2> <h2>Reward Campaigns</h2>
<div> <div>

View File

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

View File

@ -1,12 +1,13 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import hishel import hishel
from django.conf import settings from django.conf import settings
from django.db.models.manager import BaseManager
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views.generic import ListView
from core.data import WebhookData from core.data import WebhookData
from twitch_app.models import Game, RewardCampaign 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: def index(request: HttpRequest) -> HttpResponse:
"""Render the index page.""" """Render the index page."""
reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all() reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
return TemplateResponse( toc: str = build_toc([
request=request, TOCItem(name="Information", toc_id="#info-box"),
template="index.html", TOCItem(name="Games", toc_id="#games"),
context={"reward_campaigns": reward_campaigns}, ])
)
context: dict[str, BaseManager[RewardCampaign] | str] = {"reward_campaigns": reward_campaigns, "toc": toc}
return TemplateResponse(request=request, template="index.html", context=context)
class GameView(ListView): def game_view(request: HttpRequest) -> HttpResponse:
model = Game """Render the game view page."""
template_name: str = "games.html" games: BaseManager[Game] = Game.objects.all()
context_object_name: str = "games"
paginate_by = 100 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): def reward_campaign_view(request: HttpRequest) -> HttpResponse:
model = RewardCampaign """Render the reward campaign view page."""
template_name: str = "reward_campaigns.html" reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
context_object_name: str = "reward_campaigns" context: dict[str, BaseManager[RewardCampaign]] = {"reward_campaigns": reward_campaigns}
paginate_by = 100 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: if TYPE_CHECKING:
from playwright.async_api._generated import BrowserContext, Page from playwright.async_api._generated import BrowserContext, Page
import json
# Where to store the Chrome profile # Where to store the Chrome profile
data_dir = Path( 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"), "slug": json_data.get("slug"),
"display_name": json_data.get("displayName"), "display_name": json_data.get("displayName"),
"typename": json_data.get("__typename"), "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") logger.warning("No drop campaign data found")
return None, False 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( drop_campaign, _ = await DropCampaign.objects.aupdate_or_create(
id=drop_campaign_data["id"], id=drop_campaign_data["id"],
defaults={ defaults={
@ -546,16 +551,35 @@ class Command(BaseCommand):
continue continue
if "rewardCampaignsAvailableToUser" in campaign["data"]: 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) await add_reward_campaign(campaign)
if "dropCampaign" in campaign.get("data", {}).get("user", {}): if "dropCampaign" in campaign.get("data", {}).get("user", {}):
if not campaign["data"]["user"]["dropCampaign"]: if not campaign["data"]["user"]["dropCampaign"]:
logger.warning("No drop campaign found") logger.warning("No drop campaign found")
continue 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) await add_drop_campaign(campaign)
if "dropCampaigns" in campaign.get("data", {}).get("user", {}): if "dropCampaigns" in campaign.get("data", {}).get("user", {}):
for drop_campaign in campaign["data"]["user"]["dropCampaigns"]: 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) await add_drop_campaign(drop_campaign)
return json_data 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 import auto_prefetch
from django.db import models from django.db import models
@ -25,6 +27,7 @@ class Game(auto_prefetch.Model):
id = models.AutoField(primary_key=True) id = models.AutoField(primary_key=True)
slug = models.TextField(null=True, blank=True) slug = models.TextField(null=True, blank=True)
display_name = 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) typename = models.TextField(null=True, blank=True)
def __str__(self) -> str: def __str__(self) -> str:
@ -502,6 +505,11 @@ class DropCampaign(auto_prefetch.Model):
typename (str): The type name of the object, typically "DropCampaign". 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) id = models.TextField(primary_key=True)
allow = auto_prefetch.ForeignKey(Allow, on_delete=models.CASCADE, related_name="drop_campaigns", null=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) 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) name = models.TextField(null=True, blank=True)
owner = auto_prefetch.ForeignKey(Owner, on_delete=models.CASCADE, related_name="drop_campaigns", null=True) owner = auto_prefetch.ForeignKey(Owner, on_delete=models.CASCADE, related_name="drop_campaigns", null=True)
starts_at = models.DateTimeField(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") time_based_drops = models.ManyToManyField(TimeBasedDrop, related_name="drop_campaigns")
typename = models.TextField(null=True, blank=True) typename = models.TextField(null=True, blank=True)
def __str__(self) -> str: def __str__(self) -> str:
return self.name or "Unknown" 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)