Refactor HTML

This commit is contained in:
Joakim Hellsén 2026-02-11 03:14:04 +01:00
commit 05eb0d92e3
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
27 changed files with 776 additions and 393 deletions

View file

@ -155,31 +155,38 @@
</style>
</head>
<body>
<h1 style="margin-top: 0.5em; margin-bottom: 0.5em; ">ttvdrops</h1>
<strong>Twitch:</strong>
<a href="{% url 'twitch:dashboard' %}">Dashboard</a> |
<a href="{% url 'twitch:campaign_list' %}">Campaigns</a> |
<a href="{% url 'twitch:reward_campaign_list' %}">Rewards</a> |
<a href="{% url 'twitch:game_list' %}">Games</a> |
<a href="{% url 'twitch:org_list' %}">Orgs</a> |
<a href="{% url 'twitch:channel_list' %}">Channels</a> |
<a href="{% url 'twitch:badge_list' %}">Badges</a> |
<a href="{% url 'twitch:emote_gallery' %}">Emotes</a>
<br />
<strong>Other:</strong>
<a href="{% url 'twitch:docs_rss' %}">RSS</a> |
<a href="{% url 'twitch:debug' %}">Debug</a> |
<a href="{% url 'twitch:dataset_backups' %}">Dataset</a> |
<a href="https://github.com/sponsors/TheLovinator1">Donate</a> |
<a href="https://github.com/TheLovinator1/ttvdrops">GitHub</a>
<br />
<form action="{% url 'twitch:search' %}" method="get">
<input type="search"
name="q"
placeholder="Search..."
value="{{ request.GET.q }}" />
<button type="submit">Search</button>
</form>
<nav>
<a href="{% url 'twitch:dashboard' %}">Dashboard</a> |
<a href="{% url 'twitch:docs_rss' %}">RSS</a> |
<a href="{% url 'twitch:debug' %}">Debug</a> |
<a href="{% url 'twitch:dataset_backups' %}">Dataset</a> |
<a href="https://github.com/sponsors/TheLovinator1">Donate</a> |
<a href="https://github.com/TheLovinator1/ttvdrops">GitHub</a> |
<form action="{% url 'twitch:search' %}"
method="get"
style="display: inline">
<input type="search"
name="q"
placeholder="Search..."
value="{{ request.GET.q }}" />
<button type="submit">Search</button>
</form>
<br />
<strong>Twitch</strong>
<a href="{% url 'twitch:campaign_list' %}">Campaigns</a> |
<a href="{% url 'twitch:reward_campaign_list' %}">Rewards</a> |
<a href="{% url 'twitch:games_grid' %}">Games</a> |
<a href="{% url 'twitch:org_list' %}">Orgs</a> |
<a href="{% url 'twitch:channel_list' %}">Channels</a> |
<a href="{% url 'twitch:badge_list' %}">Badges</a> |
<a href="{% url 'twitch:emote_gallery' %}">Emotes</a>
<br />
<strong>Other sites</strong>
<a href="#">Steam</a> |
<a href="#">Kick</a> |
<a href="#">YouTube</a>
</nav>
<hr />
{% if messages %}
<ul>
{% for message in messages %}

View file

@ -3,14 +3,9 @@
Chat Badges - ttvdrops
{% endblock title %}
{% block content %}
<h1>Twitch Chat Badges</h1>
<pre>
These are the global chat badges available on Twitch.
</pre>
<h1>{{ badge_sets.count }} Twitch Chat Badges</h1>
{% if badge_sets %}
<p>total badge sets: {{ badge_sets.count }}</p>
{% for data in badge_data %}
<hr />
<h2>
<a href="{% url 'twitch:badge_set_detail' set_id=data.set.set_id %}">[{{ data.set.set_id }}]</a>
</h2>
@ -28,9 +23,10 @@ These are the global chat badges available on Twitch.
</td>
<td>
<strong>{{ badge.title }}</strong>
<br />
<br />
{{ badge.description }}
{% if badge.description != badge.title %}
<br>
{{ badge.description }}
{% endif %}
</td>
</tr>
</table>
@ -38,7 +34,6 @@ These are the global chat badges available on Twitch.
<br />
{% if data.badges|length > 1 %}<small>versions: {{ data.badges|length }}</small>{% endif %}
{% endfor %}
<hr />
{% else %}
<p>No badge sets found.</p>
<p>

View file

