Discord presence overhaul: game profiles, Spotify bar, Lanyard WS
Build & Push / Lint & Build (push) Successful in 1m19s
Build & Push / Build & Push Image (push) Successful in 31s

- Spotify: drop album line, normalize ";" separators to ",", add open-in-Spotify link and live progress bar
- Games: resolve missing artwork via public Discord RPC app icons with placeholder fallback; add in-site game profile modal (IGDB + Steam + ITAD: artwork, details, reviews, prices)
- Proxy game data through same-origin /api/game and /api/discord-app routes, CORS-locked to https://fristy.net
- Lanyard: SSR REST first paint, then live WebSocket with heartbeat, reconnect backoff and polling fallback
- Localize presence UI (tr/en/de): elapsed times, dates, numbers, currency, review labels, game descriptions
- Sort projects by last update, descending
- Slim dark scrollbars, compact Spotify progress bar
This commit is contained in:
fristy committed 2026-09-10 14:36:14 +03:00
1 parent ad943fff8c
commit bcc5662a44
16 files changed
+1888 -43

No files matched your search

+37
View File
@@ -0,0 +1,37 @@
import { NextResponse } from "next/server";
import { corsHeaders, forbidden, isAllowedOrigin } from "@/lib/api-cors";
import { getDiscordAppInfo } from "@/lib/lanyard";
export async function OPTIONS(request: Request) {
if (!isAllowedOrigin(request)) {
return forbidden();
}
return new NextResponse(null, {
status: 204,
headers: {
...corsHeaders(),
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Max-Age": "86400",
},
});
}
export async function GET(request: Request, context: { params: Promise<{ id: string }> }) {
if (!isAllowedOrigin(request)) {
return forbidden();
}
const { id } = await context.params;
if (!/^\d+$/.test(id)) {
return NextResponse.json({ error: "Invalid application id" }, { status: 400 });
}
const info = await getDiscordAppInfo(id);
if (!info) {
return NextResponse.json({ error: "Application not found" }, { status: 404 });
}
return NextResponse.json(info, {
headers: {
"Cache-Control": "public, s-maxage=86400, stale-while-revalidate=86400",
...corsHeaders(),
},
});
}
+44
View File
@@ -0,0 +1,44 @@
import { NextResponse } from "next/server";
import { corsHeaders, forbidden, isAllowedOrigin } from "@/lib/api-cors";
import { getGameProfile } from "@/lib/game-profile";
export async function OPTIONS(request: Request) {
if (!isAllowedOrigin(request)) {
return forbidden();
}
return new NextResponse(null, {
status: 204,
headers: {
...corsHeaders(),
"Access-Control-Allow-Methods": "GET, OPTIONS",
"Access-Control-Max-Age": "86400",
},
});
}
export async function GET(request: Request, context: { params: Promise<{ id: string }> }) {
if (!isAllowedOrigin(request)) {
return forbidden();
}
const { id } = await context.params;
if (!/^\d+$/.test(id)) {
return NextResponse.json({ error: "Invalid application id" }, { status: 400 });
}
const { searchParams } = new URL(request.url);
const country = /^[A-Z]{2}$/.test(searchParams.get("country") ?? "")
? (searchParams.get("country") as string)
: "TR";
const lang = ["tr", "en", "de"].includes(searchParams.get("lang") ?? "")
? (searchParams.get("lang") as string)
: "en";
const profile = await getGameProfile(id, country, lang);
if (!profile) {
return NextResponse.json({ error: "Game not found" }, { status: 404 });
}
return NextResponse.json(profile, {
headers: {
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
...corsHeaders(),
},
});
}
+29 -1
View File
@@ -12,6 +12,8 @@
html {
scroll-behavior: smooth;
scrollbar-width: thin;
scrollbar-color: #232c47 var(--color-void);
}
body {
@@ -29,7 +31,7 @@ body {
}
::-webkit-scrollbar {
width: 10px;
width: 8px;
}
::-webkit-scrollbar-track {
background: var(--color-void);
@@ -41,3 +43,29 @@ body {
::-webkit-scrollbar-thumb:hover {
background: var(--color-blurple);
}
.modal-scroll {
scrollbar-width: thin;
scrollbar-color: #2b3554 transparent;
}
.modal-scroll::-webkit-scrollbar {
width: 6px;
}
.modal-scroll::-webkit-scrollbar-track {
background: transparent;
}
.modal-scroll::-webkit-scrollbar-thumb {
background: #2b3554;
border-radius: 999px;
}
.modal-scroll::-webkit-scrollbar-thumb:hover {
background: var(--color-blurple);
}
.no-scrollbar {
scrollbar-width: none;
-ms-overflow-style: none;
}
.no-scrollbar::-webkit-scrollbar {
display: none;
}
+1 -1
View File
@@ -55,7 +55,7 @@ export default async function Home() {
</a>
}
/>
<DiscordCard initial={presence} dict={dict.discord} />
<DiscordCard initial={presence} dict={dict.discord} locale={locale} />
</section>
<ProjectsSection repos={repos} dict={dict.projects} locale={locale} />