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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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