This commit is contained in:
2024-07-10 01:44:33 +02:00
parent ec75ce1ccb
commit 5ebcb665c2
21 changed files with 183 additions and 1328 deletions

View File

@ -1,7 +0,0 @@
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Webhook
# https://django-simple-history.readthedocs.io/en/latest/admin.html
admin.site.register(Webhook, SimpleHistoryAdmin)

View File

@ -1,62 +0,0 @@
# Generated by Django 5.0.6 on 2024-06-30 23:42
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
initial = True
dependencies: list[tuple[str, str]] = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations: list[Operation] = [
migrations.CreateModel(
name="NotificationType",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name="DiscordSetting",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("webhook_url", models.URLField()),
("disabled", models.BooleanField(default=False)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
(
"notification_type",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="core.notificationtype",
),
),
],
),
]

View File

@ -1,26 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 01:10
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("core", "0001_initial"),
]
operations: list[Operation] = [
migrations.RemoveField(
model_name="discordsetting",
name="notification_type",
),
migrations.AddField(
model_name="discordsetting",
name="name",
field=models.CharField(default="No name", max_length=255),
preserve_default=False,
),
migrations.DeleteModel(
name="NotificationType",
),
]

View File

@ -1,53 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 03:28
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("core", "0002_remove_discordsetting_notification_type_and_more"),
("twitch_app", "0001_initial"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations: list[Operation] = [
migrations.CreateModel(
name="Subscription",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
(
"discord_webhook",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="core.discordsetting",
),
),
(
"game",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="twitch_app.game",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]

View File

@ -1,146 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 15:17
import django.db.models.deletion
import simple_history.models
from django.conf import settings
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("core", "0003_subscription"),
("twitch_app", "0003_historicaldropbenefit_historicaldropcampaign_and_more"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations: list[Operation] = [
migrations.CreateModel(
name="HistoricalDiscordSetting",
fields=[
(
"id",
models.BigIntegerField(
auto_created=True,
blank=True,
db_index=True,
verbose_name="ID",
),
),
("name", models.CharField(max_length=255)),
("webhook_url", models.URLField()),
("disabled", models.BooleanField(default=False)),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical discord setting",
"verbose_name_plural": "historical discord settings",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalSubscription",
fields=[
(
"id",
models.BigIntegerField(
auto_created=True,
blank=True,
db_index=True,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(blank=True, editable=False)),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"discord_webhook",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="core.discordsetting",
),
),
(
"game",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"user",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical subscription",
"verbose_name_plural": "historical subscriptions",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 21:47
import django.utils.timezone
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("core", "0004_historicaldiscordsetting_historicalsubscription"),
]
operations: list[Operation] = [
migrations.AddField(
model_name="discordsetting",
name="created_at",
field=models.DateTimeField(
auto_now_add=True,
default=django.utils.timezone.now,
),
preserve_default=False,
),
migrations.AddField(
model_name="historicaldiscordsetting",
name="created_at",
field=models.DateTimeField(
blank=True,
default=django.utils.timezone.now,
editable=False,
),
preserve_default=False,
),
]

View File

@ -1,26 +0,0 @@
from typing import Literal
import auto_prefetch
from django.db import models
from simple_history.models import HistoricalRecords
from twitch_app.models import Game
class Webhook(auto_prefetch.Model):
"""Webhooks to send notifications to."""
url = models.URLField(unique=True)
game = models.ForeignKey(Game, on_delete=models.CASCADE)
disabled = models.BooleanField(default=False)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta(auto_prefetch.Model.Meta):
verbose_name: str = "Webhook"
verbose_name_plural: str = "Webhooks"
ordering: tuple[Literal["url"]] = ("url",)
def __str__(self) -> str:
return self.url

View File

@ -5,6 +5,10 @@
<div class="row"> <div class="row">
<div class="col-lg-3">{% include "partials/toc.html" %}</div> <div class="col-lg-3">{% include "partials/toc.html" %}</div>
<div class="col-lg-9"> <div class="col-lg-9">
{% for org in orgs %}
{{ org }}
fsafa
{% endfor %}
{% include "partials/info_box.html" %} {% include "partials/info_box.html" %}
{% include "partials/news.html" %} {% include "partials/news.html" %}
{% for game in games %} {% for game in games %}

View File

@ -1,6 +1,5 @@
from __future__ import annotations from __future__ import annotations
import datetime
import logging import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -9,17 +8,15 @@ from django.conf import settings
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from core.data import CampaignContext, DropContext, GameContext, WebhookData from core.data import WebhookData
from twitch_app.models import ( from twitch_app.models import (
DropBenefit, Organization,
DropCampaign,
Game,
TimeBasedDrop,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from pathlib import Path from pathlib import Path
from django.db.models.manager import BaseManager
from django.http import ( from django.http import (
HttpRequest, HttpRequest,
HttpResponse, HttpResponse,
@ -44,111 +41,12 @@ controller = hishel.Controller(
) )
def fetch_campaigns(game: Game) -> list[CampaignContext]:
"""Fetch active campaigns for a given game."""
campaigns: list[CampaignContext] = []
for campaign in DropCampaign.objects.filter(
game=game,
status="ACTIVE",
end_at__gt=datetime.datetime.now(tz=datetime.UTC),
).only(
"id",
"name",
"image_url",
"status",
"account_link_url",
"description",
"details_url",
"start_at",
"end_at",
):
drops = fetch_drops(campaign)
if not drops:
logger.info("No drops found for %s", campaign.name)
continue
campaigns.append(
CampaignContext(
drop_id=campaign.id,
name=campaign.name,
image_url=campaign.image_url,
status=campaign.status,
account_link_url=campaign.account_link_url,
description=campaign.description,
details_url=campaign.details_url,
start_at=campaign.start_at,
end_at=campaign.end_at,
drops=drops,
),
)
return campaigns
def fetch_drops(campaign: DropCampaign) -> list[DropContext]:
"""Fetch drops for a given campaign."""
drops: list[DropContext] = []
drop: TimeBasedDrop
for drop in campaign.time_based_drops.all().only(
"id",
"name",
"required_minutes_watched",
"required_subs",
):
benefit: DropBenefit | None = drop.benefits.first()
image_asset_url: str = "https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/default.png"
if benefit and benefit.image_asset_url:
image_asset_url = benefit.image_asset_url
drops.append(
DropContext(
drops_id=drop.id,
image_url=image_asset_url,
name=drop.name,
required_minutes_watched=drop.required_minutes_watched,
required_subs=drop.required_subs,
),
)
return drops
def sort_games_by_campaign_start(list_of_games: list[GameContext]) -> list[GameContext]:
"""Sort games by the start date of the first campaign and reverse the list so the latest games are first."""
if list_of_games and list_of_games[0].campaigns:
list_of_games.sort(
key=lambda x: x.campaigns[0].start_at
if x.campaigns and x.campaigns[0].start_at is not None
else datetime.datetime.min,
)
list_of_games.reverse()
return list_of_games
def get_webhooks(request: HttpRequest) -> list[str]: def get_webhooks(request: HttpRequest) -> list[str]:
"""Get the webhooks from the cookie.""" """Get the webhooks from the cookie."""
cookie: str = request.COOKIES.get("webhooks", "") cookie: str = request.COOKIES.get("webhooks", "")
return list(filter(None, cookie.split(","))) return list(filter(None, cookie.split(",")))
def prepare_game_contexts() -> list[GameContext]:
"""Prepare game contexts with their respective campaigns and drops."""
list_of_games: list[GameContext] = []
for game in list(Game.objects.all().only("id", "image_url", "display_name", "slug")):
campaigns: list[CampaignContext] = fetch_campaigns(game)
if not campaigns:
continue
list_of_games.append(
GameContext(
game_id=game.id,
campaigns=campaigns,
image_url=game.image_url,
display_name=game.display_name,
twitch_url=game.twitch_url,
),
)
return list_of_games
def get_avatar(webhook_response: Response) -> str: def get_avatar(webhook_response: Response) -> str:
"""Get the avatar URL from the webhook response.""" """Get the avatar URL from the webhook response."""
avatar: str = "https://cdn.discordapp.com/embed/avatars/0.png" avatar: str = "https://cdn.discordapp.com/embed/avatars/0.png"
@ -173,12 +71,11 @@ def get_webhook_data(webhook: str) -> WebhookData:
def index(request: HttpRequest) -> HttpResponse: def index(request: HttpRequest) -> HttpResponse:
"""Render the index page.""" """Render the index page."""
list_of_games: list[GameContext] = prepare_game_contexts() orgs: BaseManager[Organization] = Organization.objects.all()
sorted_list_of_games: list[GameContext] = sort_games_by_campaign_start(list_of_games)
webhooks: list[WebhookData] = [get_webhook_data(webhook) for webhook in get_webhooks(request)] webhooks: list[WebhookData] = [get_webhook_data(webhook) for webhook in get_webhooks(request)]
return TemplateResponse( return TemplateResponse(
request=request, request=request,
template="index.html", template="index.html",
context={"games": sorted_list_of_games, "webhooks": webhooks}, context={"orgs": orgs, "webhooks": webhooks},
) )

View File

@ -1,11 +1,10 @@
from django.contrib import admin from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin from simple_history.admin import SimpleHistoryAdmin
from .models import DropBenefit, DropCampaign, Game, Organization, TimeBasedDrop from .models import Drop, DropCampaign, Game, Organization
# https://django-simple-history.readthedocs.io/en/latest/admin.html # https://django-simple-history.readthedocs.io/en/latest/admin.html
admin.site.register(DropBenefit, SimpleHistoryAdmin) admin.site.register(Drop, SimpleHistoryAdmin)
admin.site.register(DropCampaign, SimpleHistoryAdmin) admin.site.register(DropCampaign, SimpleHistoryAdmin)
admin.site.register(Game, SimpleHistoryAdmin) admin.site.register(Game, SimpleHistoryAdmin)
admin.site.register(Organization, SimpleHistoryAdmin) admin.site.register(Organization, SimpleHistoryAdmin)
admin.site.register(TimeBasedDrop, SimpleHistoryAdmin)

View File

@ -5,11 +5,10 @@ from django.http import HttpRequest
from ninja import Router, Schema from ninja import Router, Schema
from .models import ( from .models import (
DropBenefit, Drop,
DropCampaign, DropCampaign,
Game, Game,
Organization, Organization,
TimeBasedDrop,
) )
router = Router( router = Router(
@ -98,13 +97,6 @@ def get_games(request: HttpRequest) -> BaseManager[Game]: # noqa: ARG001
return Game.objects.all() return Game.objects.all()
# http://localhost:8000/api/twitch/drop_benefits
@router.get("/drop_benefits", response=list[DropBenefitSchema])
def get_drop_benefits(request: HttpRequest) -> BaseManager[DropBenefit]: # noqa: ARG001
"""Get all drop benefits."""
return DropBenefit.objects.all()
# http://localhost:8000/api/twitch/drop_campaigns # http://localhost:8000/api/twitch/drop_campaigns
@router.get("/drop_campaigns", response=list[DropCampaignSchema]) @router.get("/drop_campaigns", response=list[DropCampaignSchema])
def get_drop_campaigns(request: HttpRequest) -> BaseManager[DropCampaign]: # noqa: ARG001 def get_drop_campaigns(request: HttpRequest) -> BaseManager[DropCampaign]: # noqa: ARG001
@ -112,8 +104,8 @@ def get_drop_campaigns(request: HttpRequest) -> BaseManager[DropCampaign]: # no
return DropCampaign.objects.all() return DropCampaign.objects.all()
# http://localhost:8000/api/twitch/time_based_drops # http://localhost:8000/api/twitch/drops
@router.get("/time_based_drops", response=list[TimeBasedDropSchema]) @router.get("/drops", response=list[TimeBasedDropSchema])
def get_time_based_drops(request: HttpRequest) -> BaseManager[TimeBasedDrop]: # noqa: ARG001 def get_drops(request: HttpRequest) -> BaseManager[Drop]: # noqa: ARG001
"""Get all time-based drops.""" """Get all time-based drops."""
return TimeBasedDrop.objects.all() return Drop.objects.all()

View File

@ -11,11 +11,10 @@ from playwright.async_api import Playwright, async_playwright
from playwright.async_api._generated import Response from playwright.async_api._generated import Response
from twitch_app.models import ( from twitch_app.models import (
DropBenefit, Drop,
DropCampaign, DropCampaign,
Game, Game,
Organization, Organization,
TimeBasedDrop,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -35,10 +34,10 @@ if not data_dir:
msg = "DATA_DIR is not set in settings.py" msg = "DATA_DIR is not set in settings.py"
raise ValueError(msg) raise ValueError(msg)
logger: logging.Logger = logging.getLogger("twitch.management.commands.scrape_twitch") logger: logging.Logger = logging.getLogger(__name__)
async def insert_data(data: dict) -> None: # noqa: PLR0914, C901 async def insert_data(data: dict) -> None: # noqa: C901, PLR0914
"""Insert data into the database. """Insert data into the database.
Args: Args:
@ -52,6 +51,7 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
user_data["id"] user_data["id"]
drop_campaign_data = user_data["dropCampaign"] drop_campaign_data = user_data["dropCampaign"]
if not drop_campaign_data: if not drop_campaign_data:
logger.debug("No drop campaign data found")
return return
# Create or get the organization # Create or get the organization
@ -62,6 +62,8 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
) )
if created: if created:
logger.debug("Organization created: %s", owner) logger.debug("Organization created: %s", owner)
else:
logger.debug("Organization found: %s", owner)
# Create or get the game # Create or get the game
game_data = drop_campaign_data["game"] game_data = drop_campaign_data["game"]
@ -70,6 +72,7 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
defaults={ defaults={
"slug": game_data["slug"], "slug": game_data["slug"],
"display_name": game_data["displayName"], "display_name": game_data["displayName"],
"organization": owner,
}, },
) )
if created: if created:
@ -79,16 +82,15 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
drop_campaign, created = await sync_to_async(DropCampaign.objects.get_or_create)( drop_campaign, created = await sync_to_async(DropCampaign.objects.get_or_create)(
id=drop_campaign_data["id"], id=drop_campaign_data["id"],
defaults={ defaults={
"account_link_url": drop_campaign_data["accountLinkURL"], "account_link_url": drop_campaign_data.get("accountLinkURL"),
"description": drop_campaign_data["description"], "description": drop_campaign_data.get("description"),
"details_url": drop_campaign_data["detailsURL"], "details_url": drop_campaign_data.get("detailsURL"),
"end_at": drop_campaign_data["endAt"], "end_at": drop_campaign_data.get("endAt"),
"image_url": drop_campaign_data["imageURL"], "image_url": drop_campaign_data.get("imageURL"),
"name": drop_campaign_data["name"], "name": drop_campaign_data.get("name"),
"start_at": drop_campaign_data["startAt"], "start_at": drop_campaign_data.get("startAt"),
"status": drop_campaign_data["status"], "status": drop_campaign_data.get("status"),
"game": game, "game": game,
"owner": owner,
}, },
) )
if created: if created:
@ -97,62 +99,58 @@ async def insert_data(data: dict) -> None: # noqa: PLR0914, C901
# Create time-based drops # Create time-based drops
for drop_data in drop_campaign_data["timeBasedDrops"]: for drop_data in drop_campaign_data["timeBasedDrops"]:
drop_benefit_edges = drop_data["benefitEdges"] drop_benefit_edges = drop_data["benefitEdges"]
drop_benefits = []
time_based_drop, created = await sync_to_async(Drop.objects.get_or_create)(
id=drop_data["id"],
defaults={
"required_subs": drop_data.get("requiredSubs"),
"end_at": drop_data.get("endAt"),
"name": drop_data.get("name"),
"required_minutes_watched": drop_data.get("requiredMinutesWatched"),
"start_at": drop_data.get("startAt"),
"drop_campaign": drop_campaign,
},
)
if created:
logger.debug("Time-based drop created: %s", time_based_drop)
for edge in drop_benefit_edges: for edge in drop_benefit_edges:
benefit_data = edge["benefit"] benefit_data = edge["benefit"]
benefit_owner_data = benefit_data["ownerOrganization"] benefit_owner_data = benefit_data["ownerOrganization"]
benefit_owner, created = await sync_to_async( org, created = await sync_to_async(
Organization.objects.get_or_create, Organization.objects.get_or_create,
)( )(
id=benefit_owner_data["id"], id=benefit_owner_data["id"],
defaults={"name": benefit_owner_data["name"]}, defaults={"name": benefit_owner_data["name"]},
) )
if created: if created:
logger.debug("Benefit owner created: %s", benefit_owner) logger.debug("Organization created: %s", org)
benefit_game_data = benefit_data["game"] benefit_game_data = benefit_data["game"]
benefit_game, created = await sync_to_async(Game.objects.get_or_create)( benefit_game, created = await sync_to_async(Game.objects.get_or_create)(
id=benefit_game_data["id"], id=benefit_game_data["id"],
defaults={"name": benefit_game_data["name"]}, defaults={"display_name": benefit_game_data["name"]},
) )
if created: if created:
logger.debug("Benefit game created: %s", benefit_game) logger.debug("Benefit game created: %s", benefit_game)
benefit, created = await sync_to_async(DropBenefit.objects.get_or_create)( # Get the drop to add the data to
id=benefit_data["id"], drop, created = await sync_to_async(Drop.objects.get_or_create)(
id=drop_data["id"],
defaults={ defaults={
"created_at": benefit_data["createdAt"], "created_at": benefit_data.get("createdAt"),
"entitlement_limit": benefit_data["entitlementLimit"], "entitlement_limit": benefit_data.get("entitlementLimit"),
"image_asset_url": benefit_data["imageAssetURL"], "image_asset_url": benefit_data.get("imageAssetURL"),
"is_ios_available": benefit_data["isIosAvailable"], "is_ios_available": benefit_data.get("isIosAvailable"),
"name": benefit_data["name"], "name": benefit_data.get("name"),
"owner_organization": benefit_owner, "owner_organization": org,
"game": benefit_game, "game": benefit_game,
}, },
) )
drop_benefits.append(benefit)
if created:
logger.debug("Benefit created: %s", benefit)
time_based_drop, created = await sync_to_async(
TimeBasedDrop.objects.get_or_create,
)(
id=drop_data["id"],
defaults={
"required_subs": drop_data["requiredSubs"],
"end_at": drop_data["endAt"],
"name": drop_data["name"],
"required_minutes_watched": drop_data["requiredMinutesWatched"],
"start_at": drop_data["startAt"],
},
)
await sync_to_async(time_based_drop.benefits.set)(drop_benefits)
await sync_to_async(drop_campaign.time_based_drops.add)(time_based_drop)
if created: if created:
logger.debug("Time-based drop created: %s", time_based_drop) logger.debug("Drop created: %s", drop)
class Command(BaseCommand): class Command(BaseCommand):
@ -162,18 +160,20 @@ class Command(BaseCommand):
self, self,
playwright: Playwright, playwright: Playwright,
) -> list[dict[str, typing.Any]]: ) -> list[dict[str, typing.Any]]:
profile_dir: Path = Path(data_dir / "firefox-profile") profile_dir: Path = Path(data_dir / "chrome-profile")
profile_dir.mkdir(parents=True, exist_ok=True) profile_dir.mkdir(parents=True, exist_ok=True)
logger.debug( logger.debug(
"Launching Firefox browser with user data directory: %s", "Launching Chrome browser with user data directory: %s",
profile_dir, profile_dir,
) )
browser: BrowserContext = await playwright.firefox.launch_persistent_context( browser: BrowserContext = await playwright.chromium.launch_persistent_context(
user_data_dir=profile_dir, user_data_dir=profile_dir,
headless=True, headless=False,
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", # noqa: E501
viewport={"width": 1920, "height": 1080},
) )
logger.debug("Launched Firefox browser") logger.debug("Launched Chrome browser")
page: Page = await browser.new_page() page: Page = await browser.new_page()
json_data: list[dict] = [] json_data: list[dict] = []
@ -198,7 +198,7 @@ class Command(BaseCommand):
try: try:
await page.wait_for_selector( await page.wait_for_selector(
'div[data-a-target="top-nav-avatar"]', 'div[data-a-target="top-nav-avatar"]',
timeout=30000, timeout=300000,
) )
logged_in = True logged_in = True
logger.info("Logged in to Twitch") logger.info("Logged in to Twitch")
@ -211,6 +211,9 @@ class Command(BaseCommand):
await page.wait_for_load_state("networkidle") await page.wait_for_load_state("networkidle")
logger.debug("Page loaded. Scraping data...") logger.debug("Page loaded. Scraping data...")
# Wait 5 seconds for the page to load
await asyncio.sleep(5)
await browser.close() await browser.close()
for num, campaign in enumerate(json_data, start=1): for num, campaign in enumerate(json_data, start=1):

View File

@ -1,17 +1,45 @@
# Generated by Django 5.0.6 on 2024-07-01 00:08 # Generated by Django 5.1b1 on 2024-07-09 22:26
import auto_prefetch
import django.db.models.deletion import django.db.models.deletion
import django.db.models.functions.text import django.db.models.functions.text
import django.db.models.manager
from django.db import migrations, models from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration): class Migration(migrations.Migration):
initial = True initial = True
dependencies: list[tuple[str, str]] = [] dependencies = []
operations: list[Operation] = [ operations = [
migrations.CreateModel(
name="DropCampaign",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("account_link_url", models.URLField(blank=True, null=True)),
("description", models.TextField(blank=True, null=True)),
("details_url", models.URLField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)),
("image_url", models.URLField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("start_at", models.DateTimeField(blank=True, null=True)),
("status", models.TextField(blank=True, null=True)),
("added_at", models.DateTimeField(auto_now_add=True, null=True)),
("modified_at", models.DateTimeField(auto_now=True, null=True)),
],
options={
"verbose_name": "Drop Campaign",
"verbose_name_plural": "Drop Campaigns",
"ordering": ("name",),
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.CreateModel( migrations.CreateModel(
name="Game", name="Game",
fields=[ fields=[
@ -28,10 +56,33 @@ class Migration(migrations.Migration):
output_field=models.TextField(), output_field=models.TextField(),
), ),
), ),
(
"image_url",
models.GeneratedField( # type: ignore # noqa: PGH003
db_persist=True,
expression=django.db.models.functions.text.Concat(
models.Value("https://static-cdn.jtvnw.net/ttv-boxart/"),
"id",
models.Value("_IGDB.jpg"),
),
output_field=models.URLField(),
),
),
("display_name", models.TextField(blank=True, null=True)), ("display_name", models.TextField(blank=True, null=True)),
("added_at", models.DateTimeField(auto_now_add=True, null=True)), ("added_at", models.DateTimeField(auto_now_add=True, null=True)),
("modified_at", models.DateTimeField(auto_now=True, null=True)), ("modified_at", models.DateTimeField(auto_now=True, null=True)),
], ],
options={
"verbose_name": "Game",
"verbose_name_plural": "Games",
"ordering": ("display_name",),
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name="Organization", name="Organization",
@ -41,97 +92,69 @@ class Migration(migrations.Migration):
("added_at", models.DateTimeField(auto_now_add=True, null=True)), ("added_at", models.DateTimeField(auto_now_add=True, null=True)),
("modified_at", models.DateTimeField(auto_now=True, null=True)), ("modified_at", models.DateTimeField(auto_now=True, null=True)),
], ],
options={
"verbose_name": "Organization",
"verbose_name_plural": "Organizations",
"ordering": ("name",),
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name="DropBenefit", name="Drop",
fields=[ fields=[
("id", models.TextField(primary_key=True, serialize=False)), ("id", models.TextField(primary_key=True, serialize=False)),
("created_at", models.DateTimeField(blank=True, null=True)), ("created_at", models.DateTimeField(blank=True, null=True)),
("entitlement_limit", models.IntegerField(blank=True, null=True)), ("entitlement_limit", models.IntegerField(blank=True, null=True)),
("image_asset_url", models.URLField(blank=True, null=True)), ("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)), ("name", models.TextField(blank=True, null=True)),
("added_at", models.DateTimeField(auto_now_add=True, null=True)), ("added_at", models.DateTimeField(auto_now_add=True, null=True)),
("modified_at", models.DateTimeField(auto_now=True, null=True)), ("modified_at", models.DateTimeField(auto_now=True, null=True)),
(
"game",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="twitch_app.game",
),
),
(
"owner_organization",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="twitch_app.organization",
),
),
],
),
migrations.CreateModel(
name="TimeBasedDrop",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("required_subs", models.IntegerField(blank=True, null=True)), ("required_subs", models.IntegerField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)), ("end_at", models.DateTimeField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)), ("required_minutes_watched", models.IntegerField(blank=True, null=True)),
(
"required_minutes_watched",
models.IntegerField(blank=True, null=True),
),
("start_at", models.DateTimeField(blank=True, null=True)), ("start_at", models.DateTimeField(blank=True, null=True)),
("added_at", models.DateTimeField(auto_now_add=True, null=True)), (
("modified_at", models.DateTimeField(auto_now=True, null=True)), "drop_campaign",
("benefits", models.ManyToManyField(to="twitch_app.dropbenefit")), auto_prefetch.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="drops",
to="twitch_app.dropcampaign",
),
),
],
options={
"verbose_name": "Drop",
"verbose_name_plural": "Drops",
"ordering": ("name",),
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
], ],
), ),
migrations.CreateModel( migrations.AddField(
name="DropCampaign", model_name="dropcampaign",
fields=[ name="game",
("id", models.TextField(primary_key=True, serialize=False)), field=auto_prefetch.ForeignKey(
("account_link_url", models.URLField(blank=True, null=True)),
("description", models.TextField(blank=True, null=True)),
("details_url", models.URLField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)),
("image_url", models.URLField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("start_at", models.DateTimeField(blank=True, null=True)),
("status", models.TextField(blank=True, null=True)),
("added_at", models.DateTimeField(auto_now_add=True, null=True)),
("modified_at", models.DateTimeField(auto_now=True, null=True)),
(
"game",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns", related_name="drop_campaigns",
to="twitch_app.game", to="twitch_app.game",
), ),
), ),
( migrations.AddField(
"owner", model_name="game",
models.ForeignKey( name="organization",
field=auto_prefetch.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns", related_name="games",
to="twitch_app.organization", to="twitch_app.organization",
), ),
), ),
(
"time_based_drops",
models.ManyToManyField(to="twitch_app.timebaseddrop"),
),
],
),
migrations.CreateModel(
name="User",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("added_at", models.DateTimeField(auto_now_add=True, null=True)),
("modified_at", models.DateTimeField(auto_now=True, null=True)),
(
"drop_campaigns",
models.ManyToManyField(to="twitch_app.dropcampaign"),
),
],
),
] ]