@ -3,33 +3,21 @@
{{ badge_set.set_id }} Badges - ttvdrops
{% endblock title %}
{% block content %}
<h1>
Badge Set: <strong>{{ badge_set.set_id }}</strong>
</h1>
<p>
<a href="{% url 'twitch:badge_list' %}">Back to all badges</a>
</p>
<ul>
<li>
<strong>Set ID:</strong> {{ badge_set.set_id }}
</li>
<li>
<strong>Total Versions:</strong> {{ badges.count }}
</li>
<li>
<strong>Added:</strong> {{ badge_set.added_at|date:"Y-m-d H:i:s T" }}
</li>
<li>
<strong>Updated:</strong> {{ badge_set.updated_at|date:"Y-m-d H:i:s T" }}
</li>
</ul>
<h1>{{ badge_set.set_id }}</h1>
{% if badges %}
<h2>Badge Versions ({{ badges.count }})</h2>
<h2>
{{ badges.count }}
{% if badges.count == 1 %}
version
{% else %}
versions
{% endif %}
</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>Preview</th>
<th></th>
<th>Title</th>
<th>Description</th>
<th>Images</th>
@ -52,35 +40,33 @@
height: 72px !important;
object-fit: contain" />
</td>
<td>
<strong>{{ badge.title }}</strong>
</td>
<td>{{ badge.title }}</td>
<td>{{ badge.description }}</td>
<td style="font-size: 0.85em">
<a href="{{ badge.image_url_1x }}" target="_blank">18px</a> |
<a href="{{ badge.image_url_2x }}" target="_blank">36px</a> |
<a href="{{ badge.image_url_4x }}" target="_blank">72px</a>
<td>
<a href="{{ badge.image_url_1x }}" rel="nofollow ugc">[18px]</a>
<a href="{{ badge.image_url_2x }}" rel="nofollow ugc">[36px]</a>
<a href="{{ badge.image_url_4x }}" rel="nofollow ugc">[72px]</a>
</td>
<td>
{% if badge.click_url %}
<a href="{{ badge.click_url }}" target="_blank" rel="noopener">{{ badge.click_action|default:"visit_url" }}</a>
<a href="{{ badge.click_url }}" rel="nofollow ugc">{{ badge.click_action }}</a>
{% else %}
<em>None</em>
{% endif %}
{% if badge.award_campaigns %}
<div style="margin-top: 8px; font-size: 0.9em;">
<strong>Awarded by Drop Campaigns:</strong>
<ul style="margin: 0; padding-left: 18px;">
{% for campaign in badge.award_campaigns %}
<li>
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
</li>
{% endfor %}
</ul>
</div>
-
{% endif %}
</td>
</tr>
{% if badge.award_campaigns %}
<div>
The following campaigns have the same name as this badge and may be awarding it:
<ul>
{% for campaign in badge.award_campaigns %}
<li>
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">{{ campaign.clean_name }}</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% endfor %}
</tbody>
</table>

View file

