WIP 2024-08-10

This commit is contained in:
2024-08-10 21:31:21 +02:00
parent 09b21d4f43
commit 99b48bc3f6
36 changed files with 835 additions and 2081 deletions

View File

@ -43,7 +43,7 @@ repos:
# An extremely fast Python linter and formatter. # An extremely fast Python linter and formatter.
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.5 rev: v0.5.7
hooks: hooks:
- id: ruff-format - id: ruff-format
- id: ruff - id: ruff

View File

@ -17,6 +17,7 @@
"Stresss", "Stresss",
"ttvdrops", "ttvdrops",
"ulimits", "ulimits",
"Valair",
"xdefiant" "xdefiant"
] ]
} }

View File

@ -1,14 +0,0 @@
import logging
from debug_toolbar.toolbar import debug_toolbar_urls
from django.urls import URLPattern, include, path
from django.urls.resolvers import URLResolver
logger: logging.Logger = logging.getLogger(__name__)
app_name: str = "config"
urlpatterns: list[URLPattern | URLResolver] = [
path(route="", view=include(arg="core.urls")),
*debug_toolbar_urls(),
]

View File

@ -2,5 +2,5 @@ from django.apps import AppConfig
class CoreConfig(AppConfig): class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField" default_auto_field: str = "django.db.models.BigAutoField"
name = "core" name = "core"

View File