View File

@ -1,27 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 03:49
import django.db.models.functions.text
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0001_initial"),
]
operations: list[Operation] = [
migrations.AddField(
model_name="game",
name="image_url",
field=models.GeneratedField( # type: ignore # noqa: PGH003
db_persist=True,
expression=django.db.models.functions.text.Concat(
models.Value("https://static-cdn.jtvnw.net/ttv-boxart/"),
"id",
models.Value("_IGDB.jpg"),
),
output_field=models.URLField(),
),
),
]

View File

@ -1,308 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 15:17
import django.db.models.deletion
import django.db.models.functions.text
import simple_history.models
from django.conf import settings
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0002_game_image_url"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations: list[Operation] = [
migrations.CreateModel(
name="HistoricalDropBenefit",
fields=[
("id", models.TextField(db_index=True)),
("created_at", models.DateTimeField(blank=True, null=True)),
("entitlement_limit", models.IntegerField(blank=True, null=True)),
("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"game",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"owner_organization",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.organization",
),
),
],
options={
"verbose_name": "historical drop benefit",
"verbose_name_plural": "historical drop benefits",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalDropCampaign",
fields=[
("id", models.TextField(db_index=True)),
("account_link_url", models.URLField(blank=True, null=True)),
("description", models.TextField(blank=True, null=True)),
("details_url", models.URLField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)),
("image_url", models.URLField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("start_at", models.DateTimeField(blank=True, null=True)),
("status", models.TextField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"game",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
(
"owner",
models.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.organization",
),
),
],
options={
"verbose_name": "historical drop campaign",
"verbose_name_plural": "historical drop campaigns",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalGame",
fields=[
("id", models.TextField(db_index=True)),
("slug", models.TextField(blank=True, null=True)),
(
"twitch_url",
models.GeneratedField( # type: ignore # noqa: PGH003
db_persist=True,
expression=django.db.models.functions.text.Concat(
models.Value("https://www.twitch.tv/directory/category/"),
"slug",
),
output_field=models.TextField(),
),
),
(
"image_url",
models.GeneratedField( # type: ignore # noqa: PGH003
db_persist=True,
expression=django.db.models.functions.text.Concat(
models.Value("https://static-cdn.jtvnw.net/ttv-boxart/"),
"id",
models.Value("_IGDB.jpg"),
),
output_field=models.URLField(),
),
),
("display_name", models.TextField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical game",
"verbose_name_plural": "historical games",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalTimeBasedDrop",
fields=[
("id", models.TextField(db_index=True)),
("required_subs", models.IntegerField(blank=True, null=True)),
("end_at", models.DateTimeField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
(
"required_minutes_watched",
models.IntegerField(blank=True, null=True),
),
("start_at", models.DateTimeField(blank=True, null=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical time based drop",
"verbose_name_plural": "historical time based drops",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name="HistoricalUser",
fields=[
("id", models.TextField(db_index=True)),
(
"added_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
(
"modified_at",
models.DateTimeField(blank=True, editable=False, null=True),
),
("history_id", models.AutoField(primary_key=True, serialize=False)),
("history_date", models.DateTimeField(db_index=True)),
("history_change_reason", models.CharField(max_length=100, null=True)),
(
"history_type",
models.CharField(
choices=[("+", "Created"), ("~", "Changed"), ("-", "Deleted")],
max_length=1,
),
),
(
"history_user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="+",
to=settings.AUTH_USER_MODEL,
),
),
],
options={
"verbose_name": "historical user",
"verbose_name_plural": "historical users",
"ordering": ("-history_date", "-history_id"),
"get_latest_by": ("history_date", "history_id"),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 15:23
from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0003_historicaldropbenefit_historicaldropcampaign_and_more"),
]
operations: list[Operation] = [
migrations.RemoveField(
model_name="user",
name="drop_campaigns",
),
migrations.DeleteModel(
name="HistoricalUser",
),
migrations.DeleteModel(
name="User",
),
]

View File

@ -1,92 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 15:40
from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
(
"twitch_app",
"0004_remove_user_drop_campaigns_delete_historicaluser_and_more",
),
]
operations: list[Operation] = [
migrations.AlterModelOptions(
name="dropbenefit",
options={
"ordering": ("name",),
"verbose_name": "Drop Benefit",
"verbose_name_plural": "Drop Benefits",
},
),
migrations.AlterModelOptions(
name="dropcampaign",
options={
"ordering": ("name",),
"verbose_name": "Drop Campaign",
"verbose_name_plural": "Drop Campaigns",
},
),
migrations.AlterModelOptions(
name="game",
options={
"ordering": ("display_name",),
"verbose_name": "Game",
"verbose_name_plural": "Games",
},
),
migrations.AlterModelOptions(
name="historicaldropbenefit",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Drop Benefit",
"verbose_name_plural": "historical Drop Benefits",
},
),
migrations.AlterModelOptions(
name="historicaldropcampaign",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Drop Campaign",
"verbose_name_plural": "historical Drop Campaigns",
},
),
migrations.AlterModelOptions(
name="historicalgame",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Game",
"verbose_name_plural": "historical Games",
},
),
migrations.AlterModelOptions(
name="historicaltimebaseddrop",
options={
"get_latest_by": ("history_date", "history_id"),
"ordering": ("-history_date", "-history_id"),
"verbose_name": "historical Time-Based Drop",
"verbose_name_plural": "historical Time-Based Drops",
},
),
migrations.AlterModelOptions(
name="organization",
options={
"ordering": ("name",),
"verbose_name": "Organization",
"verbose_name_plural": "Organizations",
},
),
migrations.AlterModelOptions(
name="timebaseddrop",
options={
"ordering": ("name",),
"verbose_name": "Time-Based Drop",
"verbose_name_plural": "Time-Based Drops",
},
),
]

View File

@ -1,172 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-01 15:44
import auto_prefetch
import django.db.models.deletion
import django.db.models.manager
from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0005_alter_dropbenefit_options_alter_dropcampaign_options_and_more"),
]
operations: list[Operation] = [
migrations.AlterModelOptions(
name="dropbenefit",
options={
"base_manager_name": "prefetch_manager",
"ordering": ("name",),
"verbose_name": "Drop Benefit",
"verbose_name_plural": "Drop Benefits",
},
),
migrations.AlterModelOptions(
name="dropcampaign",
options={
"base_manager_name": "prefetch_manager",
"ordering": ("name",),
"verbose_name": "Drop Campaign",
"verbose_name_plural": "Drop Campaigns",
},
),
migrations.AlterModelOptions(
name="game",
options={
"base_manager_name": "prefetch_manager",
"ordering": ("display_name",),
"verbose_name": "Game",
"verbose_name_plural": "Games",
},
),
migrations.AlterModelOptions(
name="organization",
options={
"base_manager_name": "prefetch_manager",
"ordering": ("name",),
"verbose_name": "Organization",
"verbose_name_plural": "Organizations",
},
),
migrations.AlterModelOptions(
name="timebaseddrop",
options={
"base_manager_name": "prefetch_manager",
"ordering": ("name",),
"verbose_name": "Time-Based Drop",
"verbose_name_plural": "Time-Based Drops",
},
),
migrations.AlterModelManagers(
name="dropbenefit",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="dropcampaign",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="game",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="organization",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="timebaseddrop",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterField(
model_name="dropbenefit",
name="game",
field=auto_prefetch.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="twitch_app.game"),
),
migrations.AlterField(
model_name="dropbenefit",
name="owner_organization",
field=auto_prefetch.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="twitch_app.organization"),
),
migrations.AlterField(
model_name="dropcampaign",
name="game",
field=auto_prefetch.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="dropcampaign",
name="owner",
field=auto_prefetch.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.organization",
),
),
migrations.AlterField(
model_name="historicaldropbenefit",
name="game",
field=auto_prefetch.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="historicaldropbenefit",
name="owner_organization",
field=auto_prefetch.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.organization",
),
),
migrations.AlterField(
model_name="historicaldropcampaign",
name="game",
field=auto_prefetch.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="historicaldropcampaign",
name="owner",
field=auto_prefetch.ForeignKey(
blank=True,
db_constraint=False,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name="+",
to="twitch_app.organization",
),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-02 17:11
from django.db import migrations, models
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0006_alter_dropbenefit_options_alter_dropcampaign_options_and_more"),
]
operations: list[Operation] = [
migrations.AlterField(
model_name="dropcampaign",
name="time_based_drops",
field=models.ManyToManyField(related_name="drop_campaigns", to="twitch_app.timebaseddrop"),
),
migrations.AlterField(
model_name="timebaseddrop",
name="benefits",
field=models.ManyToManyField(related_name="time_based_drops", to="twitch_app.dropbenefit"),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 5.0.6 on 2024-07-03 21:19
import auto_prefetch
import django.db.models.deletion
from django.db import migrations
from django.db.migrations.operations.base import Operation
class Migration(migrations.Migration):
dependencies: list[tuple[str, str]] = [
("twitch_app", "0007_alter_dropcampaign_time_based_drops_and_more"),
]
operations: list[Operation] = [
migrations.AlterField(
model_name="dropbenefit",
name="game",
field=auto_prefetch.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_benefits",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="dropbenefit",
name="owner_organization",
field=auto_prefetch.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_benefits",
to="twitch_app.organization",
),
),
]

View File

@ -3,10 +3,7 @@ from typing import Literal
import auto_prefetch import auto_prefetch
from django.db import models from django.db import models
from django.db.models import Value from django.db.models import Value
from django.db.models.functions import ( from django.db.models.functions import Concat
Concat,
)
from simple_history.models import HistoricalRecords
class Organization(auto_prefetch.Model): class Organization(auto_prefetch.Model):
@ -35,6 +32,11 @@ class Game(auto_prefetch.Model):
For example, MultiVersus. For example, MultiVersus.
""" """
organization = auto_prefetch.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="games",
)
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
slug = models.TextField(blank=True, null=True) slug = models.TextField(blank=True, null=True)
twitch_url = models.GeneratedField( # type: ignore # noqa: PGH003 twitch_url = models.GeneratedField( # type: ignore # noqa: PGH003
@ -54,7 +56,6 @@ class Game(auto_prefetch.Model):
display_name = models.TextField(blank=True, null=True) display_name = models.TextField(blank=True, null=True)
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta(auto_prefetch.Model.Meta): class Meta(auto_prefetch.Model.Meta):
verbose_name: str = "Game" verbose_name: str = "Game"
@ -65,55 +66,29 @@ class Game(auto_prefetch.Model):
return self.display_name or self.slug or self.id return self.display_name or self.slug or self.id
class DropBenefit(auto_prefetch.Model): class Drop(auto_prefetch.Model):
"""Information about the drop.""" """The actual drop that is being given out."""
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
created_at = models.DateTimeField(blank=True, null=True) created_at = models.DateTimeField(blank=True, null=True)
entitlement_limit = models.IntegerField(blank=True, null=True) entitlement_limit = models.IntegerField(blank=True, null=True)
image_asset_url = models.URLField(blank=True, null=True) image_asset_url = models.URLField(blank=True, null=True)
is_ios_available = models.BooleanField(blank=True, null=True)
name = models.TextField(blank=True, null=True) name = models.TextField(blank=True, null=True)
owner_organization = auto_prefetch.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="drop_benefits",
)
game = auto_prefetch.ForeignKey(Game, on_delete=models.CASCADE, related_name="drop_benefits")
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta(auto_prefetch.Model.Meta):
verbose_name: str = "Drop Benefit"
verbose_name_plural: str = "Drop Benefits"
ordering: tuple[Literal["name"]] = ("name",)
def __str__(self) -> str:
return f"{self.owner_organization.name} - {self.game.display_name} - {self.name}"
class TimeBasedDrop(auto_prefetch.Model):
"""The actual drop that is being given out."""
id = models.TextField(primary_key=True)
required_subs = models.IntegerField(blank=True, null=True) required_subs = models.IntegerField(blank=True, null=True)
end_at = models.DateTimeField(blank=True, null=True) end_at = models.DateTimeField(blank=True, null=True)
name = models.TextField(blank=True, null=True)
required_minutes_watched = models.IntegerField(blank=True, null=True) required_minutes_watched = models.IntegerField(blank=True, null=True)
start_at = models.DateTimeField(blank=True, null=True) start_at = models.DateTimeField(blank=True, null=True)
benefits = models.ManyToManyField(DropBenefit, related_name="time_based_drops") drop_campaign = auto_prefetch.ForeignKey("DropCampaign", on_delete=models.CASCADE, related_name="drops")
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta(auto_prefetch.Model.Meta): class Meta(auto_prefetch.Model.Meta):
verbose_name: str = "Time-Based Drop" verbose_name: str = "Drop"
verbose_name_plural: str = "Time-Based Drops" verbose_name_plural: str = "Drops"
ordering: tuple[Literal["name"]] = ("name",) ordering: tuple[Literal["name"]] = ("name",)
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.benefits.first()} - {self.name}" return f"{self.name}"
class DropCampaign(auto_prefetch.Model): class DropCampaign(auto_prefetch.Model):
@ -136,15 +111,8 @@ class DropCampaign(auto_prefetch.Model):
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name="drop_campaigns", related_name="drop_campaigns",
) )
owner = auto_prefetch.ForeignKey(
Organization,
on_delete=models.CASCADE,
related_name="drop_campaigns",
)
time_based_drops = models.ManyToManyField(TimeBasedDrop, related_name="drop_campaigns")
added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True) added_at = models.DateTimeField(blank=True, null=True, auto_now_add=True)
modified_at = models.DateTimeField(blank=True, null=True, auto_now=True) modified_at = models.DateTimeField(blank=True, null=True, auto_now=True)
history = HistoricalRecords()
class Meta(auto_prefetch.Model.Meta): class Meta(auto_prefetch.Model.Meta):
verbose_name: str = "Drop Campaign" verbose_name: str = "Drop Campaign"
@ -152,4 +120,4 @@ class DropCampaign(auto_prefetch.Model):
ordering: tuple[Literal["name"]] = ("name",) ordering: tuple[Literal["name"]] = ("name",)
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.owner.name} - {self.game.display_name} - {self.name}" return f"{self.game.display_name} - {self.name}"