diff options
| author | Bobby <[email protected]> | 2026-05-14 05:10:28 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2026-05-14 05:10:28 +0530 |
| commit | e229c334e9491bd7dd6399a1716d641dcb6ee2ad (patch) | |
| tree | e66b1e8f1fd6b03a547efcee1a2ae5549fcc3d6d | |
| parent | 6cf63b8d08c7b1d8cb144efb3378b249b7b21629 (diff) | |
| download | thatcomputerscientist-e229c334e9491bd7dd6399a1716d641dcb6ee2ad.tar.xz thatcomputerscientist-e229c334e9491bd7dd6399a1716d641dcb6ee2ad.zip | |
Add screenshots page with Steam integration on CRT GIF
| -rw-r--r-- | core/urls.py | 1 | ||||
| -rw-r--r-- | core/views.py | 9 | ||||
| -rw-r--r-- | internal/steam_wrapper.py | 91 | ||||
| -rw-r--r-- | static/css/core/screenshots.css | 76 | ||||
| -rw-r--r-- | static/images/core/backgrounds/screenshot.gif | bin | 0 -> 1091859 bytes | |||
| -rw-r--r-- | templates/_partials/left_sidebar.html | 2 | ||||
| -rw-r--r-- | templates/core/screenshots.html | 88 |
7 files changed, 266 insertions, 1 deletions
diff --git a/core/urls.py b/core/urls.py index 4d5fa85e..598bd806 100644 --- a/core/urls.py +++ b/core/urls.py @@ -4,6 +4,7 @@ from . import views app_name = "core" urlpatterns = [ path("", views.home, name="home"), + path("screenshots", views.screenshots, name="screenshots"), path("journal", views.journal, name="journal_default"), path("journal/<slug:slug>", views.journal, name="journal"), path("letters", include("core.letters.urls")), diff --git a/core/views.py b/core/views.py index e46e9773..86608b5f 100644 --- a/core/views.py +++ b/core/views.py @@ -14,6 +14,7 @@ from administration.annoucements.functions import get_announcements from authentication.functions import get_user_from_username from blog.functions import get_posts from internal.mal_wrapper import get_mal_recent_activity +from internal.steam_wrapper import get_steam_screenshots from services.journals.functions import ( get_latest_journal_entry, get_journal, @@ -56,6 +57,14 @@ def home(request: HttpRequest) -> HttpResponse: return render(request, "core/home.html", context) +def screenshots(request: HttpRequest) -> HttpResponse: + title_map = {"en": "Screenshots", "ja": "スクリーンショット"} + request.meta.title = title_map.get(request.LANGUAGE_CODE) + + shots = get_steam_screenshots(os.getenv("STEAM_USERNAME", "")) + return render(request, "core/screenshots.html", {"shots": shots}) + + def journal(request: HttpRequest, slug: str = "journal-of-random-thoughts") -> HttpResponse: page = int(request.GET.get("page", 1)) entry_slug = request.GET.get("entry") diff --git a/internal/steam_wrapper.py b/internal/steam_wrapper.py new file mode 100644 index 00000000..67f7e0e6 --- /dev/null +++ b/internal/steam_wrapper.py @@ -0,0 +1,91 @@ +from __future__ import annotations + +import os + +import requests + +from internal.cache import cache + + +STEAM_API_KEY = os.getenv("STEAM_API_KEY", "") +SCREENSHOT_TTL = 6 * 60 * 60 +VANITY_TTL = 30 * 24 * 60 * 60 + + +def _resolve_vanity(username: str) -> str | None: + if not username: + return None + if username.isdigit(): + return username + cache_key = f"steam_vanity:{username}" + cached = cache.get(cache_key) + if cached: + return cached + if not STEAM_API_KEY: + return None + url = "https://api.steampowered.com/ISteamUser/ResolveVanityURL/v1/" + try: + r = requests.get(url, params={"key": STEAM_API_KEY, "vanityurl": username}, timeout=10) + payload = r.json().get("response", {}) + except (requests.RequestException, ValueError): + return None + if payload.get("success") != 1: + return None + sid = str(payload.get("steamid", "")) + if sid: + cache.set(cache_key, sid, ex=VANITY_TTL) + return sid or None + + +def get_steam_screenshots(username: str) -> list[dict]: + """Return every screenshot as a flat list in Steam's native order (newest + first across all games — no per-game grouping).""" + if not username: + return [] + cache_key = f"steam_screenshots_flat:{username}" + cached = cache.get(cache_key) + if cached is not None: + return cached + + steam_id = _resolve_vanity(username) + if not steam_id or not STEAM_API_KEY: + return [] + + shots: list[dict] = [] + page = 1 + per_page = 100 + while page <= 4: + params = { + "key": STEAM_API_KEY, + "steamid": steam_id, + "page": page, + "numperpage": per_page, + "filetype": 4, + "return_metadata": 1, + } + try: + r = requests.get( + "https://api.steampowered.com/IPublishedFileService/GetUserFiles/v1/", + params=params, + timeout=15, + ) + data = r.json().get("response", {}) + except (requests.RequestException, ValueError): + break + files = data.get("publishedfiledetails", []) or [] + if not files: + break + for f in files: + full = f.get("file_url") or f.get("preview_url") or "" + if not full: + continue + shots.append({ + "id": str(f.get("publishedfileid", "")), + "url": full, + }) + if len(files) < per_page: + break + page += 1 + + cache.set(cache_key, shots, ex=SCREENSHOT_TTL) + return shots diff --git a/static/css/core/screenshots.css b/static/css/core/screenshots.css new file mode 100644 index 00000000..ffb53858 --- /dev/null +++ b/static/css/core/screenshots.css @@ -0,0 +1,76 @@ +.scr-room { + width: 780px; + height: 780px; + margin: 8px auto 0; + background: #0a0a0a url('/static/images/core/backgrounds/screenshot.gif') no-repeat center top; + background-size: 780px 780px; + position: relative; + image-rendering: pixelated; +} + +.scr-monitor { + position: absolute; + left: 122px; + top: 161px; + width: 408px; + height: 317px; + overflow: hidden; + cursor: zoom-in; + background: #000; +} + +.scr-img { + display: block; + width: 100%; + height: 100%; + object-fit: cover; + image-rendering: auto; +} + +.scr-noise { + position: absolute; + inset: 0; + display: none; + align-items: center; + justify-content: center; + color: #f0e6d2; + font-family: 'Mali', sans-serif; + font-style: italic; + letter-spacing: 4px; + text-transform: uppercase; + font-size: 16px; + background: #000; +} + +.scr-hint { + width: 780px; + margin: 12px auto 24px; + text-align: center; + color: #f0e6d2; + font-family: 'Mali', sans-serif; + font-size: 13px; + letter-spacing: 2px; +} + +.scr-zoom { + position: fixed; + inset: 0; + background: rgba(8, 4, 18, 0.95); + z-index: 1000; + display: none; + align-items: center; + justify-content: center; + cursor: zoom-out; + backdrop-filter: blur(6px); +} + +.scr-zoom.is-open { + display: flex; +} + +.scr-zoom-img { + max-width: 94vw; + max-height: 92vh; + object-fit: contain; + box-shadow: 0 18px 60px rgba(0, 0, 0, 0.7); +} diff --git a/static/images/core/backgrounds/screenshot.gif b/static/images/core/backgrounds/screenshot.gif Binary files differnew file mode 100644 index 00000000..53732055 --- /dev/null +++ b/static/images/core/backgrounds/screenshot.gif diff --git a/templates/_partials/left_sidebar.html b/templates/_partials/left_sidebar.html index bd0fd8a6..45ed898d 100644 --- a/templates/_partials/left_sidebar.html +++ b/templates/_partials/left_sidebar.html @@ -110,7 +110,7 @@ <div class="navigation-item"> <img src="{% static 'images/core/icons/screenshots.png' %}" width="20" height="20" alt="Screenshots Icon" /> - <a href="#screenshots">{% translate "Screenshots" %}</a> + <a href="{% url 'core:screenshots' %}">{% translate "Screenshots" %}</a> </div> <div class="navigation-item"> <img src="{% static 'images/core/icons/chatrooms.png' %}" width="20" height="20" diff --git a/templates/core/screenshots.html b/templates/core/screenshots.html new file mode 100644 index 00000000..f48d888e --- /dev/null +++ b/templates/core/screenshots.html @@ -0,0 +1,88 @@ +{% extends "_layouts/base.html" %} +{% load static %} +{% load i18n %} + +{% block head %} +<link type="text/css" rel="stylesheet" href="{% static 'css/core/screenshots.css' %}" /> +{% endblock head %} + +{% block content %} +<div class="scr-room"> + <div class="scr-monitor" id="scr-monitor"> + <img class="scr-img" id="scr-img" alt="" /> + <div class="scr-noise" id="scr-noise">{% trans "no signal" %}</div> + </div> +</div> +<div class="scr-hint" id="scr-hint">{% trans "press ← or → to change picture" %}</div> + +<div class="scr-zoom" id="scr-zoom"> + <img class="scr-zoom-img" id="scr-zoom-img" alt="" /> +</div> + +<script id="scr-data" type="application/json">[{% for s in shots %}{"id":"{{ s.id }}","url":"{{ s.url }}"}{% if not forloop.last %},{% endif %}{% endfor %}]</script> + +<script> +(function() { + var shots = JSON.parse(document.getElementById('scr-data').textContent || '[]'); + var img = document.getElementById('scr-img'); + var noise = document.getElementById('scr-noise'); + var hint = document.getElementById('scr-hint'); + var zoom = document.getElementById('scr-zoom'); + var zoomImg = document.getElementById('scr-zoom-img'); + var monitor = document.getElementById('scr-monitor'); + + if (!shots.length) { + img.style.display = 'none'; + noise.style.display = 'flex'; + hint.style.display = 'none'; + return; + } + + var idx = 0; + var cache = {}; + var AHEAD = 4; + + function preload(i) { + if (i < 0 || i >= shots.length || cache[i]) return; + var im = new Image(); + im.src = shots[i].url; + cache[i] = im; + } + + function show(i) { + idx = (i + shots.length) % shots.length; + img.src = shots[idx].url; + for (var j = 1; j <= AHEAD; j++) preload(idx + j); + preload(idx - 1); + hint.textContent = (idx + 1) + ' / ' + shots.length + ' — {% trans "press ← or → to change picture" %}'; + } + + function navigate(dir) { + show(idx + dir); + if (zoom.classList.contains('is-open')) zoomImg.src = shots[idx].url; + } + + function openZoom() { + zoomImg.src = shots[idx].url; + zoom.classList.add('is-open'); + document.body.style.overflow = 'hidden'; + } + + function closeZoom() { + zoom.classList.remove('is-open'); + document.body.style.overflow = ''; + } + + monitor.addEventListener('click', openZoom); + zoom.addEventListener('click', closeZoom); + + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && zoom.classList.contains('is-open')) { closeZoom(); return; } + if (e.key === 'ArrowLeft') { e.preventDefault(); navigate(-1); } + else if (e.key === 'ArrowRight') { e.preventDefault(); navigate(1); } + }); + + show(0); +})(); +</script> +{% endblock content %} |
