All checks were successful
Deploy to Server / deploy (push) Successful in 10s
218 lines
7.8 KiB
Python
218 lines
7.8 KiB
Python
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
import httpx
|
|
from django.core.management.base import BaseCommand
|
|
from pydantic import ValidationError
|
|
|
|
from kick.models import KickCategory
|
|
from kick.models import KickChannel
|
|
from kick.models import KickDropCampaign
|
|
from kick.models import KickOrganization
|
|
from kick.models import KickReward
|
|
from kick.models import KickUser
|
|
from kick.schemas import KickDropsResponseSchema
|
|
|
|
if TYPE_CHECKING:
|
|
from django.core.management.base import CommandParser
|
|
|
|
from kick.schemas import KickCategorySchema
|
|
from kick.schemas import KickDropCampaignSchema
|
|
from kick.schemas import KickOrganizationSchema
|
|
from kick.schemas import KickUserSchema
|
|
|
|
logger: logging.Logger = logging.getLogger("ttvdrops")
|
|
|
|
KICK_DROPS_API_URL = "https://web.kick.com/api/v1/drops/campaigns"
|
|
|
|
# Kick's public API requires a browser-like User-Agent.
|
|
REQUEST_HEADERS: dict[str, str] = {
|
|
"User-Agent": (
|
|
"Mozilla/5.0 (X11; Linux x86_64; rv:148.0) Gecko/20100101 Firefox/148.0"
|
|
),
|
|
"Accept": "application/json",
|
|
"Referer": "https://kick.com/",
|
|
}
|
|
|
|
|
|
class Command(BaseCommand):
|
|
"""Import drop campaigns from the Kick public API."""
|
|
|
|
help = "Fetch and import Kick drop campaigns from the public API."
|
|
|
|
def add_arguments(self, parser: CommandParser) -> None:
|
|
"""Add command-line arguments for the import_kick_drops command."""
|
|
parser.add_argument(
|
|
"--url",
|
|
default=KICK_DROPS_API_URL,
|
|
help="API endpoint to fetch (default: %(default)s).",
|
|
)
|
|
|
|
def handle(self, *args: object, **options: object) -> None: # noqa: ARG002
|
|
"""Main entry point for the command."""
|
|
url: str = str(options["url"])
|
|
self.stdout.write(f"Fetching Kick drops from {url} ...")
|
|
|
|
try:
|
|
response: httpx.Response = httpx.get(
|
|
url,
|
|
headers=REQUEST_HEADERS,
|
|
timeout=30,
|
|
follow_redirects=True,
|
|
)
|
|
response.raise_for_status()
|
|
except httpx.HTTPError as exc:
|
|
self.stderr.write(
|
|
self.style.ERROR(f"HTTP error fetching Kick drops: {exc}"),
|
|
)
|
|
return
|
|
|
|
try:
|
|
payload: dict = response.json()
|
|
except Exception as exc: # noqa: BLE001
|
|
self.stderr.write(self.style.ERROR(f"Failed to parse JSON response: {exc}"))
|
|
return
|
|
|
|
try:
|
|
drops_response: KickDropsResponseSchema = (
|
|
KickDropsResponseSchema.model_validate(payload)
|
|
)
|
|
except ValidationError as exc:
|
|
self.stderr.write(self.style.ERROR(f"Response validation failed: {exc}"))
|
|
return
|
|
|
|
campaigns: list[KickDropCampaignSchema] = drops_response.data
|
|
self.stdout.write(f"Found {len(campaigns)} campaign(s). Importing ...")
|
|
|
|
imported = 0
|
|
for campaign_data in campaigns:
|
|
try:
|
|
self._import_campaign(campaign_data)
|
|
imported += 1
|
|
except Exception as exc:
|
|
logger.exception("Failed to import campaign %s", campaign_data.id)
|
|
self.stderr.write(
|
|
self.style.WARNING(f"Skipped campaign {campaign_data.id!r}: {exc}"),
|
|
)
|
|
|
|
self.stdout.write(
|
|
self.style.SUCCESS(f"Imported {imported}/{len(campaigns)} campaign(s)."),
|
|
)
|
|
|
|
def _import_campaign(self, data: KickDropCampaignSchema) -> None:
|
|
"""Import a single campaign and all its related objects."""
|
|
# Organisation
|
|
org_data: KickOrganizationSchema = data.organization
|
|
org, created = KickOrganization.objects.update_or_create(
|
|
kick_id=org_data.id,
|
|
defaults={
|
|
"name": org_data.name,
|
|
"logo_url": org_data.logo_url,
|
|
"url": org_data.url,
|
|
"restricted": org_data.restricted,
|
|
},
|
|
)
|
|
if created:
|
|
logger.info("Created new organization: %s", org.kick_id)
|
|
|
|
# Category
|
|
cat_data: KickCategorySchema = data.category
|
|
category, created = KickCategory.objects.update_or_create(
|
|
kick_id=cat_data.id,
|
|
defaults={
|
|
"name": cat_data.name,
|
|
"slug": cat_data.slug,
|
|
"image_url": cat_data.image_url,
|
|
},
|
|
)
|
|
if created:
|
|
logger.info("Created new category: %s", category.kick_id)
|
|
|
|
# Campaign
|
|
campaign, created = KickDropCampaign.objects.update_or_create(
|
|
kick_id=data.id,
|
|
defaults={
|
|
"name": data.name,
|
|
"status": data.status,
|
|
"starts_at": data.starts_at,
|
|
"ends_at": data.ends_at,
|
|
"connect_url": data.connect_url,
|
|
"url": data.url,
|
|
"rule_id": data.rule.id,
|
|
"rule_name": data.rule.name,
|
|
"organization": org,
|
|
"category": category,
|
|
"created_at": data.created_at,
|
|
"api_updated_at": data.updated_at,
|
|
},
|
|
)
|
|
if created:
|
|
logger.info("Created new campaign: %s", campaign.kick_id)
|
|
|
|
# Channels
|
|
channel_objs: list[KickChannel] = []
|
|
for ch_data in data.channels:
|
|
user_data: KickUserSchema = ch_data.user
|
|
user, created = KickUser.objects.update_or_create(
|
|
kick_id=user_data.id,
|
|
defaults={
|
|
"username": user_data.username,
|
|
"profile_picture": user_data.profile_picture,
|
|
},
|
|
)
|
|
if created:
|
|
logger.info("Created new user: %s", user.kick_id)
|
|
|
|
channel, created = KickChannel.objects.update_or_create(
|
|
kick_id=ch_data.id,
|
|
defaults={
|
|
"slug": ch_data.slug,
|
|
"description": ch_data.description,
|
|
"banner_picture_url": ch_data.banner_picture_url,
|
|
"user": user,
|
|
},
|
|
)
|
|
if created:
|
|
logger.info("Created new channel: %s", channel.kick_id)
|
|
|
|
channel_objs.append(channel)
|
|
|
|
campaign.channels.set(channel_objs)
|
|
|
|
for reward_data in data.rewards:
|
|
# Resolve reward's category (may differ from campaign category)
|
|
reward_category: KickCategory = category
|
|
if reward_data.category_id != cat_data.id:
|
|
reward_category, created = KickCategory.objects.get_or_create(
|
|
kick_id=reward_data.category_id,
|
|
defaults={"name": "", "slug": "", "image_url": ""},
|
|
)
|
|
if created:
|
|
logger.info("Created new category: %s", reward_category.kick_id)
|
|
|
|
# Resolve reward's organization (may differ from campaign org)
|
|
reward_org: KickOrganization = org
|
|
if reward_data.organization_id != org_data.id:
|
|
reward_org, created = KickOrganization.objects.get_or_create(
|
|
kick_id=reward_data.organization_id,
|
|
defaults={
|
|
"name": "",
|
|
"logo_url": "",
|
|
"url": "",
|
|
"restricted": False,
|
|
},
|
|
)
|
|
if created:
|
|
logger.info("Created new organization: %s", reward_org.kick_id)
|
|
|
|
KickReward.objects.update_or_create(
|
|
kick_id=reward_data.id,
|
|
defaults={
|
|
"name": reward_data.name,
|
|
"image_url": reward_data.image_url,
|
|
"required_units": reward_data.required_units,
|
|
"campaign": campaign,
|
|
"category": reward_category,
|
|
"organization": reward_org,
|
|
},
|
|
)
|