Add support for Reward campaigns
This commit is contained in:
@ -7,6 +7,11 @@
|
||||
<div class="col-lg-9">
|
||||
{% include "partials/info_box.html" %}
|
||||
{% include "partials/news.html" %}
|
||||
<h2>Reward Campaigns</h2>
|
||||
{% for campaign in reward_campaigns %}
|
||||
{% include "partials/reward_campaign_card.html" %}
|
||||
{% endfor %}
|
||||
<h2>Organizations</h2>
|
||||
{% for org in orgs %}
|
||||
<h2 id="org-{{ org|slugify }}">
|
||||
<a href="#org-{{ org|slugify }}">{{ org }}</a>
|
||||
|
@ -14,7 +14,7 @@
|
||||
<a class="nav-link" href="https://github.com/sponsors/TheLovinator1">Donate</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link" href='{% url "core:webhooks" %}'>Webhooks</a>
|
||||
<a class="nav-link" href='{% url "core:reward_campaigns" %}'>Reward campaigns</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
@ -7,9 +7,6 @@
|
||||
<p>
|
||||
This site allows users to subscribe to Twitch drops notifications. You can choose to be alerted when new drops are found on Twitch or when the drops become available for farming.
|
||||
</p>
|
||||
<p>
|
||||
You can add a Discord Webhook <a href="{% url 'core:webhooks' %}">here</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
56
core/templates/partials/reward_campaign_card.html
Normal file
56
core/templates/partials/reward_campaign_card.html
Normal file
@ -0,0 +1,56 @@
|
||||
<div class="card mb-4 shadow-sm" id="campaign-{{ campaign.id }}">
|
||||
<div class="row g-0">
|
||||
<div class="col-md-2">
|
||||
<img src="{{ campaign.image.image1_x_url }}"
|
||||
alt="{{ campaign.name }}"
|
||||
class="img-fluid rounded-start"
|
||||
height="283"
|
||||
width="212"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<div class="col-md-10">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title h5">{{ campaign.name }}</h2>
|
||||
<p class="card-text text-muted">{{ campaign.summary }}</p>
|
||||
<p>
|
||||
Starts at: <abbr title="{{ campaign.starts_at|date:'l d F H:i e' }}">{{ campaign.starts_at }}</abbr>
|
||||
<br>
|
||||
Ends at: <abbr title="{{ campaign.ends_at|date:'l d F H:i e' }}">{{ campaign.ends_at|timeuntil }}</abbr>
|
||||
</p>
|
||||
<a href="{{ campaign.external_url }}"
|
||||
class="btn btn-primary"
|
||||
target="_blank">Learn More</a>
|
||||
{% if campaign.instructions %}
|
||||
<div class="mt-3">
|
||||
<h3 class="h6">Instructions</h3>
|
||||
<p>{{ campaign.instructions }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if campaign.rewards.exists %}
|
||||
<div class="mt-3">
|
||||
<h3 class="h6">Rewards</h3>
|
||||
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 g-2">
|
||||
{% for reward in campaign.rewards.all %}
|
||||
<div class="col d-flex align-items-center position-relative">
|
||||
<img src="{{ reward.thumbnail_image.image1_x_url }}"
|
||||
alt="{{ reward.name }} reward image"
|
||||
class="img-fluid rounded me-3"
|
||||
height="50"
|
||||
width="50"
|
||||
loading="lazy">
|
||||
<div>
|
||||
<strong>{{ reward.name }}</strong>
|
||||
<br>
|
||||
<a href="{{ reward.redemption_url }}"
|
||||
class="btn btn-sm btn-link"
|
||||
target="_blank">Redeem</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
14
core/templates/reward_campaigns.html
Normal file
14
core/templates/reward_campaigns.html
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<div class="row">
|
||||
<h2>Reward Campaigns</h2>
|
||||
<div>
|
||||
{% for campaign in reward_campaigns %}
|
||||
{% include "partials/reward_campaign_card.html" %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
@ -24,35 +24,5 @@
|
||||
</form>
|
||||
</div>
|
||||
<h2 class="mt-5">Webhooks</h2>
|
||||
{% if webhooks %}
|
||||
<ul class="list-group mt-3">
|
||||
{% for webhook in webhooks %}
|
||||
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<img src="{{ webhook.avatar }}?size=32"
|
||||
alt="{{ webhook.name }}"
|
||||
class="rounded-circle"
|
||||
height="32"
|
||||
width="32">
|
||||
<a href="{{ webhook.url }}" target="_blank">{{ webhook.name }}</a>
|
||||
{% if webhook.status == 'Success' %}
|
||||
<span class="badge bg-success">Working</span>
|
||||
{% else %}
|
||||
<span class="badge bg-danger">Failed</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<form method="post" action="" class="mb-0">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="webhook_id" value="{{ webhook.id }}">
|
||||
<input type="hidden" name="webhook_name" value="{{ webhook.name }}">
|
||||
<input type="hidden" name="webhook_url" value="{{ webhook.webhook_url }}">
|
||||
<button type="submit" class="btn btn-sm btn-danger">Delete</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p class="text-muted">No webhooks added yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
10
core/urls.py
10
core/urls.py
@ -2,9 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from django.urls import URLPattern, URLResolver, path
|
||||
|
||||
from .views.games import GameView
|
||||
from .views.index import index
|
||||
from .views.webhooks import WebhooksView
|
||||
from core.views import GameView, RewardCampaignView, index
|
||||
|
||||
app_name: str = "core"
|
||||
|
||||
@ -15,5 +13,9 @@ urlpatterns: list[URLPattern | URLResolver] = [
|
||||
view=GameView.as_view(),
|
||||
name="games",
|
||||
),
|
||||
path("webhooks/", WebhooksView.as_view(), name="webhooks"),
|
||||
path(
|
||||
route="reward_campaigns/",
|
||||
view=RewardCampaignView.as_view(),
|
||||
name="reward_campaigns",
|
||||
),
|
||||
]
|
||||
|
@ -5,15 +5,16 @@ from typing import TYPE_CHECKING
|
||||
|
||||
import hishel
|
||||
from django.conf import settings
|
||||
from django.db.models.manager import BaseManager
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views.generic import ListView
|
||||
|
||||
from core.data import WebhookData
|
||||
from twitch_app.models import Organization
|
||||
from twitch_app.models import Game, RewardCampaign
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from django.db.models.manager import BaseManager
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from httpx import Response
|
||||
|
||||
@ -59,11 +60,24 @@ def get_webhook_data(webhook: str) -> WebhookData:
|
||||
|
||||
def index(request: HttpRequest) -> HttpResponse:
|
||||
"""Render the index page."""
|
||||
orgs: BaseManager[Organization] = Organization.objects.all()
|
||||
webhooks: list[WebhookData] = [get_webhook_data(webhook) for webhook in get_webhooks(request)]
|
||||
reward_campaigns: BaseManager[RewardCampaign] = RewardCampaign.objects.all()
|
||||
|
||||
return TemplateResponse(
|
||||
request=request,
|
||||
template="index.html",
|
||||
context={"orgs": orgs, "webhooks": webhooks},
|
||||
context={"reward_campaigns": reward_campaigns},
|
||||
)
|
||||
|
||||
|
||||
class GameView(ListView):
|
||||
model = Game
|
||||
template_name: str = "games.html"
|
||||
context_object_name: str = "games"
|
||||
paginate_by = 100
|
||||
|
||||
|
||||
class RewardCampaignView(ListView):
|
||||
model = RewardCampaign
|
||||
template_name: str = "reward_campaigns.html"
|
||||
context_object_name: str = "reward_campaigns"
|
||||
paginate_by = 100
|
@ -1,16 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
|
||||
from django.views.generic import ListView
|
||||
|
||||
from twitch_app.models import Game
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GameView(ListView):
|
||||
model = Game
|
||||
template_name: str = "games.html"
|
||||
context_object_name: str = "games"
|
||||
paginate_by = 100
|
@ -1,118 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import hishel
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.http.response import HttpResponse
|
||||
from django.views.generic import FormView
|
||||
|
||||
from core.data import WebhookData
|
||||
from core.forms import DiscordSettingForm
|
||||
from twitch_app.models import Game
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.http import (
|
||||
HttpResponse,
|
||||
)
|
||||
from httpx import Response
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
class WebhooksView(FormView):
|
||||
model = Game
|
||||
template_name = "webhooks.html"
|
||||
form_class = DiscordSettingForm
|
||||
context_object_name: str = "webhooks"
|
||||
paginate_by = 100
|
||||
|
||||
def get_context_data(self: WebhooksView, **kwargs: dict[str, WebhooksView] | DiscordSettingForm) -> dict[str, Any]:
|
||||
"""Get the context data for the view."""
|
||||
context: dict[str, DiscordSettingForm | list[WebhookData]] = super().get_context_data(**kwargs)
|
||||
webhooks: list[str] = get_webhooks(self.request)
|
||||
|
||||
context.update({
|
||||
"webhooks": [get_webhook_data(webhook) for webhook in webhooks],
|
||||
"form": DiscordSettingForm(),
|
||||
})
|
||||
return context
|
||||
|
||||
def form_valid(self: WebhooksView, form: DiscordSettingForm) -> HttpResponse:
|
||||
"""Handle valid form submission."""
|
||||
webhook = str(form.cleaned_data["webhook_url"])
|
||||
|
||||
with hishel.CacheClient(storage=storage, controller=controller) as client:
|
||||
webhook_response: Response = client.get(url=webhook, extensions={"cache_metadata": True})
|
||||
if not webhook_response.is_success:
|
||||
messages.error(self.request, "Failed to get webhook information. Is the URL correct?")
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
webhook_name: str | None = str(webhook_response.json().get("name")) if webhook_response.is_success else None
|
||||
|
||||
cookie: str = self.request.COOKIES.get("webhooks", "")
|
||||
webhooks: list[str] = cookie.split(",")
|
||||
webhooks = list(filter(None, webhooks))
|
||||
if webhook in webhooks:
|
||||
if webhook_name:
|
||||
messages.error(self.request, f"Webhook {webhook_name} already exists.")
|
||||
else:
|
||||
messages.error(self.request, "Webhook already exists.")
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
webhooks.append(webhook)
|
||||
response: HttpResponse = self.render_to_response(self.get_context_data(form=form))
|
||||
response.set_cookie(key="webhooks", value=",".join(webhooks), max_age=315360000) # 10 years
|
||||
|
||||
messages.success(self.request, "Webhook successfully added.")
|
||||
return response
|
||||
|
||||
def form_invalid(self: WebhooksView, form: DiscordSettingForm) -> HttpResponse:
|
||||
messages.error(self.request, "Failed to add webhook.")
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
Reference in New Issue
Block a user