Discord presence overhaul: game profiles, Spotify bar, Lanyard WS
- 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:
16 files changed
+1888
-43
No files matched your search
@@ -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(),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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} />
|
||||
|
||||
Reference in new issue
Block a user