Add Void difficulty and publish changelogs
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 3m40s
Some checks failed
Deploy MineGeo to Dokploy / deploy (push) Failing after 3m40s
This commit is contained in:
16
lib/api.ts
16
lib/api.ts
@@ -188,14 +188,16 @@ export async function handle(request: NextRequest, path: string[]) {
|
||||
await client
|
||||
.from("difficulty_presets")
|
||||
.select(
|
||||
"id,name,seconds,zoom,pan,compass,coordinates,map_zoom,scoring",
|
||||
"id,name,seconds,zoom,pan,compass,coordinates,map_zoom,map_pan,timer_visible,scoring",
|
||||
)
|
||||
.order("name", { ascending: true }),
|
||||
);
|
||||
return NextResponse.json({
|
||||
presets: presets.map(({ map_zoom, ...preset }) => ({
|
||||
presets: presets.map(({ map_zoom, map_pan, timer_visible, ...preset }) => ({
|
||||
...preset,
|
||||
mapZoom: map_zoom,
|
||||
mapPan: map_pan,
|
||||
timerVisible: timer_visible,
|
||||
})),
|
||||
});
|
||||
}
|
||||
@@ -205,17 +207,17 @@ export async function handle(request: NextRequest, path: string[]) {
|
||||
request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
|
||||
"unknown";
|
||||
await limit("difficulty-presets:" + hash(address), 8, 3600);
|
||||
const { mapZoom, ...fields } = body;
|
||||
const { mapZoom, mapPan, timerVisible, ...fields } = body;
|
||||
const { data, error } = await client
|
||||
.from("difficulty_presets")
|
||||
.insert({ ...fields, map_zoom: mapZoom })
|
||||
.insert({ ...fields, map_zoom: mapZoom, map_pan: mapPan, timer_visible: timerVisible })
|
||||
.select(
|
||||
"id,name,seconds,zoom,pan,compass,coordinates,map_zoom,scoring",
|
||||
"id,name,seconds,zoom,pan,compass,coordinates,map_zoom,map_pan,timer_visible,scoring",
|
||||
)
|
||||
.single();
|
||||
checkDifficultyPresetError(error);
|
||||
return NextResponse.json({
|
||||
preset: data ? { ...data, mapZoom: data.map_zoom } : null,
|
||||
preset: data ? { ...data, mapZoom: data.map_zoom, mapPan: data.map_pan, timerVisible: data.timer_visible } : null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -247,7 +249,7 @@ export async function handle(request: NextRequest, path: string[]) {
|
||||
const body = z
|
||||
.object({
|
||||
datasetId: idSchema,
|
||||
difficulty: z.union([z.enum(["easy", "normal", "hard"]), idSchema]),
|
||||
difficulty: z.union([z.enum(["easy", "normal", "hard", "void"]), idSchema]),
|
||||
})
|
||||
.parse(await jsonBody(request));
|
||||
const secret = cookieSecret(request) || randomBytes(32).toString("hex");
|
||||
|
||||
76
lib/changelog-feed.ts
Normal file
76
lib/changelog-feed.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import {
|
||||
parseReleasePost,
|
||||
RELEASE_NEWS_CHANNEL,
|
||||
RELEASE_WEBHOOK_ID,
|
||||
type ReleasePost,
|
||||
} from "../shared/changelog";
|
||||
|
||||
export async function readChangelogFeed({
|
||||
token,
|
||||
channel = RELEASE_NEWS_CHANNEL,
|
||||
webhookId = RELEASE_WEBHOOK_ID,
|
||||
before,
|
||||
request = fetch,
|
||||
}: {
|
||||
token: string;
|
||||
channel?: string;
|
||||
webhookId?: string;
|
||||
before?: string;
|
||||
request?: typeof fetch;
|
||||
}) {
|
||||
if (!/^\d{17,20}$/.test(channel) || (before && !/^\d{17,20}$/.test(before)))
|
||||
throw new Error("Invalid news channel or cursor");
|
||||
const url = new URL(
|
||||
`https://discord.com/api/v10/channels/${channel}/messages`,
|
||||
);
|
||||
url.searchParams.set("limit", "100");
|
||||
if (before) url.searchParams.set("before", before);
|
||||
// Reading history only. No Discord write endpoint is used by this feature.
|
||||
const response = await request(url.toString(), {
|
||||
method: "GET",
|
||||
headers: { Authorization: `Bot ${token}` },
|
||||
next: { revalidate: 60 },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
redirect: "error",
|
||||
});
|
||||
if (!response.ok)
|
||||
throw new Error(`News feed unavailable (${response.status})`);
|
||||
const posts: ReleasePost[] = await response.json();
|
||||
if (
|
||||
!Array.isArray(posts) ||
|
||||
posts.some(
|
||||
(post) =>
|
||||
typeof post.content !== "string" ||
|
||||
post.channel_id !== channel ||
|
||||
!/^\d{17,20}$/.test(post.id),
|
||||
)
|
||||
)
|
||||
throw new Error("Invalid news response");
|
||||
if (
|
||||
posts.some((post) => post.webhook_id === webhookId) &&
|
||||
posts
|
||||
.filter((post) => post.webhook_id === webhookId)
|
||||
.every((post) => !post.content.trim())
|
||||
)
|
||||
throw new Error(
|
||||
"Release text is unavailable; check Discord Message Content access",
|
||||
);
|
||||
const entries = posts
|
||||
.flatMap((post) => {
|
||||
const entry = parseReleasePost(post, webhookId);
|
||||
return entry ? [entry] : [];
|
||||
})
|
||||
.sort(
|
||||
(a, b) =>
|
||||
b.publishedAt.localeCompare(a.publishedAt) ||
|
||||
(BigInt(a.id) > BigInt(b.id) ? -1 : 1),
|
||||
);
|
||||
const oldest = posts.reduce<string | undefined>(
|
||||
(id, post) => (!id || BigInt(post.id) < BigInt(id) ? post.id : id),
|
||||
undefined,
|
||||
);
|
||||
return {
|
||||
entries,
|
||||
before: posts.length === 100 && oldest !== before ? oldest : undefined,
|
||||
};
|
||||
}
|
||||
21
lib/changelog.ts
Normal file
21
lib/changelog.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import "server-only";
|
||||
import { readChangelogFeed } from "./changelog-feed";
|
||||
|
||||
export async function getChangelog(before?: string) {
|
||||
const token = process.env.DISCORD_BOT_TOKEN?.trim();
|
||||
if (!token) return { entries: [], unavailable: true };
|
||||
try {
|
||||
return {
|
||||
...(await readChangelogFeed({
|
||||
token,
|
||||
before,
|
||||
channel: process.env.DISCORD_NEWS_CHANNEL_ID?.trim() || undefined,
|
||||
webhookId: process.env.DISCORD_RELEASE_WEBHOOK_ID?.trim() || undefined,
|
||||
})),
|
||||
unavailable: false,
|
||||
};
|
||||
} catch {
|
||||
// Do not expose Discord details or credentials to the public page.
|
||||
return { entries: [], unavailable: true };
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function checkDifficultyPresetError(
|
||||
throw new ServiceError("A difficulty with that name already exists", 409);
|
||||
if (error?.code === "23514")
|
||||
throw new ServiceError(
|
||||
"Use a name with 2–40 characters and a round time of 15–600 seconds in 15-second steps",
|
||||
"Use a name with 2–40 characters and a round time of 5–600 seconds in 5-second steps",
|
||||
400,
|
||||
);
|
||||
checkServiceError(error);
|
||||
|
||||
Reference in New Issue
Block a user