@ -1,4 +1,5 @@
import asyncio import asyncio
import json
import logging import logging
import typing import typing
from datetime import datetime from datetime import datetime
@ -11,7 +12,7 @@ from platformdirs import user_data_dir
from playwright.async_api import Playwright, async_playwright from playwright.async_api import Playwright, async_playwright
from playwright.async_api._generated import Response from playwright.async_api._generated import Response
from twitch_app.models import ( from core.models import (
Allow, Allow,
Benefit, Benefit,
BenefitEdge, BenefitEdge,
@ -28,37 +29,38 @@ 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
data_dir = Path(
user_data_dir(
appname="TTVDrops",
appauthor="TheLovinator",
roaming=True,
ensure_exists=True,
),
)
if not data_dir:
msg = "DATA_DIR is not set in settings.py"
raise ValueError(msg)
logger: logging.Logger = logging.getLogger(__name__) logger: logging.Logger = logging.getLogger(__name__)
async def add_or_get_game(json_data: dict, name: str) -> tuple[Game | None, bool]: def get_data_dir() -> Path:
"""Get the data directory.
Returns:
Path: The data directory.
"""
return Path(
user_data_dir(
appname="TTVDrops",
appauthor="TheLovinator",
roaming=True,
ensure_exists=True,
),
)
async def add_or_get_game(json_data: dict | None) -> tuple[Game | None, bool]:
"""Add or get Game from JSON data. """Add or get Game from JSON data.
Args: Args:
json_data (dict): JSON data to add to the database. json_data (dict): JSON data to add to the database.
name (str): Name of the drop campaign.
Returns: Returns:
tuple[Game | None, bool]: Game instance and whether it was created. tuple[Game | None, bool]: Game instance and whether it was created.
""" """
if not json_data: if not json_data:
logger.warning("%s is not for a game?", name) logger.warning("Couldn't find game data, probably a reward campaign?")
return None, False return None, False
game, created = await Game.objects.aupdate_or_create( game, created = await Game.objects.aupdate_or_create(
@ -71,21 +73,23 @@ async def add_or_get_game(json_data: dict, name: str) -> tuple[Game | None, bool
}, },
) )
if created:
logger.info("Found new game: %s", game.display_name or "Unknown Game")
return game, created return game, created
async def add_or_get_owner(json_data: dict, name: str) -> tuple[Owner | None, bool]: async def add_or_get_owner(json_data: dict | None) -> tuple[Owner | None, bool]:
"""Add or get Owner from JSON data. """Add or get Owner from JSON data.
Args: Args:
json_data (dict): JSON data to add to the database. json_data (dict): JSON data to add to the database.
name (str): Name of the drop campaign.
Returns: Returns:
Owner: Owner instance. Owner: Owner instance.
""" """
if not json_data: if not json_data:
logger.warning("Owner data is missing for %s", name) logger.warning("No owner data provided")
return None, False return None, False
owner, created = await Owner.objects.aupdate_or_create( owner, created = await Owner.objects.aupdate_or_create(
@ -99,18 +103,17 @@ async def add_or_get_owner(json_data: dict, name: str) -> tuple[Owner | None, bo
return owner, created return owner, created
async def add_or_get_allow(json_data: dict, name: str) -> tuple[Allow | None, bool]: async def add_or_get_allow(json_data: dict | None) -> tuple[Allow | None, bool]:
"""Add or get Allow from JSON data. """Add or get Allow from JSON data.
Args: Args:
json_data (dict): JSON data to add to the database. json_data (dict): JSON data to add to the database.
name (str): Name of the drop campaign.
Returns: Returns:
Allow: Allow instance. Allow: Allow instance.
""" """
if not json_data: if not json_data:
logger.warning("Allow data is missing for %s", name) logger.warning("No allow data provided")
return None, False return None, False
allow, created = await Allow.objects.aupdate_or_create( allow, created = await Allow.objects.aupdate_or_create(
@ -133,14 +136,15 @@ async def add_or_get_time_based_drops(
owner (Owner): Owner instance. owner (Owner): Owner instance.
game (Game): Game instance. game (Game): Game instance.
Returns: Returns:
list[TimeBasedDrop]: TimeBasedDrop instances. list[TimeBasedDrop]: TimeBasedDrop instances.
""" """
time_based_drops: list[TimeBasedDrop] = [] time_based_drops: list[TimeBasedDrop] = []
if not time_based_drops_data: if not time_based_drops_data:
logger.warning("No time based drops found") logger.warning("No time based drops data provided")
return [] return time_based_drops
for time_based_drop_data in time_based_drops_data: for time_based_drop_data in time_based_drops_data:
time_based_drop, _ = await TimeBasedDrop.objects.aupdate_or_create( time_based_drop, _ = await TimeBasedDrop.objects.aupdate_or_create(
@ -188,7 +192,7 @@ async def add_or_get_time_based_drops(
async def add_or_get_drop_campaign( async def add_or_get_drop_campaign(
drop_campaign_data: dict, drop_campaign_data: dict | None,
game: Game | None, game: Game | None,
owner: Owner | None, owner: Owner | None,
) -> tuple[DropCampaign | None, bool]: ) -> tuple[DropCampaign | None, bool]:
@ -199,11 +203,12 @@ async def add_or_get_drop_campaign(
game (Game): Game instance. game (Game): Game instance.
owner (Owner): Owner instance. owner (Owner): Owner instance.
Returns: Returns:
tuple[DropCampaign, bool]: DropCampaign instance and whether it was created. tuple[DropCampaign, bool]: DropCampaign instance and whether it was created.
""" """
if not drop_campaign_data: if not drop_campaign_data:
logger.warning("No drop campaign data found") logger.warning("No drop campaign data provided")
return None, False return None, False
if drop_campaign_data.get("__typename") != "Game": if drop_campaign_data.get("__typename") != "Game":
@ -232,17 +237,18 @@ async def add_or_get_drop_campaign(
return drop_campaign, True return drop_campaign, True
async def add_or_get_channel(json_data: dict) -> tuple[Channel | None, bool]: async def add_or_get_channel(json_data: dict | None) -> tuple[Channel | None, bool]:
"""Add or get Channel from JSON data. """Add or get Channel from JSON data.
Args: Args:
json_data (dict): JSON data to add to the database. json_data (dict): JSON data to add to the database.
Returns: Returns:
tuple[Channel | None, bool]: Channel instance and whether it was created. tuple[Channel | None, bool]: Channel instance and whether it was created.
""" """
if not json_data: if not json_data:
logger.warning("Channel data is missing") logger.warning("No channel data provided")
return None, False return None, False
channel, created = await Channel.objects.aupdate_or_create( channel, created = await Channel.objects.aupdate_or_create(
@ -257,29 +263,31 @@ async def add_or_get_channel(json_data: dict) -> tuple[Channel | None, bool]:
return channel, created return channel, created
async def add_drop_campaign(json_data: dict) -> None: async def add_drop_campaign(json_data: dict | None) -> None:
"""Add data from JSON to the database.""" """Add data from JSON to the database.
Args:
json_data (dict): JSON data to add to the database.
"""
if not json_data:
logger.warning("No JSON data provided")
return
# Get the data from the JSON # Get the data from the JSON
user_data: dict = json_data.get("data", {}).get("user", {}) user_data: dict = json_data.get("data", {}).get("user", {})
drop_campaign_data: dict = user_data.get("dropCampaign", {}) drop_campaign_data: dict = user_data.get("dropCampaign", {})
# Add or get Game # Add or get Game
game_data: dict = drop_campaign_data.get("game", {}) game_data: dict = drop_campaign_data.get("game", {})
game, _ = await add_or_get_game(json_data=game_data, name=drop_campaign_data.get("name", "Unknown Drop Campaign")) game, _ = await add_or_get_game(json_data=game_data)
# Add or get Owner # Add or get Owner
owner_data: dict = drop_campaign_data.get("owner", {}) owner_data: dict = drop_campaign_data.get("owner", {})
owner, _ = await add_or_get_owner( owner, _ = await add_or_get_owner(json_data=owner_data)
json_data=owner_data,
name=drop_campaign_data.get("name", "Unknown Drop Campaign"),
)
# Add or get Allow # Add or get Allow
allow_data: dict = drop_campaign_data.get("allow", {}) allow_data: dict = drop_campaign_data.get("allow", {})
allow, _ = await add_or_get_allow( allow, _ = await add_or_get_allow(json_data=allow_data)
json_data=allow_data,
name=drop_campaign_data.get("name", "Unknown Drop Campaign"),
)
# Add channels to Allow # Add channels to Allow
if allow: if allow:
@ -309,7 +317,7 @@ async def add_drop_campaign(json_data: dict) -> None:
logger.info("Added Drop Campaign: %s", drop_campaign.name or "Unknown Drop Campaign") logger.info("Added Drop Campaign: %s", drop_campaign.name or "Unknown Drop Campaign")
async def add_or_get_image(json_data: dict) -> tuple[Image | None, bool]: async def add_or_get_image(json_data: dict | None) -> tuple[Image | None, bool]:
"""Add or get Image from JSON data. """Add or get Image from JSON data.
Args: Args:
@ -337,7 +345,7 @@ async def add_or_get_image(json_data: dict) -> tuple[Image | None, bool]:
return image, created return image, created
async def add_or_get_rewards(json_data: dict) -> list[Reward]: async def add_or_get_rewards(json_data: dict | None) -> list[Reward]:
"""Add or get Rewards from JSON data. """Add or get Rewards from JSON data.
Args: Args:
@ -397,17 +405,19 @@ async def add_or_get_rewards(json_data: dict) -> list[Reward]:
return rewards return rewards
async def add_or_get_unlock_requirements(json_data: dict) -> tuple[UnlockRequirements | None, bool]: async def add_or_get_unlock_requirements(json_data: dict | None) -> tuple[UnlockRequirements | None, bool]:
"""Add or get UnlockRequirements from JSON data. """Add or get UnlockRequirements from JSON data.
Args: Args:
json_data (dict): JSON data to add to the database. json_data (dict): JSON data to add to the database.
Returns: Returns:
tuple[UnlockRequirements | None, bool]: UnlockRequirements instance and whether it was created. tuple[UnlockRequirements | None, bool]: UnlockRequirements instance and whether it was created.
""" """
if not json_data: if not json_data:
logger.warning("Unlock Requirements data is missing") logger.warning("No unlock requirements data provided")
return None, False return None, False
unlock_requirements, created = await UnlockRequirements.objects.aget_or_create( unlock_requirements, created = await UnlockRequirements.objects.aget_or_create(
@ -421,20 +431,21 @@ async def add_or_get_unlock_requirements(json_data: dict) -> tuple[UnlockRequire
return unlock_requirements, created return unlock_requirements, created
async def add_reward_campaign(json_data: dict) -> None: async def add_reward_campaign(json_data: dict | None) -> None:
"""Add data from JSON to the database. """Add data from JSON to the database.
Args: Args:
json_data (dict): JSON data to add to the database. json_data (dict): JSON data to add to the database.
Returns:
None: No return value.
""" """
if not json_data:
logger.warning("No JSON data provided")
return
campaign_data: list[dict] = json_data["data"]["rewardCampaignsAvailableToUser"] campaign_data: list[dict] = json_data["data"]["rewardCampaignsAvailableToUser"]
for campaign in campaign_data: for campaign in campaign_data:
# Add or get Game # Add or get Game
game_data: dict = campaign.get("game", {}) game_data: dict = campaign.get("game", {})
game, _ = await add_or_get_game(json_data=game_data, name=campaign.get("name", "Unknown Reward Campaign")) game, _ = await add_or_get_game(json_data=game_data)
# Add or get Image # Add or get Image
image_data: dict = campaign.get("image", {}) image_data: dict = campaign.get("image", {})
@ -477,30 +488,81 @@ async def add_reward_campaign(json_data: dict) -> None:
await reward_campaign.asave() await reward_campaign.asave()
def get_profile_dir() -> Path:
"""Get the profile directory for the browser.
Returns:
Path: The profile directory.
"""
profile_dir: Path = Path(get_data_dir() / "chrome-profile")
profile_dir.mkdir(parents=True, exist_ok=True)
logger.debug("Launching Chrome browser with user data directory: %s", profile_dir)
return profile_dir
def save_json(campaign: dict, dir_name: str) -> None:
"""Save JSON data to a file.
Args:
campaign (dict): The JSON data to save.
dir_name (Path): The directory to save the JSON data to.
"""
save_dir: Path = Path(dir_name)
save_dir.mkdir(parents=True, exist_ok=True)
# File name is the hash of the JSON data
file_name: str = f"{hash(json.dumps(campaign))}.json"
with Path(save_dir / file_name).open(mode="w", encoding="utf-8") as f:
json.dump(campaign, f, indent=4)
async def process_json_data(num: int, campaign: dict | None, json_data: list[dict] | None) -> None:
"""Process JSON data.
Args:
num (int): The number of the JSON data.
campaign (dict): The JSON data to process.
json_data (list[dict]): The list of JSON
"""
if not json_data:
logger.warning("No JSON data provided")
return
logger.info("Processing JSON %d of %d", num, len(json_data))
if not isinstance(campaign, dict):
logger.warning("Campaign is not a dictionary")
return
if "rewardCampaignsAvailableToUser" in campaign["data"]:
save_json(campaign, "reward_campaigns")
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")
return
save_json(campaign, "drop_campaign")
await add_drop_campaign(campaign)
if "dropCampaigns" in campaign.get("data", {}).get("user", {}):
for drop_campaign in campaign["data"]["user"]["dropCampaigns"]:
save_json(campaign, "drop_campaigns")
await add_drop_campaign(drop_campaign)
class Command(BaseCommand): class Command(BaseCommand):
help = "Scrape Twitch Drops Campaigns with login using Firefox" help = "Scrape Twitch Drops Campaigns with login using Firefox"
async def run( # noqa: PLR6301, C901 @staticmethod
self, async def run(playwright: Playwright) -> list[dict[str, typing.Any]]:
playwright: Playwright, profile_dir: Path = get_profile_dir()
) -> list[dict[str, typing.Any]]:
args: list[str] = []
# disable navigator.webdriver:true flag
args.append("--disable-blink-features=AutomationControlled")
profile_dir: Path = Path(data_dir / "chrome-profile")
profile_dir.mkdir(parents=True, exist_ok=True)
logger.debug(
"Launching Chrome browser with user data directory: %s",
profile_dir,
)
browser: BrowserContext = await playwright.chromium.launch_persistent_context( browser: BrowserContext = await playwright.chromium.launch_persistent_context(
channel="chrome", channel="chrome",
user_data_dir=profile_dir, user_data_dir=profile_dir,
headless=False, headless=False,
args=args, args=["--disable-blink-features=AutomationControlled"],
) )
logger.debug("Launched Chrome browser") logger.debug("Launched Chrome browser")
@ -540,47 +602,10 @@ 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):
logger.info("Processing JSON %d of %d", num, len(json_data)) await process_json_data(num, campaign, json_data)
if not isinstance(campaign, dict):
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 return json_data

View File

@ -0,0 +1,482 @@
# Generated by Django 5.1 on 2024-08-09 02:49
import auto_prefetch
import django.db.models.deletion
import django.db.models.manager
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]] = []
operations: list[Operation] = [
migrations.CreateModel(
name="Benefit",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("created_at", models.DateTimeField(null=True)),
("entitlement_limit", models.TextField(null=True)),
("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(null=True)),
("name", models.TextField(blank=True, null=True)),
("typename", 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="Channel",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("display_name", models.TextField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("typename", 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="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.CreateModel(
name="Game",
fields=[
("id", models.AutoField(primary_key=True, serialize=False)),
("slug", models.TextField(blank=True, null=True)),
("display_name", models.TextField(blank=True, null=True)),
("box_art_url", models.URLField(blank=True, null=True)),
("typename", 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="Image",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("image1_x_url", models.URLField(blank=True, null=True)),
("typename", 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="Owner",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("slug", models.TextField(blank=True, null=True)),
("display_name", models.TextField(blank=True, null=True)),
("typename", 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="UnlockRequirements",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("subs_goal", models.TextField(null=True)),
("minute_watched_goal", models.TextField(null=True)),
("typename", 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="BenefitEdge",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("entitlement_limit", models.TextField(null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"benefit",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefit_edges",
to="core.benefit",
),
),
],
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="Allow",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("is_enabled", models.BooleanField(default=True)),
("typename", models.TextField(blank=True, null=True)),
("channels", models.ManyToManyField(related_name="allow", to="core.channel")),
],
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="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="core.frontendchannel")),
(
"game",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="core.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="core.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="core.frontendorg",
),
),
migrations.AddField(
model_name="benefit",
name="game",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefits",
to="core.game",
),
),
migrations.AddField(
model_name="benefit",
name="owner_organization",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefits",
to="core.owner",
),
),
migrations.CreateModel(
name="Reward",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("name", models.TextField(blank=True, null=True)),
("earnable_until", models.DateTimeField(null=True)),
("redemption_instructions", models.TextField(blank=True, null=True)),
("redemption_url", models.URLField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"banner_image",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="banner_rewards",
to="core.image",
),
),
(
"thumbnail_image",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="thumbnail_rewards",
to="core.image",
),
),
],
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="TimeBasedDrop",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("created_at", models.DateTimeField(null=True)),
("entitlement_limit", models.TextField(null=True)),
("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(null=True)),
("name", models.TextField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"game",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="core.game",
),
),
(
"owner_organization",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="core.owner",
),
),
],
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="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)),
("ends_at", models.DateTimeField(null=True)),
("image_url", models.URLField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("starts_at", models.DateTimeField(null=True)),
(
"status",
models.TextField(blank=True, choices=[("ACTIVE", "Active"), ("EXPIRED", "Expired")], null=True),
),
("typename", models.TextField(blank=True, null=True)),
(
"allow",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="core.allow",
),
),
(
"game",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="core.game",
),
),
(
"owner",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="core.owner",
),
),
("time_based_drops", models.ManyToManyField(related_name="drop_campaigns", to="core.timebaseddrop")),
],
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="RewardCampaign",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("name", models.TextField(blank=True, null=True)),
("brand", models.TextField(blank=True, null=True)),
("starts_at", models.DateTimeField(null=True)),
("ends_at", models.DateTimeField(null=True)),
("status", models.TextField(blank=True, null=True)),
("summary", models.TextField(blank=True, null=True)),
("instructions", models.TextField(blank=True, null=True)),
("external_url", models.URLField(blank=True, null=True)),
("reward_value_url_param", models.TextField(blank=True, null=True)),
("about_url", models.URLField(blank=True, null=True)),
("is_sitewide", models.BooleanField(null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"game",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="core.game",
),
),
(
"image",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="core.image",
),
),
("rewards", models.ManyToManyField(related_name="reward_campaigns", to="core.reward")),
(
"unlock_requirements",
auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="core.unlockrequirements",
),
),
],
options={
"abstract": False,
"base_manager_name": "prefetch_manager",
},
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
]

View File

@ -1,39 +1,71 @@
from __future__ import annotations
import logging
import typing import typing
import auto_prefetch import auto_prefetch
from django.db import models from django.db import models
logger: logging.Logger = logging.getLogger(__name__)
class Game(auto_prefetch.Model): class Game(auto_prefetch.Model):
"""The game that the reward is for. """The game that the reward is for.
Used for reward campaigns (buy subs) and drop campaigns (watch games). Optional for reward campaigns. Required for drop campaigns.
Attributes: Attributes:
id (int): The primary key of the game. id (str): The primary key of the game.
slug (str): The slug identifier of the game. slug (str): The slug identifier of the game.
display_name (str): The display name of the game. display_name (str): The display name of the game.
typename (str): The type name of the object, typically "Game". typename (str): The type name of the object, typically "Game".
JSON example: Example JSON data:
{ {
"id": "780302568", "data": {
"slug": "xdefiant", "currentUser": {
"displayName": "XDefiant", "dropCampaigns": [
"__typename": "Game" {
"game": {
"id": "263490",
"slug": "rust",
"displayName": "Rust",
"__typename": "Game"
}
}
]
}
}
} }
""" """
id = models.AutoField(primary_key=True) id = models.TextField(primary_key=True, unique=True, help_text="The game ID.", verbose_name="Game ID")
slug = models.TextField(null=True, blank=True) slug = models.TextField(null=True, blank=True, help_text="Slug used for building URL where all the streams are.")
display_name = models.TextField(null=True, blank=True) display_name = models.TextField(
box_art_url = models.URLField(null=True, blank=True) null=True,
typename = models.TextField(null=True, blank=True) blank=True,
help_text="Game name.",
default="Unknown Game",
verbose_name="Game Name",
)
typename = models.TextField(null=True, blank=True, help_text="Always 'Game'.", verbose_name="Type Name")
# Only used for reward campaigns?
box_art_url = models.URLField(
null=True,
blank=True,
help_text="URL to the box art of the game.",
default="https://static-cdn.jtvnw.net/ttv-static/404_boxart.jpg",
verbose_name="Box Art URL",
)
def __str__(self) -> str: def __str__(self) -> str:
return self.display_name or "Unknown" return self.display_name or "Unknown"
def get_twitch_url(self) -> str: def get_twitch_url(self) -> str:
if not self.slug:
logger.error("Game %s has no slug", self.display_name)
return "https://www.twitch.tv/"
return f"https://www.twitch.tv/directory/game/{self.slug}" return f"https://www.twitch.tv/directory/game/{self.slug}"
@ -240,24 +272,38 @@ class Channel(auto_prefetch.Model):
name (str): The name of the channel. name (str): The name of the channel.
typename (str): The type name of the object, typically "Channel". typename (str): The type name of the object, typically "Channel".
JSON example: Example JSON data:
{ {
"id": "25254906", "data": {
"displayName": "Stresss", "user": {
"name": "stresss", "dropCampaign": {
"__typename": "Channel" "allow": {
"channels": [
{
"id": "464161875",
"displayName": "Valair",
"name": "valair",
"__typename": "Channel"
}
]
}
}
}
}
} }
""" """
# Used in Drop Campaigns
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True)
display_name = models.TextField(null=True, blank=True) display_name = models.TextField(null=True, blank=True)
name = models.TextField(null=True, blank=True) name = models.TextField(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:
return self.display_name or "Unknown" return self.display_name or "Unknown Channel"
def get_twitch_url(self) -> str: def get_twitch_url(self) -> str:
# TODO(TheLovinator): Use a field instead # noqa: TD003
return f"https://www.twitch.tv/{self.name}" return f"https://www.twitch.tv/{self.name}"
@ -269,19 +315,20 @@ class Allow(auto_prefetch.Model):
is_enabled (bool): Indicates if the channel is enabled. is_enabled (bool): Indicates if the channel is enabled.
typename (str): The type name of the object, typically "RewardCampaignChannelAllow". typename (str): The type name of the object, typically "RewardCampaignChannelAllow".
JSON example: Example JSON data:
"allow": { {
"channels": [ "data": {
{ "user": {
"id": "25254906", "dropCampaign": {
"displayName": "Stresss", "allow": {
"name": "stresss", "channels": [],
"__typename": "Channel" "isEnabled": true,
"__typename": "DropCampaignACL"
}
}
} }
], }
"isEnabled": false, }
"__typename": "DropCampaignACL"
},
""" """
channels = models.ManyToManyField(Channel, related_name="allow") channels = models.ManyToManyField(Channel, related_name="allow")
@ -295,6 +342,9 @@ class Allow(auto_prefetch.Model):
class Owner(auto_prefetch.Model): class Owner(auto_prefetch.Model):
"""Represents the owner of the reward campaign. """Represents the owner of the reward campaign.
Used for:
- Reward campaigns
Attributes: Attributes:
id (int): The primary key of the owner. id (int): The primary key of the owner.
slug (str): The slug identifier of the owner. slug (str): The slug identifier of the owner.
@ -302,23 +352,27 @@ class Owner(auto_prefetch.Model):
typename (str): The type name of the object, typically "Organization". typename (str): The type name of the object, typically "Organization".
JSON example: JSON example:
"game": { "owner": {
"id": "491487", # Can also be a string like 'c57a089c-088f-4402-b02d-c13281b3397e' "id": "a1a51d5a-233d-41c3-9acd-a03bdab35159",
"slug": "dead-by-daylight", "name": "Out of the Park Developments",
"displayName": "Dead by Daylight", "__typename": "Organization"
"__typename": "Game" },
},"
""" """
id = models.TextField(primary_key=True) id = models.TextField(primary_key=True, unique=True, help_text="The owner ID.")
slug = models.TextField(null=True, blank=True) name = models.TextField(null=True, blank=True, help_text="Owner name.")
display_name = models.TextField(null=True, blank=True) slug = models.TextField(null=True, blank=True, help_text="Slug used for building URL where all the streams are.")
typename = models.TextField(null=True, blank=True) display_name = models.TextField(null=True, blank=True, help_text="Owner name.")
typename = models.TextField(null=True, blank=True, help_text="Always 'Organization'.")
def __str__(self) -> str: def __str__(self) -> str:
return self.display_name or "Unknown" return self.display_name or "Unknown"
def get_twitch_url(self) -> str: def get_twitch_url(self) -> str:
if not self.slug:
logger.error("Owner %s has no slug", self.display_name)
return "https://www.twitch.tv/"
return f"https://www.twitch.tv/{self.slug}" return f"https://www.twitch.tv/{self.slug}"

View File

@ -30,14 +30,14 @@ if not DEBUG:
BASE_DIR: Path = Path(__file__).resolve().parent.parent BASE_DIR: Path = Path(__file__).resolve().parent.parent
ADMINS: list[tuple[str, str]] = [("Joakim Hellsén", "tlovinator@gmail.com")] ADMINS: list[tuple[str, str]] = [("Joakim Hellsén", "tlovinator@gmail.com")]
WSGI_APPLICATION = "config.wsgi.application" WSGI_APPLICATION = "core.wsgi.application"
SECRET_KEY: str = os.getenv("DJANGO_SECRET_KEY", default="") SECRET_KEY: str = os.getenv("DJANGO_SECRET_KEY", default="")
TIME_ZONE = "Europe/Stockholm" TIME_ZONE = "Europe/Stockholm"
USE_TZ = True USE_TZ = True
LANGUAGE_CODE = "en-us" LANGUAGE_CODE = "en-us"
DECIMAL_SEPARATOR = "," DECIMAL_SEPARATOR = ","
THOUSAND_SEPARATOR = " " THOUSAND_SEPARATOR = " "
ROOT_URLCONF = "config.urls" ROOT_URLCONF = "core.urls"
STATIC_URL = "static/" STATIC_URL = "static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"] STATICFILES_DIRS: list[Path] = [BASE_DIR / "static"]
@ -67,7 +67,6 @@ DISCORD_WEBHOOK_URL: str = os.getenv(key="DISCORD_WEBHOOK_URL", default="")
INSTALLED_APPS: list[str] = [ INSTALLED_APPS: list[str] = [
"core.apps.CoreConfig", "core.apps.CoreConfig",
"twitch_app.apps.TwitchConfig",
"whitenoise.runserver_nostatic", "whitenoise.runserver_nostatic",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",

View File

@ -1,14 +1,14 @@
<header class="d-flex justify-content-between align-items-center py-3 border-bottom"> <header class="d-flex justify-content-between align-items-center py-3 border-bottom">
<h1 class="h2"> <h1 class="h2">
<a href='{% url "core:index" %}' class="text-decoration-none nav-title">Twitch drops</a> <a href='{% url "index" %}' class="text-decoration-none nav-title">Twitch drops</a>
</h1> </h1>
<nav> <nav>
<ul class="nav"> <ul class="nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href='{% url "core:games" %}'>Games</a> <a class="nav-link" href='{% url "games" %}'>Games</a>
</li> </li>
<li> <li>
<a class="nav-link" href='{% url "core:reward_campaigns" %}'>Reward campaigns</a> <a class="nav-link" href='{% url "reward_campaigns" %}'>Reward campaigns</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href=''>API</a> <a class="nav-link" href=''>API</a>

View File

@ -1,19 +1,13 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pytest import pytest
from django.test import Client, RequestFactory from django.test import Client
from django.urls import reverse from django.urls import reverse
if TYPE_CHECKING: if TYPE_CHECKING:
from django.http import HttpResponse from django.http import HttpResponse
@pytest.fixture
def factory() -> RequestFactory:
"""Factory for creating requests."""
return RequestFactory()
@pytest.mark.django_db @pytest.mark.django_db
def test_index_view(client: Client) -> None: def test_index_view(client: Client) -> None:
"""Test index view.""" """Test index view."""

View File

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from debug_toolbar.toolbar import debug_toolbar_urls
from django.urls import URLPattern, URLResolver, path from django.urls import URLPattern, URLResolver, path
from core.views import game_view, index, reward_campaign_view from core.views import game_view, index, reward_campaign_view
@ -18,4 +19,5 @@ urlpatterns: list[URLPattern | URLResolver] = [
view=reward_campaign_view, view=reward_campaign_view,
name="reward_campaigns", name="reward_campaigns",
), ),
*debug_toolbar_urls(),
] ]

View File

@ -4,60 +4,17 @@ import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import hishel
from django.conf import settings
from django.db.models.manager import BaseManager from django.db.models.manager import BaseManager
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from core.data import WebhookData from core.models import Game, RewardCampaign
from twitch_app.models import Game, RewardCampaign
if TYPE_CHECKING: if TYPE_CHECKING:
from pathlib import Path
from django.db.models.manager import BaseManager from django.db.models.manager import BaseManager
from django.http import HttpRequest, HttpResponse from django.http import HttpRequest, HttpResponse
from httpx import Response
logger: logging.Logger = logging.getLogger(__name__) logger: logging.Logger = logging.getLogger(__name__)
cache_dir: Path = settings.DATA_DIR / "cache"
cache_dir.mkdir(exist_ok=True, parents=True)
storage = hishel.FileStorage(base_path=cache_dir)
controller = hishel.Controller(
cacheable_status_codes=[200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414, 501],
allow_stale=True,
always_revalidate=True,
)
def get_webhooks(request: HttpRequest) -> list[str]:
"""Get the webhooks from the cookie."""
cookie: str = request.COOKIES.get("webhooks", "")
return list(filter(None, cookie.split(",")))
def get_avatar(webhook_response: Response) -> str:
"""Get the avatar URL from the webhook response."""
avatar: str = "https://cdn.discordapp.com/embed/avatars/0.png"
if webhook_response.is_success and webhook_response.json().get("id") and webhook_response.json().get("avatar"):
avatar = f'https://cdn.discordapp.com/avatars/{webhook_response.json().get("id")}/{webhook_response.json().get("avatar")}.png'
return avatar
def get_webhook_data(webhook: str) -> WebhookData:
"""Get the webhook data."""
with hishel.CacheClient(storage=storage, controller=controller) as client:
webhook_response: Response = client.get(url=webhook, extensions={"cache_metadata": True})
return WebhookData(
name=webhook_response.json().get("name") if webhook_response.is_success else "Unknown",
url=webhook,
avatar=get_avatar(webhook_response),
status="Success" if webhook_response.is_success else "Failed",
response=webhook_response.text,
)
@dataclass @dataclass
class TOCItem: class TOCItem:
@ -68,7 +25,14 @@ class TOCItem:
def build_toc(list_of_things: list[TOCItem]) -> str: def build_toc(list_of_things: list[TOCItem]) -> str:
"""Build the table of contents.""" """Build the table of contents.
Args:
list_of_things (list[TOCItem]): The list of table of contents items.
Returns:
str: The HTML for the table of contents.
"""
html: str = """ html: str = """
<div class="position-sticky d-none d-lg-block toc"> <div class="position-sticky d-none d-lg-block toc">
<div class="card"> <div class="card">
@ -85,7 +49,14 @@ def build_toc(list_of_things: list[TOCItem]) -> str:
def index(request: HttpRequest) -> HttpResponse: def index(request: HttpRequest) -> HttpResponse:
"""Render the index page.""" """Render the index page.
Args:
request (HttpRequest): The request object.
Returns:
HttpResponse: The response object
"""
reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all() reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
toc: str = build_toc([ toc: str = build_toc([
@ -98,7 +69,14 @@ def index(request: HttpRequest) -> HttpResponse:
def game_view(request: HttpRequest) -> HttpResponse: def game_view(request: HttpRequest) -> HttpResponse:
"""Render the game view page.""" """Render the game view page.
Args:
request (HttpRequest): The request object.
Returns:
HttpResponse: The response object.
"""
games: BaseManager[Game] = Game.objects.all() games: BaseManager[Game] = Game.objects.all()
tocs: list[TOCItem] = [ tocs: list[TOCItem] = [
@ -111,7 +89,14 @@ def game_view(request: HttpRequest) -> HttpResponse:
def reward_campaign_view(request: HttpRequest) -> HttpResponse: def reward_campaign_view(request: HttpRequest) -> HttpResponse:
"""Render the reward campaign view page.""" """Render the reward campaign view page.
Args:
request (HttpRequest): The request object.
Returns:
HttpResponse: The response object.
"""
reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all() reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
context: dict[str, BaseManager[RewardCampaign]] = {"reward_campaigns": reward_campaigns} context: dict[str, BaseManager[RewardCampaign]] = {"reward_campaigns": reward_campaigns}
return TemplateResponse(request=request, template="reward_campaigns.html", context=context) return TemplateResponse(request=request, template="reward_campaigns.html", context=context)

View File

@ -3,6 +3,6 @@ import os
from django.core.handlers.wsgi import WSGIHandler from django.core.handlers.wsgi import WSGIHandler
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value="config.settings") os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value="core.settings")
application: WSGIHandler = get_wsgi_application() application: WSGIHandler = get_wsgi_application()

View File

@ -7,7 +7,7 @@ import sys
def main() -> None: def main() -> None:
"""Run administrative tasks.""" """Run administrative tasks."""
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value="config.settings") os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value="core.settings")
try: try:
from django.core.management import execute_from_command_line # noqa: PLC0415 from django.core.management import execute_from_command_line # noqa: PLC0415
except ImportError as exc: except ImportError as exc:

268
poetry.lock generated
View File

@ -1,25 +1,5 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "anyio"
version = "4.4.0"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.8"
files = [
{file = "anyio-4.4.0-py3-none-any.whl", hash = "sha256:c1b2d8f46a8a812513012e1107cb0e68c17159a7a594208005a57dc776e1bdc7"},
{file = "anyio-4.4.0.tar.gz", hash = "sha256:5aadc6a1bbb7cdb0bede386cac5e2940f5e2ff3aa20277e991cf028e0585ce94"},
]
[package.dependencies]
idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
trio = ["trio (>=0.23)"]
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.8.1" version = "3.8.1"
@ -317,13 +297,13 @@ files = [
[[package]] [[package]]
name = "django" name = "django"
version = "5.1rc1" version = "5.1"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
files = [ files = [
{file = "Django-5.1rc1-py3-none-any.whl", hash = "sha256:dc162667eb0e66352cd1b6fc2c7a107649ff293cbc8f2a9fdee1a1c0ea3d9e13"}, {file = "Django-5.1-py3-none-any.whl", hash = "sha256:d3b811bf5371a26def053d7ee42a9df1267ef7622323fe70a601936725aa4557"},
{file = "Django-5.1rc1.tar.gz", hash = "sha256:4b3d5c509ccb1528f158afe831d9d98c40efc852eee0d530c5fbe92e6b54cfdf"}, {file = "Django-5.1.tar.gz", hash = "sha256:848a5980e8efb76eea70872fb0e4bc5e371619c70fffbe48e3e1b50b2c09455d"},
] ]
[package.dependencies] [package.dependencies]
@ -498,38 +478,6 @@ files = [
docs = ["Sphinx"] docs = ["Sphinx"]
test = ["objgraph", "psutil"] test = ["objgraph", "psutil"]
[[package]]
name = "h11"
version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
optional = false
python-versions = ">=3.7"
files = [
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "hishel"
version = "0.0.29"
description = "Persistent cache implementation for httpx and httpcore"
optional = false
python-versions = ">=3.8"
files = [
{file = "hishel-0.0.29-py3-none-any.whl", hash = "sha256:b050ef4f7224d2e56e88e2f6c57aea76dc712822e1148947519128023b04e800"},
{file = "hishel-0.0.29.tar.gz", hash = "sha256:2e49e444ff0dd412962de060ea8803b6bf1a1daca6365221ffb5a160a015126b"},
]
[package.dependencies]
httpx = ">=0.22.0"
typing-extensions = ">=4.8.0"
[package.extras]
redis = ["redis (==5.0.1)"]
s3 = ["boto3 (>=1.15.0,<=1.15.3)", "boto3 (>=1.15.3)"]
sqlite = ["anysqlite (>=0.0.5)"]
yaml = ["pyyaml (==6.0.1)"]
[[package]] [[package]]
name = "html-tag-names" name = "html-tag-names"
version = "0.1.2" version = "0.1.2"
@ -552,51 +500,6 @@ files = [
{file = "html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2"}, {file = "html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2"},
] ]
[[package]]
name = "httpcore"
version = "1.0.5"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
{file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
[[package]]
name = "httpx"
version = "0.27.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
{file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]] [[package]]
name = "identify" name = "identify"
version = "2.6.0" version = "2.6.0"
@ -908,62 +811,64 @@ cli = ["click (>=5.0)"]
[[package]] [[package]]
name = "pyyaml" name = "pyyaml"
version = "6.0.1" version = "6.0.2"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.8"
files = [ files = [
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
] ]
[[package]] [[package]]
@ -1091,29 +996,29 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.5.5" version = "0.5.7"
description = "An extremely fast Python linter and code formatter, written in Rust." description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "ruff-0.5.5-py3-none-linux_armv6l.whl", hash = "sha256:605d589ec35d1da9213a9d4d7e7a9c761d90bba78fc8790d1c5e65026c1b9eaf"}, {file = "ruff-0.5.7-py3-none-linux_armv6l.whl", hash = "sha256:548992d342fc404ee2e15a242cdbea4f8e39a52f2e7752d0e4cbe88d2d2f416a"},
{file = "ruff-0.5.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00817603822a3e42b80f7c3298c8269e09f889ee94640cd1fc7f9329788d7bf8"}, {file = "ruff-0.5.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:00cc8872331055ee017c4f1071a8a31ca0809ccc0657da1d154a1d2abac5c0be"},
{file = "ruff-0.5.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:187a60f555e9f865a2ff2c6984b9afeffa7158ba6e1eab56cb830404c942b0f3"}, {file = "ruff-0.5.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf3d86a1fdac1aec8a3417a63587d93f906c678bb9ed0b796da7b59c1114a1e"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe26fc46fa8c6e0ae3f47ddccfbb136253c831c3289bba044befe68f467bfb16"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a01c34400097b06cf8a6e61b35d6d456d5bd1ae6961542de18ec81eaf33b4cb8"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad25dd9c5faac95c8e9efb13e15803cd8bbf7f4600645a60ffe17c73f60779b"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcc8054f1a717e2213500edaddcf1dbb0abad40d98e1bd9d0ad364f75c763eea"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f70737c157d7edf749bcb952d13854e8f745cec695a01bdc6e29c29c288fc36e"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f70284e73f36558ef51602254451e50dd6cc479f8b6f8413a95fcb5db4a55fc"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cfd7de17cef6ab559e9f5ab859f0d3296393bc78f69030967ca4d87a541b97a0"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a78ad870ae3c460394fc95437d43deb5c04b5c29297815a2a1de028903f19692"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09b43e02f76ac0145f86a08e045e2ea452066f7ba064fd6b0cdccb486f7c3e7"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ccd078c66a8e419475174bfe60a69adb36ce04f8d4e91b006f1329d5cd44bcf"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0b856cb19c60cd40198be5d8d4b556228e3dcd545b4f423d1ad812bfdca5884"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7e31c9bad4ebf8fdb77b59cae75814440731060a09a0e0077d559a556453acbb"},
{file = "ruff-0.5.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3687d002f911e8a5faf977e619a034d159a8373514a587249cc00f211c67a091"}, {file = "ruff-0.5.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d796327eed8e168164346b769dd9a27a70e0298d667b4ecee6877ce8095ec8e"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ac9dc814e510436e30d0ba535f435a7f3dc97f895f844f5b3f347ec8c228a523"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4a09ea2c3f7778cc635e7f6edf57d566a8ee8f485f3c4454db7771efb692c499"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:af9bdf6c389b5add40d89b201425b531e0a5cceb3cfdcc69f04d3d531c6be74f"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a36d8dcf55b3a3bc353270d544fb170d75d2dff41eba5df57b4e0b67a95bb64e"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d40a8533ed545390ef8315b8e25c4bb85739b90bd0f3fe1280a29ae364cc55d8"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9369c218f789eefbd1b8d82a8cf25017b523ac47d96b2f531eba73770971c9e5"},
{file = "ruff-0.5.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cab904683bf9e2ecbbe9ff235bfe056f0eba754d0168ad5407832928d579e7ab"}, {file = "ruff-0.5.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b88ca3db7eb377eb24fb7c82840546fb7acef75af4a74bd36e9ceb37a890257e"},
{file = "ruff-0.5.5-py3-none-win32.whl", hash = "sha256:696f18463b47a94575db635ebb4c178188645636f05e934fdf361b74edf1bb2d"}, {file = "ruff-0.5.7-py3-none-win32.whl", hash = "sha256:33d61fc0e902198a3e55719f4be6b375b28f860b09c281e4bdbf783c0566576a"},
{file = "ruff-0.5.5-py3-none-win_amd64.whl", hash = "sha256:50f36d77f52d4c9c2f1361ccbfbd09099a1b2ea5d2b2222c586ab08885cf3445"}, {file = "ruff-0.5.7-py3-none-win_amd64.whl", hash = "sha256:083bbcbe6fadb93cd86709037acc510f86eed5a314203079df174c40bbbca6b3"},
{file = "ruff-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3191317d967af701f1b73a31ed5788795936e423b7acce82a2b63e26eb3e89d6"}, {file = "ruff-0.5.7-py3-none-win_arm64.whl", hash = "sha256:2dca26154ff9571995107221d0aeaad0e75a77b5a682d6236cf89a58c70b76f4"},
{file = "ruff-0.5.5.tar.gz", hash = "sha256:cc5516bdb4858d972fbc31d246bdb390eab8df1a26e2353be2dbc0c2d7f5421a"}, {file = "ruff-0.5.7.tar.gz", hash = "sha256:8dfc0a458797f5d9fb622dd0efc52d796f23f0a1493a9527f4e49a550ae9a7e5"},
] ]
[[package]] [[package]]
@ -1178,17 +1083,6 @@ files = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
] ]
[[package]]
name = "sniffio"
version = "1.3.1"
description = "Sniff out which async library your code is running under"
optional = false
python-versions = ">=3.7"
files = [
{file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
{file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
]
[[package]] [[package]]
name = "sqlparse" name = "sqlparse"
version = "0.5.1" version = "0.5.1"
@ -1206,13 +1100,13 @@ doc = ["sphinx"]
[[package]] [[package]]
name = "tqdm" name = "tqdm"
version = "4.66.4" version = "4.66.5"
description = "Fast, Extensible Progress Meter" description = "Fast, Extensible Progress Meter"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "tqdm-4.66.4-py3-none-any.whl", hash = "sha256:b75ca56b413b030bc3f00af51fd2c1a1a5eac6a0c1cca83cbb37a5c52abce644"}, {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"},
{file = "tqdm-4.66.4.tar.gz", hash = "sha256:e4d936c9de8727928f3be6079590e97d9abfe8d39a590be678eb5919ffc186bb"}, {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"},
] ]
[package.dependencies] [package.dependencies]
@ -1323,4 +1217,4 @@ brotli = ["brotli"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "c850a2e1a9930e82c95171753810c2f3bfd26ac8ade30496750c07b0d41caaa8" content-hash = "4efd9a959fea305be05a55cc0041474ab83ba202dc4c55479bb9d2b66387393b"

View File

@ -9,12 +9,10 @@ package-mode = false
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.12" python = "^3.12"
discord-webhook = "^1.3.1" discord-webhook = "^1.3.1"
django = { version = "^5.1rc1", allow-prereleases = true } django = { version = "^5.1", allow-prereleases = true }
django-auto-prefetch = "^1.9.0" django-auto-prefetch = "^1.9.0"
django-debug-toolbar = "^4.4.6" django-debug-toolbar = "^4.4.6"
django-simple-history = "^3.7.0" django-simple-history = "^3.7.0"
hishel = "^0.0.29"
httpx = "^0.27.0"
pillow = "^10.4.0" pillow = "^10.4.0"
platformdirs = "^4.2.2" platformdirs = "^4.2.2"
python-dotenv = "^1.0.1" python-dotenv = "^1.0.1"
@ -75,5 +73,5 @@ format_attribute_template_tags = true
ignore = "H006" # Img tag should have height and width attributes. ignore = "H006" # Img tag should have height and width attributes.
[tool.pytest.ini_options] [tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "config.settings" DJANGO_SETTINGS_MODULE = "core.settings"
python_files = ["test_*.py"] python_files = ["test_*.py"]

View File

@ -1,380 +0,0 @@
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

View File

@ -1,252 +0,0 @@
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

@ -1,6 +0,0 @@
from django.apps import AppConfig
class TwitchConfig(AppConfig):
default_auto_field: str = "django.db.models.BigAutoField"
name: str = "twitch_app"

View File

@ -1,90 +0,0 @@
# Generated by Django 5.1rc1 on 2024-07-30 21:49
import uuid
import django.db.models.deletion
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]] = []
operations: list[Operation] = [
migrations.CreateModel(
name="Game",
fields=[
("id", models.AutoField(primary_key=True, serialize=False)),
("slug", models.TextField()),
("display_name", models.TextField()),
("typename", models.TextField()),
],
),
migrations.CreateModel(
name="Image",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("image1_x_url", models.URLField()),
("typename", models.TextField()),
],
),
migrations.CreateModel(
name="UnlockRequirements",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("subs_goal", models.IntegerField()),
("minute_watched_goal", models.IntegerField()),
("typename", models.TextField()),
],
),
migrations.CreateModel(
name="Reward",
fields=[
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
("name", models.TextField()),
("earnable_until", models.DateTimeField()),
("redemption_instructions", models.TextField()),
("redemption_url", models.URLField()),
("typename", models.TextField()),
(
"banner_image",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="banner_rewards",
to="twitch_app.image",
),
),
(
"thumbnail_image",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="thumbnail_rewards",
to="twitch_app.image",
),
),
],
),
migrations.CreateModel(
name="RewardCampaign",
fields=[
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
("name", models.TextField()),
("brand", models.TextField()),
("starts_at", models.DateTimeField()),
("ends_at", models.DateTimeField()),
("status", models.TextField()),
("summary", models.TextField()),
("instructions", models.TextField()),
("external_url", models.URLField()),
("reward_value_url_param", models.TextField()),
("about_url", models.URLField()),
("is_sitewide", models.BooleanField()),
("typename", models.TextField()),
("game", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="twitch_app.game")),
("image", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="twitch_app.image")),
("rewards", models.ManyToManyField(to="twitch_app.reward")),
],
),
]

View File

@ -1,94 +0,0 @@
# Generated by Django 5.1rc1 on 2024-07-30 23:39
import django.db.models.deletion
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.AlterField(
model_name="rewardcampaign",
name="about_url",
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="brand",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="ends_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="external_url",
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="game",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="rewardcampaign",
name="image",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="twitch_app.image",
),
),
migrations.AlterField(
model_name="rewardcampaign",
name="instructions",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="is_sitewide",
field=models.BooleanField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="name",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="reward_value_url_param",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="starts_at",
field=models.DateTimeField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="status",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="summary",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="typename",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -1,119 +0,0 @@
# Generated by Django 5.1rc1 on 2024-07-30 23:43
import django.db.models.deletion
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_alter_rewardcampaign_about_url_and_more"),
]
operations: list[Operation] = [
migrations.AlterField(
model_name="game",
name="display_name",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="game",
name="slug",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="game",
name="typename",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="image",
name="image1_x_url",
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name="image",
name="typename",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="reward",
name="banner_image",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="banner_rewards",
to="twitch_app.image",
),
),
migrations.AlterField(
model_name="reward",
name="earnable_until",
field=models.DateTimeField(null=True),
),
migrations.AlterField(
model_name="reward",
name="name",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="reward",
name="redemption_instructions",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="reward",
name="redemption_url",
field=models.URLField(blank=True, null=True),
),
migrations.AlterField(
model_name="reward",
name="thumbnail_image",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="thumbnail_rewards",
to="twitch_app.image",
),
),
migrations.AlterField(
model_name="reward",
name="typename",
field=models.TextField(blank=True, null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="ends_at",
field=models.DateTimeField(null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="is_sitewide",
field=models.BooleanField(null=True),
),
migrations.AlterField(
model_name="rewardcampaign",
name="rewards",
field=models.ManyToManyField(related_name="reward_campaigns", to="twitch_app.reward"),
),
migrations.AlterField(
model_name="rewardcampaign",
name="starts_at",
field=models.DateTimeField(null=True),
),
migrations.AlterField(
model_name="unlockrequirements",
name="minute_watched_goal",
field=models.IntegerField(null=True),
),
migrations.AlterField(
model_name="unlockrequirements",
name="subs_goal",
field=models.IntegerField(null=True),
),
migrations.AlterField(
model_name="unlockrequirements",
name="typename",
field=models.TextField(blank=True, null=True),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 5.1rc1 on 2024-07-30 23:44
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", "0003_alter_game_display_name_alter_game_slug_and_more"),
]
operations: list[Operation] = [
migrations.AlterField(
model_name="reward",
name="id",
field=models.UUIDField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="rewardcampaign",
name="id",
field=models.UUIDField(primary_key=True, serialize=False),
),
]

View File

@ -1,174 +0,0 @@
# Generated by Django 5.1rc1 on 2024-08-01 01:00
import django.db.models.deletion
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", "0004_alter_reward_id_alter_rewardcampaign_id"),
]
operations: list[Operation] = [
migrations.CreateModel(
name="Channel",
fields=[
("id", models.PositiveBigIntegerField(primary_key=True, serialize=False)),
("display_name", models.TextField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name="Owner",
fields=[
("id", models.PositiveBigIntegerField(primary_key=True, serialize=False)),
("slug", models.TextField(blank=True, null=True)),
("display_name", models.TextField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
],
),
migrations.AlterField(
model_name="unlockrequirements",
name="minute_watched_goal",
field=models.PositiveBigIntegerField(null=True),
),
migrations.AlterField(
model_name="unlockrequirements",
name="subs_goal",
field=models.PositiveBigIntegerField(null=True),
),
migrations.CreateModel(
name="Benefit",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("created_at", models.DateTimeField(null=True)),
("entitlement_limit", models.PositiveBigIntegerField(null=True)),
("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(null=True)),
("name", models.TextField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"game",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefits",
to="twitch_app.game",
),
),
(
"owner_organization",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefits",
to="twitch_app.owner",
),
),
],
),
migrations.CreateModel(
name="BenefitEdge",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("entitlement_limit", models.PositiveBigIntegerField(null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"benefit",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefit_edges",
to="twitch_app.benefit",
),
),
],
),
migrations.CreateModel(
name="Allow",
fields=[
("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("is_enabled", models.BooleanField(default=True)),
("typename", models.TextField(blank=True, null=True)),
("channels", models.ManyToManyField(related_name="allow", to="twitch_app.channel")),
],
),
migrations.CreateModel(
name="TimeBasedDrop",
fields=[
("id", models.TextField(primary_key=True, serialize=False)),
("created_at", models.DateTimeField(null=True)),
("entitlement_limit", models.PositiveBigIntegerField(null=True)),
("image_asset_url", models.URLField(blank=True, null=True)),
("is_ios_available", models.BooleanField(null=True)),
("name", models.TextField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"game",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="twitch_app.game",
),
),
(
"owner_organization",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="twitch_app.owner",
),
),
],
),
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)),
("ends_at", models.DateTimeField(null=True)),
("image_url", models.URLField(blank=True, null=True)),
("name", models.TextField(blank=True, null=True)),
("starts_at", models.DateTimeField(null=True)),
("status", models.TextField(blank=True, null=True)),
("typename", models.TextField(blank=True, null=True)),
(
"allow",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.allow",
),
),
(
"game",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.game",
),
),
(
"owner",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.owner",
),
),
(
"time_based_drops",
models.ManyToManyField(related_name="drop_campaigns", to="twitch_app.timebaseddrop"),
),
],
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 5.1rc1 on 2024-08-01 02:50
import django.db.models.deletion
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", "0005_channel_owner_and_more"),
]
operations: list[Operation] = [
migrations.AddField(
model_name="rewardcampaign",
name="unlock_requirements",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="twitch_app.unlockrequirements",
),
),
migrations.AlterField(
model_name="dropcampaign",
name="time_based_drops",
field=models.ManyToManyField(null=True, related_name="drop_campaigns", to="twitch_app.timebaseddrop"),
),
migrations.AlterField(
model_name="reward",
name="id",
field=models.TextField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="rewardcampaign",
name="id",
field=models.TextField(primary_key=True, serialize=False),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 5.1rc1 on 2024-08-01 14:15
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_rewardcampaign_unlock_requirements_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"),
),
]