@ -5,52 +5,37 @@
{% endblock title %}
{% block content %}
<!-- Campaign Title -->
{% if campaign.game %}
<h1 id="campaign-title">
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">{{ campaign.game.get_game_name }}</a> - {{ campaign.clean_name }}
</h1>
{% else %}
<h1 id="campaign-title">{{ campaign.clean_name }}</h1>
{% endif %}
{% if owner %}
<p id="campaign-owner">
<a href="{% url 'twitch:organization_detail' owner.twitch_id %}">{{ owner.name }}</a>
</p>
{% endif %}
<!-- RSS Feeds -->
<div style="margin-bottom: 1rem;">
<h1>
{% if campaign.game %}
<a href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}"
style="margin-right: 1rem"
title="RSS feed for {{ campaign.game.display_name }} campaigns">RSS feed for {{ campaign.game.display_name }} campaigns</a>
<a href="{% url 'twitch:game_detail' campaign.game.twitch_id %}">{{ campaign.game.get_game_name }}</a> - {{ campaign.clean_name }}
{% else %}
{{ campaign.clean_name }}
{% endif %}
{% if owner %}
<a href="{% url 'twitch:organization_campaign_feed' owner.twitch_id %}"
style="margin-right: 1rem"
title="RSS feed for {{ owner.name }} campaigns">RSS feed for {{ owner.name }} campaigns</a>
{% endif %}
</div>
</h1>
<!-- Campaign Owners -->
{% for org in owners %}
<p>
<a href="{% url 'twitch:organization_detail' org.twitch_id %}">{{ org.name }}</a>
</p>
{% endfor %}
<!-- Campaign image -->
{% if campaign.image_best_url or campaign.image_url %}
<img id="campaign-image"
height="160"
{% if campaign.image_url %}
<img height="160"
width="160"
src="{{ campaign.image_best_url|default:campaign.image_url }}"
alt="{{ campaign.name }}" />
{% endif %}
<!-- Campaign description -->
<p id="campaign-description">{{ campaign.description|linebreaksbr }}</p>
<p>{{ campaign.description|linebreaksbr }}</p>
<!-- Campaign end times -->
<div>
{% if campaign.end_at < now %}
<time id="campaign-end-time"
datetime="{{ campaign.end_at|date:'c' }}"
<time datetime="{{ campaign.end_at|date:'c' }}"
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}">
<strong>Ended</strong> {{ campaign.end_at|timesince }} ago
</time>
{% else %}
<time id="campaign-end-time"
datetime="{{ campaign.end_at|date:'c' }}"
<time datetime="{{ campaign.end_at|date:'c' }}"
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}">
<strong>Ends in</strong> {{ campaign.end_at|timeuntil }}
</time>
@ -59,73 +44,47 @@
<!-- Campaign start times -->
<div>
{% if campaign.start_at > now %}
<time id="campaign-start-time"
datetime="{{ campaign.start_at|date:'c' }}"
<time datetime="{{ campaign.start_at|date:'c' }}"
title="{{ campaign.start_at|date:'DATETIME_FORMAT' }}">
<strong>Starts in</strong> {{ campaign.start_at|timeuntil }}
</time>
{% else %}
<time id="campaign-start-time"
datetime="{{ campaign.start_at|date:'c' }}"
<time datetime="{{ campaign.start_at|date:'c' }}"
title="{{ campaign.start_at|date:'DATETIME_FORMAT' }}">
<strong>Started</strong> {{ campaign.start_at|timesince }} ago
</time>
{% endif %}
</div>
<!-- Campaign added times -->
<div>
<time id="campaign-added-time"
datetime="{{ campaign.added_at|date:'c' }}"
title="{{ campaign.added_at|date:'DATETIME_FORMAT' }}">
<strong>Scraped</strong> {{ campaign.added_at|timesince }} ago
</time>
</div>
<!-- Campaign duration -->
<div>
<time id="campaign-duration"
datetime="{{ campaign.start_at|date:'c' }} to {{ campaign.end_at|date:'c' }}"
<time datetime="{{ campaign.duration_iso }}"
title="{{ campaign.start_at|date:'DATETIME_FORMAT' }} to {{ campaign.end_at|date:'DATETIME_FORMAT' }}">
<strong>Duration</strong> {{ campaign.start_at|timesince:campaign.end_at }}
<strong>Duration</strong> {{ campaign.end_at|timeuntil:campaign.start_at }}
</time>
</div>
<!-- Campaign Detail URL -->
{% if campaign.details_url %}
{# TODO: Archive this URL automatically #}
<p>
<a id="campaign-details-url"
href="{{ campaign.details_url }}"
target="_blank">Official Details</a>
</p>
{% endif %}
<!-- Campaign Account Link URL -->
{% if campaign.account_link_url %}
{# TODO: Archive this URL automatically #}
<p>
<a id="campaign-account-link-url"
href="{{ campaign.account_link_url }}"
target="_blank">Connect Account</a>
</p>
{% endif %}
<!-- Allowed Channels -->
<div>
<!-- Campaign Detail URL -->
{% if campaign.details_url %}<a href="{{ campaign.details_url }}" rel="nofollow ugc">[details]</a>{% endif %}
<!-- Campaign Account Link URL -->
{% if campaign.account_link_url %}
<a href="{{ campaign.account_link_url }}" rel="nofollow ugc">[connect]</a>
{% endif %}
<!-- RSS Feeds -->
{% if campaign.game %}
<a href="{% url 'twitch:game_campaign_feed' campaign.game.twitch_id %}"
title="RSS feed for {{ campaign.game.display_name }} campaigns">[rss]</a>
{% endif %}
</div>
{% if allowed_channels %}
<h5>Allowed Channels</h5>
<div id="allowed-channels" style="margin-bottom: 20px;">
<div>
{% for channel in allowed_channels %}
<a href="{% url 'twitch:channel_detail' channel.twitch_id %}"
style="display: inline-block;
margin: 2px 5px 2px 0;
padding: 3px 8px;
background-color: #9146ff;
color: white;
text-decoration: none;
border-radius: 4px;
font-size: 0.9em">{{ channel.display_name }}</a>
<a href="{% url 'twitch:channel_detail' channel.twitch_id %}">{{ channel.display_name }}</a>
{% endfor %}
</div>
{% else %}
<a href="{{ campaign.game.twitch_directory_url }}"
target="_blank"
rel="noopener noreferrer"
rel="nofollow ugc"
title="Find streamers playing {{ campaign.game.display_name }} with drops enabled">
Go to a participating live channel
</a>
@ -135,7 +94,7 @@
<table id="drops-table" style="border-collapse: collapse; width: 100%;">
<thead>
<tr>
<th>Benefits</th>
<th></th>
<th>Drop Name</th>
<th>Requirements</th>
<th>Period</th>

View file

@ -14,6 +14,24 @@
style="margin-right: 1rem"
title="RSS feed for all campaigns">RSS feed for all campaigns</a>
</div>
<!-- Export Options -->
<div style="margin-bottom: 1rem;
display: flex;
gap: 1rem;
flex-wrap: wrap">
<a href="{% url 'twitch:export_campaigns_csv' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
title="Export campaigns as CSV">📥 Campaigns (CSV)</a>
<a href="{% url 'twitch:export_campaigns_json' %}{% if request.GET %}?{{ request.GET.urlencode }}{% endif %}"
title="Export campaigns as JSON">📥 Campaigns (JSON)</a>
<a href="{% url 'twitch:export_games_csv' %}"
title="Export all games as CSV">📥 Games (CSV)</a>
<a href="{% url 'twitch:export_games_json' %}"
title="Export all games as JSON">📥 Games (JSON)</a>
<a href="{% url 'twitch:export_organizations_csv' %}"
title="Export all organizations as CSV">📥 Organizations (CSV)</a>
<a href="{% url 'twitch:export_organizations_json' %}"
title="Export all organizations as JSON">📥 Organizations (JSON)</a>
</div>
</header>
<form id="filter-form"
method="get"

View file

@ -4,9 +4,9 @@
{% endblock title %}
{% block content %}
<!-- Channel Title -->
<h1 id="channel-name">{{ channel.display_name }}</h1>
<h1>{{ channel.display_name }}</h1>
{% if channel.display_name != channel.name %}
<p id="channel-username">
<p>
Username: <code>{{ channel.name }}</code>
</p>
{% endif %}
@ -14,13 +14,6 @@
<p>
<strong>Channel ID:</strong> {{ channel.twitch_id }}
</p>
<p>
<strong>Added to database:</strong>
<time datetime="{{ channel.added_at|date:'c' }}"
title="{{ channel.added_at|date:'DATETIME_FORMAT' }}">
{{ channel.added_at|timesince }} ago ({{ channel.added_at|date:'M d, Y H:i' }})
</time>
</p>
{% if active_campaigns %}
<h5 id="active-campaigns-header">Active Campaigns</h5>
<table id="active-campaigns-table">

View file

@ -1,21 +1,17 @@
{% extends "base.html" %}
{% load static %}
{% block title %}
Channels - Twitch Drops Tracker
Channels
{% endblock title %}
{% block content %}
<h1 id="page-title">Channels</h1>
<h1>Channels</h1>
<p>Browse all channels that can participate in drop campaigns</p>
<form id="search-form"
method="get"
action="{% url 'twitch:channel_list' %}">
<label for="search">Search:</label>
<form method="get" action="{% url 'twitch:channel_list' %}">
<input type="text"
id="search"
name="search"
value="{{ search_query }}"
placeholder="Search channels..." />
<button id="search-button" type="submit">Search</button>
<button type="submit">Search</button>
{% if search_query %}
<a href="{% url 'twitch:channel_list' %}">Clear</a>
{% endif %}
@ -27,42 +23,36 @@
<th>Channel</th>
<th>Username</th>
<th>Campaigns</th>
<th>Added</th>
</tr>
</thead>
<tbody>
{% for channel in channels %}
<tr id="channel-row-{{ channel.twitch_id }}">
<tr>
<td>
<a id="channel-link-{{ channel.twitch_id }}"
href="{% url 'twitch:channel_detail' channel.twitch_id %}">{{ channel.display_name }}</a>
<a href="{% url 'twitch:channel_detail' channel.twitch_id %}">{{ channel.display_name }}</a>
</td>
<td>{{ channel.name }}</td>
<td>{{ channel.campaign_count|default:0 }}</td>
<td>
<time datetime="{{ channel.added_at|date:'c' }}"
title="{{ channel.added_at|date:'DATETIME_FORMAT' }}">
{{ channel.added_at|timesince }} ago
</time>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Pagination -->
{% if is_paginated %}
<p>
{% if page_obj.has_previous %}
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page=1">««</a>
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page={{ page_obj.previous_page_number }}">«</a>
{% endif %}
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page={{ page_obj.next_page_number }}">»</a>
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page={{ page_obj.paginator.num_pages }}">»»</a>
{% endif %}
</p>
<p>Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} channels</p>
<div>
<p>
{% if page_obj.has_previous %}
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page=1">[first]</a>
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page={{ page_obj.previous_page_number }}">[previous]</a>
{% endif %}
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page={{ page_obj.next_page_number }}">[next]</a>
<a href="?{% if search_query %}search={{ search_query }}&{% endif %}page={{ page_obj.paginator.num_pages }}">[last]</a>
{% endif %}
</p>
<p>Showing {{ page_obj.start_index }} to {{ page_obj.end_index }} of {{ page_obj.paginator.count }} channels</p>
</div>
{% endif %}
{% else %}
{% if search_query %}

View file

@ -5,7 +5,7 @@
{% endblock title %}
{% block content %}
<main>
<h1 id="page-title">Twitch Drops</h1>
<h1>Twitch Drops</h1>
<pre>
Latest drops are shown first within each game. Click on a campaign or game title to see more details.
Hover over the end time to see the exact date and time.
@ -43,82 +43,73 @@ Hover over the end time to see the exact date and time.
</div>
<div style="flex: 1; overflow-x: auto;">
<div style="display: flex; gap: 1rem; min-width: max-content;">
{% for campaign in game_data.campaigns %}
<article id="campaign-article-{{ campaign.twitch_id }}"
{% for campaign_data in game_data.campaigns %}
<article id="campaign-article-{{ campaign_data.campaign.twitch_id }}"
style="display: flex;
flex-direction: column;
align-items: center;
padding: 0.5rem;
flex-shrink: 0">
<div>
<a href="{% url 'twitch:campaign_detail' campaign.twitch_id %}">
<img src="{{ campaign.image_url }}"
alt="Image for {{ campaign.name }}"
<a href="{% url 'twitch:campaign_detail' campaign_data.campaign.twitch_id %}">
<img src="{{ campaign_data.campaign.image_url }}"
alt="Image for {{ campaign_data.campaign.name }}"
width="120"
height="120"
style="border-radius: 4px" />
<h4 style="margin: 0.5rem 0; text-align: left;">{{ campaign.clean_name }}</h4>
<h4 style="margin: 0.5rem 0; text-align: left;">{{ campaign_data.campaign.clean_name }}</h4>
</a>
<time datetime="{{ campaign.end_at|date:'c' }}"
title="{{ campaign.end_at|date:'DATETIME_FORMAT' }}"
<time datetime="{{ campaign_data.campaign.end_at|date:'c' }}"
title="{{ campaign_data.campaign.end_at|date:'DATETIME_FORMAT' }}"
style="font-size: 0.9rem;
display: block;
text-align: left">
Ends in {{ campaign.end_at|timeuntil }}
Ends in {{ campaign_data.campaign.end_at|timeuntil }}
</time>
<time datetime="{{ campaign.start_at|date:'c' }}"
title="{{ campaign.start_at|date:'DATETIME_FORMAT' }}"
<time datetime="{{ campaign_data.campaign.start_at|date:'c' }}"
title="{{ campaign_data.campaign.start_at|date:'DATETIME_FORMAT' }}"
style="font-size: 0.9rem;
display: block;
text-align: left">
Started {{ campaign.start_at|timesince }} ago
Started {{ campaign_data.campaign.start_at|timesince }} ago
</time>
<time datetime="{{ campaign.added_at|date:'c' }}"
title="{{ campaign.added_at|date:'DATETIME_FORMAT' }}"
<time datetime="{{ campaign_data.campaign.duration_iso }}"
title="{{ campaign_data.campaign.start_at|date:'DATETIME_FORMAT' }} to {{ campaign_data.campaign.end_at|date:'DATETIME_FORMAT' }}"
style="font-size: 0.9rem;
display: block;
text-align: left">
Scraped {{ campaign.added_at|timesince }} ago
</time>
<time datetime="{{ campaign.start_at|date:'c' }} to {{ campaign.end_at|date:'c' }}"
title="{{ campaign.start_at|date:'DATETIME_FORMAT' }} to {{ campaign.end_at|date:'DATETIME_FORMAT' }}"
style="font-size: 0.9rem;
display: block;
text-align: left">
Duration: {{ campaign.start_at|timesince:campaign.end_at }}
Duration: {{ campaign_data.campaign.start_at|timesince:campaign_data.campaign.end_at }}
</time>
<div style="margin-top: 0.5rem; font-size: 0.8rem; ">
<strong>Channels:</strong>
<ul style="margin: 0.25rem 0 0 0;
padding-left: 1rem;
list-style-type: none">
{% if campaign.allow_is_enabled %}
{% if campaign.allow_channels.all %}
{% for channel in campaign.allow_channels.all %}
{% if campaign_data.campaign.allow_is_enabled %}
{% if campaign_data.allowed_channels %}
{% for channel in campaign_data.allowed_channels %}
{% if forloop.counter <= 5 %}
<li style="margin-bottom: 0.1rem;">
<a href="https://twitch.tv/{{ channel.name }}"
target="_blank"
rel="noopener noreferrer"
rel="nofollow ugc"
title="Watch {{ channel.display_name }} on Twitch">
{{ channel.display_name }}
</a>
</li>
{% endif %}
{% endfor %}
{% if campaign.allow_channels.all|length > 5 %}
{% if campaign_data.allowed_channels|length > 5 %}
<li style="margin-bottom: 0.1rem; color: #666; font-style: italic;">
... and {{ campaign.allow_channels.all|length|add:"-5" }} more
... and {{ campaign_data.allowed_channels|length|add:"-5" }} more
</li>
{% endif %}
{% else %}
{% if campaign.game.twitch_directory_url %}
<li>
<a href="{{ campaign.game.twitch_directory_url }}"
target="_blank"
rel="noopener noreferrer"
title="Open Twitch category page for {{ campaign.game.display_name }} with Drops filter">
Browse {{ campaign.game.display_name }} category
rel="nofollow ugc"
title="Open Twitch category page for {{ campaign_data.campaign.game.display_name }} with Drops filter">
Browse {{ campaign_data.campaign.game.display_name }} category
</a>
</li>
{% else %}
@ -126,12 +117,11 @@ Hover over the end time to see the exact date and time.
{% endif %}
{% endif %}
{% else %}
{% if campaign.game.twitch_directory_url %}
{% if campaign_data.campaign.game.twitch_directory_url %}
<li>
<a href="{{ campaign.game.twitch_directory_url }}"
target="_blank"
rel="noopener noreferrer"
title="Find streamers playing {{ campaign.game.display_name }} with drops enabled">
<a href="{{ campaign_data.campaign.game.twitch_directory_url }}"
rel="nofollow ugc"
title="Find streamers playing {{ campaign_data.campaign.game.display_name }} with drops enabled">
Go to a participating live channel
</a>
</li>
@ -227,8 +217,7 @@ Hover over the end time to see the exact date and time.
{% if campaign.external_url %}
<div style="margin-top: 0.75rem;">
<a href="{{ campaign.external_url }}"
target="_blank"
rel="noopener noreferrer"
rel="nofollow ugc"
style="display: inline-block;
padding: 0.5rem 1rem;
background-color: #9146ff;

View file

@ -1,41 +1,32 @@
{% extends "base.html" %}
{% block title %}
Dataset Backups
Dataset
{% endblock title %}
{% block content %}
<main>
<h1 id="page-title">Dataset Backups</h1>
<p>Scanning {{ data_dir }} for database backups.</p>
<h1>Dataset Backups</h1>
{% if datasets %}
<table>
<thead>
<tr>
<th>Name</th>
<th>Path</th>
<th>Size</th>
<th>Updated</th>
<th>Download</th>
</tr>
</thead>
<tbody>
{% for dataset in datasets %}
<tr id="dataset-row-{{ forloop.counter }}">
<td>{{ dataset.name }}</td>
<td>{{ dataset.display_path }}</td>
<tr">
<td>
<a href="{% url 'twitch:dataset_backup_download' dataset.download_path %}">{{ dataset.name }}</a>
</td>
<td>{{ dataset.size }}</td>
<td>
<time datetime="{{ dataset.updated_at|date:'c' }}"
title="{{ dataset.updated_at|date:'DATETIME_FORMAT' }}">
{{ dataset.updated_at|timesince }} ago
{{ dataset.updated_at|timesince }} ago ({{ dataset.updated_at|date:'M d, Y H:i' }})
</time>
</td>
<td>
{% if dataset.download_path %}
<a href="{% url 'twitch:dataset_backup_download' dataset.download_path %}">Download</a>
{% else %}
-
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>

View file

@ -3,12 +3,13 @@
Debug
{% endblock title %}
{% block content %}
<h1 id="page-title">Debug Data Integrity Report</h1>
<h1>Debug</h1>
<p>
Generated at: <time id="generation-time">{{ now }}</time>
Generated at: <time datetime="{{ now|date:'c' }}"
title="{{ now|date:'DATETIME_FORMAT' }}">{{ now }}</time>
</p>
<section>
<h2 id="operation-names-header">Distinct GraphQL operation_names ({{ operation_names_with_counts|length }})</h2>
<h2>Distinct GraphQL operation_names ({{ operation_names_with_counts|length }})</h2>
{% if operation_names_with_counts %}
<table id="operation-names-table">
<thead>
@ -29,11 +30,11 @@
{% endif %}
</section>
<section>
<h2 id="games-without-owner-header">Games Without an Assigned Owner ({{ games_without_owner|length }})</h2>
<h2>Games Without an Assigned Owner ({{ games_without_owner|length }})</h2>
{% if games_without_owner %}
<ul id="games-without-owner-list">
<ul>
{% for game in games_without_owner %}
<li id="game-{{ game.twitch_id }}">
<li>
<a href="{% url 'twitch:game_detail' game.twitch_id %}">{{ game.display_name }}</a> (ID: {{ game.twitch_id }})
</li>
{% endfor %}
@ -43,14 +44,13 @@
{% endif %}
</section>
<section>
<h2 id="broken-image-campaigns-header">Campaigns With Broken Image URLs ({{ broken_image_campaigns|length }})</h2>
<h2>Campaigns without Image URLs ({{ broken_image_campaigns|length }})</h2>
{% if broken_image_campaigns %}
<ul id="broken-image-campaigns-list">
<ul>
{% for c in broken_image_campaigns %}
<li id="campaign-{{ c.twitch_id }}">
<li>
<a href="{% url 'twitch:campaign_detail' c.twitch_id %}">{{ c.name }}</a>
(Game: <a href="{% url 'twitch:game_detail' c.game.twitch_id %}">{{ c.game.display_name }}</a>)
- URL: {{ c.image_best_url|default:c.image_url|default:'(empty)' }}
</li>
{% endfor %}
</ul>
@ -59,16 +59,13 @@
{% endif %}
</section>
<section>
<h2 id="broken-benefit-images-header">Benefits With Broken Image URLs ({{ broken_benefit_images|length }})</h2>
<h2>Benefits without image URLs ({{ broken_benefit_images|length }})</h2>
{% if broken_benefit_images %}
<ul id="broken-benefit-images-list">
<ul>
{% for b in broken_benefit_images %}
{# A benefit is linked to a game via a drop and a campaign. #}
{# We use the 'with' tag to get the first drop for cleaner access. #}
{% with first_drop=b.drops.all.0 %}
<li id="benefit-{{ b.twitch_id }}">
<li>
{{ b.name }}
{# Check if the relationship path to the game exists #}
{% if first_drop and first_drop.campaign and first_drop.campaign.game %}
(Game: <a href="{% url 'twitch:game_detail' first_drop.campaign.game.twitch_id %}">{{ first_drop.campaign.game.display_name }}</a>)
{% else %}
@ -84,11 +81,11 @@
{% endif %}
</section>
<section>
<h2 id="active-missing-image-header">Active Campaigns Missing Image ({{ active_missing_image|length }})</h2>
<h2>Active Campaigns Missing Image ({{ active_missing_image|length }})</h2>
{% if active_missing_image %}
<ul id="active-missing-image-list">
<ul>
{% for c in active_missing_image %}
<li id="campaign-{{ c.twitch_id }}">
<li>
<a href="{% url 'twitch:campaign_detail' c.twitch_id %}">{{ c.name }}</a>
(Game: <a href="{% url 'twitch:game_detail' c.game.twitch_id %}">{{ c.game.display_name }}</a>)
</li>
@ -99,11 +96,11 @@
{% endif %}
</section>
<section>
<h2 id="drops-without-benefits-header">Time-Based Drops Without Benefits ({{ drops_without_benefits|length }})</h2>
<h2>Time-Based Drops Without Benefits ({{ drops_without_benefits|length }})</h2>
{% if drops_without_benefits %}
<ul id="drops-without-benefits-list">
<ul>
{% for d in drops_without_benefits %}
<li id="drop-{{ d.twitch_id }}">
<li>
{{ d.name }}
(Campaign: <a href="{% url 'twitch:campaign_detail' d.campaign.twitch_id %}">{{ d.campaign.name }}</a>
in Game: <a href="{% url 'twitch:game_detail' d.campaign.game.twitch_id %}">{{ d.campaign.game.display_name }}</a>)
@ -115,11 +112,11 @@
{% endif %}
</section>
<section>
<h2 id="invalid-date-campaigns-header">Campaigns With Invalid Dates ({{ invalid_date_campaigns|length }})</h2>
<h2>Campaigns With Invalid Dates ({{ invalid_date_campaigns|length }})</h2>
{% if invalid_date_campaigns %}
<ul id="invalid-date-campaigns-list">
<ul>
{% for c in invalid_date_campaigns %}
<li id="campaign-{{ c.twitch_id }}">
<li>
<a href="{% url 'twitch:campaign_detail' c.twitch_id %}">{{ c.name }}</a>
(Game: <a href="{% url 'twitch:game_detail' c.game.twitch_id %}">{{ c.game.display_name }}</a>)
- Start: {{ c.start_at|default:'(none)' }} / End: {{ c.end_at|default:'(none)' }}
@ -131,9 +128,9 @@
{% endif %}
</section>
<section>
<h2 id="duplicate-name-campaigns-header">Duplicate Campaign Names Per Game ({{ duplicate_name_campaigns|length }})</h2>
<h2>Duplicate Campaign Names Per Game ({{ duplicate_name_campaigns|length }})</h2>
{% if duplicate_name_campaigns %}
<table id="duplicate-name-campaigns-table">
<table>
<thead>
<tr>
<th>Game</th>
@ -158,13 +155,11 @@
{% endif %}
</section>
<section>
<h2 id="missing-details-campaigns-header">
Campaigns Missing DropCampaignDetails ({{ campaigns_missing_dropcampaigndetails|length }})
</h2>
<h2>Campaigns Missing DropCampaignDetails ({{ campaigns_missing_dropcampaigndetails|length }})</h2>
{% if campaigns_missing_dropcampaigndetails %}
<ul id="missing-details-campaigns-list">
<ul>
{% for c in campaigns_missing_dropcampaigndetails %}
<li id="campaign-{{ c.twitch_id }}">
<li>
<a href="{% url 'twitch:campaign_detail' c.twitch_id %}">{{ c.name }}</a>
(Game: <a href="{% url 'twitch:game_detail' c.game.twitch_id %}">{{ c.game.display_name }}</a>)
- Operations: {{ c.operation_names|join:", "|default:'(none)' }}

View file

@ -4,28 +4,19 @@
{% endblock title %}
{% block content %}
<h1>Emotes</h1>
<div class="emote-gallery"
style="display: flex;
flex-wrap: wrap;
gap: 1.5rem;
justify-content: flex-start">
{% for emote in emotes %}
<a href="{% url 'twitch:campaign_detail' emote.campaign.twitch_id %}"
title="{{ emote.campaign.name }}"
style="display: inline-block">
<img src="{{ emote.image_url }}"
height="96"
width="96"
alt="Emote"
style="max-width: 96px;
max-height: 96px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.12);
padding: 4px"
loading="lazy" />
</a>
{% empty %}
<p>No drop campaigns with emotes found.</p>
{% endfor %}
</div>
{% for emote in emotes %}
<a href="{% url 'twitch:campaign_detail' emote.campaign.twitch_id %}"
title="{{ emote.campaign.name }}"
style="display: inline-block">
<img src="{{ emote.image_url }}"
height="96"
width="96"
alt="Emote"
style="max-width: 96px;
max-height: 96px"
loading="lazy" />
</a>
{% empty %}
<p>No drop campaigns with emotes found.</p>
{% endfor %}
{% endblock content %}

View file

@ -4,22 +4,21 @@
{% endblock title %}
{% block content %}
<!-- Game Title -->
<h1 id="game-name">
<h1>
{{ game.display_name }}
{% if game.display_name != game.name and game.name %}<small>({{ game.name }})</small>{% endif %}
</h1>
<!-- RSS Feeds -->
<div style="margin-bottom: 1rem;">
<div>
<a href="{% url 'twitch:game_campaign_feed' game.twitch_id %}"
style="margin-right: 1rem"
title="RSS feed for {{ game.display_name }} campaigns">RSS feed for {{ game.display_name }} campaigns</a>
{% if owner %}
<a href="{% url 'twitch:organization_campaign_feed' owner.twitch_id %}"
style="margin-right: 1rem"
title="RSS feed for {{ owner.name }} campaigns">RSS feed for {{ owner.name }} campaigns</a>
{% if owners %}
{% for owner in owners %}
<a href="{% url 'twitch:organization_campaign_feed' owner.twitch_id %}"
title="RSS feed for {{ owner.name }} campaigns">RSS feed for {{ owner.name }} campaigns</a>
{% endfor %}
{% endif %}
<a href="{% url 'twitch:campaign_feed' %}"
style="margin-right: 1rem"
title="RSS feed for all campaigns">RSS feed for all campaigns</a>
</div>
<!-- Game image -->
@ -31,9 +30,14 @@
alt="{{ game.name }}" />
{% endif %}
<!-- Game owner -->
{% if owner %}
<small><a id="owner-link"
href="{% url 'twitch:organization_detail' owner.twitch_id %}">{{ owner.name }}</a></small>
{% if owners %}
<small>
{% for owner in owners %}
<a id="owner-link-{{ owner.twitch_id }}"
href="{% url 'twitch:organization_detail' owner.twitch_id %}">{{ owner.name }}</a>
{% if not forloop.last %},{% endif %}
{% endfor %}
</small>
{% endif %}
{% if active_campaigns %}
<h5 id="active-campaigns-header">Active Campaigns</h5>

View file

@ -6,15 +6,13 @@
<main>
<header>
<h1 id="page-title">All Games</h1>
<p>Browse all available games</p>
<p>
<a href="{% url 'twitch:game_list_simple' %}">List View</a>
</p>
<!-- RSS Feeds -->
<div style="margin-bottom: 1rem;">
<a href="{% url 'twitch:game_feed' %}"
style="margin-right: 1rem"
title="RSS feed for all games">RSS feed for all games</a>
<div>
<a href="{% url 'twitch:games_list' %}" title="View games as list">[list]</a>
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
<a href="{% url 'twitch:export_games_csv' %}"
title="Export all games as CSV">[csv]</a>
<a href="{% url 'twitch:export_games_json' %}"
title="Export all games as JSON">[json]</a>
</div>
</header>
{% if games_by_org %}

View file

@ -4,22 +4,23 @@
{% endblock title %}
{% block content %}
<main>
<h1 id="page-title">Games List</h1>
<p>
<a href="{% url 'twitch:game_list' %}">Grid View</a>
</p>
<!-- RSS Feeds -->
<div style="margin-bottom: 1rem;">
<a href="{% url 'twitch:game_feed' %}"
style="margin-right: 1rem"
title="RSS feed for all games">RSS feed for all games</a>
<h1>Games List</h1>
<div>
<a href="{% url 'twitch:games_grid' %}" title="View games as grid">[grid]</a>
<a href="{% url 'twitch:game_feed' %}" title="RSS feed for all games">[rss]</a>
<a href="{% url 'twitch:export_games_csv' %}"
title="Export all games as CSV">[csv]</a>
<a href="{% url 'twitch:export_games_json' %}"
title="Export all games as JSON">[json]</a>
</div>
{% if games_by_org %}
{% for organization, games in games_by_org.items %}
<h2 id="org-{{ organization.twitch_id }}">{{ organization.name }}</h2>
<ul style="list-style: none; padding: 0; margin: 0;">
<h2>
<a href="{% url 'twitch:organization_detail' organization.twitch_id %}">{{ organization.name }}</a>
</h2>
<ul>
{% for item in games %}
<li id="game-{{ item.game.twitch_id }}">
<li>
<a href="{% url 'twitch:game_detail' item.game.twitch_id %}">{{ item.game.display_name }}</a>
</li>
{% endfor %}

View file

@ -10,6 +10,13 @@
style="margin-right: 1rem"
title="RSS feed for all organizations">RSS feed for organizations</a>
</div>
<!-- Export Options -->
<div style="margin-bottom: 1rem; display: flex; gap: 1rem;">
<a href="{% url 'twitch:export_organizations_csv' %}"
title="Export all organizations as CSV">[csv]</a>
<a href="{% url 'twitch:export_organizations_json' %}"
title="Export all organizations as JSON">[json]</a>
</div>
{% if orgs %}
<ul id="org-list">
{% for organization in orgs %}

View file

@ -90,14 +90,10 @@
{% if reward_campaign.external_url or reward_campaign.about_url %}
<p>
{% if reward_campaign.external_url %}
<a href="{{ reward_campaign.external_url }}"
target="_blank"
rel="noopener noreferrer">Claim Reward →</a>
<a href="{{ reward_campaign.external_url }}" rel="nofollow ugc">Claim Reward →</a>
{% endif %}
{% if reward_campaign.about_url %}
<a href="{{ reward_campaign.about_url }}"
target="_blank"
rel="noopener noreferrer">Learn More →</a>
<a href="{{ reward_campaign.about_url }}" rel="nofollow ugc">Learn More →</a>
{% endif %}
</p>
{% endif %}