From e229c334e9491bd7dd6399a1716d641dcb6ee2ad Mon Sep 17 00:00:00 2001 From: Bobby <30593201+luciferreeves@users.noreply.github.com> Date: Thu, 14 May 2026 05:10:28 +0530 Subject: Add screenshots page with Steam integration on CRT GIF --- core/urls.py | 1 + core/views.py | 9 +++ internal/steam_wrapper.py | 91 ++++++++++++++++++++++++++ static/css/core/screenshots.css | 76 +++++++++++++++++++++ static/images/core/backgrounds/screenshot.gif | Bin 0 -> 1091859 bytes templates/_partials/left_sidebar.html | 2 +- templates/core/screenshots.html | 88 +++++++++++++++++++++++++ 7 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 internal/steam_wrapper.py create mode 100644 static/css/core/screenshots.css create mode 100644 static/images/core/backgrounds/screenshot.gif create mode 100644 templates/core/screenshots.html 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/", 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 new file mode 100644 index 00000000..53732055 Binary files /dev/null and b/static/images/core/backgrounds/screenshot.gif differ 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 @@