88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
|
|
import { NextResponse } from "next/server";
|
||
|
|
import { readFileSync } from "fs";
|
||
|
|
import { auth } from "@/lib/auth";
|
||
|
|
import { MOD_METADATA_FILE } from "@/lib/constants";
|
||
|
|
import { memoAsync } from "@/lib/cache";
|
||
|
|
|
||
|
|
export const dynamic = "force-dynamic";
|
||
|
|
|
||
|
|
const API = "https://api.modrinth.com/v2";
|
||
|
|
const GAME_VERSION = "1.20.1";
|
||
|
|
const LOADER = "forge";
|
||
|
|
const UA = "HurkiCorgiMC/1.0 (minecraft.hurkicorgi.com)";
|
||
|
|
|
||
|
|
type ModMetadataEntry = { projectId: string; side: string };
|
||
|
|
|
||
|
|
type UpdateInfo = {
|
||
|
|
filename: string;
|
||
|
|
projectId: string;
|
||
|
|
latestFilename: string;
|
||
|
|
latestVersionId: string;
|
||
|
|
dateModified: string;
|
||
|
|
hasUpdate: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
async function checkUpdates(): Promise<UpdateInfo[]> {
|
||
|
|
let metadata: Record<string, ModMetadataEntry>;
|
||
|
|
try {
|
||
|
|
metadata = JSON.parse(readFileSync(MOD_METADATA_FILE, "utf8"));
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
const entries = Object.entries(metadata).filter(([, m]) => m?.projectId);
|
||
|
|
if (entries.length === 0) return [];
|
||
|
|
|
||
|
|
const results = await Promise.all(
|
||
|
|
entries.map(async ([filename, entry]) => {
|
||
|
|
try {
|
||
|
|
const url = `${API}/project/${entry.projectId}/version?game_versions=["${GAME_VERSION}"]&loaders=["${LOADER}"]`;
|
||
|
|
const res = await fetch(url, {
|
||
|
|
headers: { "User-Agent": UA },
|
||
|
|
signal: AbortSignal.timeout(10_000),
|
||
|
|
});
|
||
|
|
if (!res.ok) return null;
|
||
|
|
const versions: Array<{
|
||
|
|
id: string;
|
||
|
|
date_published: string;
|
||
|
|
files: Array<{ filename: string }>;
|
||
|
|
}> = await res.json();
|
||
|
|
const latest = versions[0];
|
||
|
|
if (!latest || !latest.files?.[0]) return null;
|
||
|
|
const latestFilename = latest.files[0].filename;
|
||
|
|
return {
|
||
|
|
filename,
|
||
|
|
projectId: entry.projectId,
|
||
|
|
latestFilename,
|
||
|
|
latestVersionId: latest.id,
|
||
|
|
dateModified: latest.date_published,
|
||
|
|
hasUpdate: latestFilename !== filename,
|
||
|
|
} satisfies UpdateInfo;
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
})
|
||
|
|
);
|
||
|
|
|
||
|
|
return results.filter((r): r is UpdateInfo => r !== null && r.hasUpdate);
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function GET() {
|
||
|
|
const session = await auth();
|
||
|
|
if (!session) {
|
||
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const updates = await memoAsync("mods:updates", 30 * 60 * 1000, checkUpdates);
|
||
|
|
return NextResponse.json(updates, {
|
||
|
|
headers: { "Cache-Control": "private, max-age=300" },
|
||
|
|
});
|
||
|
|
} catch (e) {
|
||
|
|
return NextResponse.json(
|
||
|
|
{ error: (e as Error).message },
|
||
|
|
{ status: 500 }
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|