View File

@ -1,278 +0,0 @@
# Generated by Django 5.1rc1 on 2024-08-01 14:27
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", "0007_alter_dropcampaign_time_based_drops"),
]
operations: list[Operation] = [
migrations.AlterModelOptions(
name="allow",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="benefit",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="benefitedge",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="channel",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="dropcampaign",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="game",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="image",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="owner",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="reward",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="rewardcampaign",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="timebaseddrop",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelOptions(
name="unlockrequirements",
options={"base_manager_name": "prefetch_manager"},
),
migrations.AlterModelManagers(
name="allow",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="benefit",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="benefitedge",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="channel",
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="image",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="owner",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="reward",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterModelManagers(
name="rewardcampaign",
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.AlterModelManagers(
name="unlockrequirements",
managers=[
("objects", django.db.models.manager.Manager()),
("prefetch_manager", django.db.models.manager.Manager()),
],
),
migrations.AlterField(
model_name="benefit",
name="game",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefits",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="benefit",
name="owner_organization",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefits",
to="twitch_app.owner",
),
),
migrations.AlterField(
model_name="benefitedge",
name="benefit",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="benefit_edges",
to="twitch_app.benefit",
),
),
migrations.AlterField(
model_name="dropcampaign",
name="allow",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.allow",
),
),
migrations.AlterField(
model_name="dropcampaign",
name="game",
field=auto_prefetch.ForeignKey(
null=True,
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(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="drop_campaigns",
to="twitch_app.owner",
),
),
migrations.AlterField(
model_name="reward",
name="banner_image",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="banner_rewards",
to="twitch_app.image",
),
),
migrations.AlterField(
model_name="reward",
name="thumbnail_image",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="thumbnail_rewards",
to="twitch_app.image",
),
),
migrations.AlterField(
model_name="rewardcampaign",
name="game",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="rewardcampaign",
name="image",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="twitch_app.image",
),
),
migrations.AlterField(
model_name="rewardcampaign",
name="unlock_requirements",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="reward_campaigns",
to="twitch_app.unlockrequirements",
),
),
migrations.AlterField(
model_name="timebaseddrop",
name="game",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="twitch_app.game",
),
),
migrations.AlterField(
model_name="timebaseddrop",
name="owner_organization",
field=auto_prefetch.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="time_based_drops",
to="twitch_app.owner",
),
),
]

View File

@ -1,48 +0,0 @@
# Generated by Django 5.1rc1 on 2024-08-01 14:48
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", "0008_alter_allow_options_alter_benefit_options_and_more"),
]
operations: list[Operation] = [
migrations.AlterField(
model_name="benefit",
name="entitlement_limit",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="benefitedge",
name="entitlement_limit",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="channel",
name="id",
field=models.TextField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="owner",
name="id",
field=models.TextField(primary_key=True, serialize=False),
),
migrations.AlterField(
model_name="timebaseddrop",
name="entitlement_limit",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="unlockrequirements",
name="minute_watched_goal",
field=models.TextField(null=True),
),
migrations.AlterField(
model_name="unlockrequirements",
name="subs_goal",
field=models.TextField(null=True),
),
]

View File

@ -1,135 +0,0 @@
# 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,10 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from django.urls import URLPattern
app_name: str = "twitch"
urlpatterns: list[URLPattern] = []