0de85cbc51
Build and push image / build (push) Failing after 15m20s
Archive and answer pages now exist in all six languages and are prerendered instead of rendered per request, and each answer page carries a sentence of real context per player (club, league, nationality, former clubs, honours) plus Article and BreadcrumbList JSON-LD. The sitemap grows from 121 to 691 URLs, every one with hreflang alternates. Adds an optional display name to shared results, so a challenge link reads "<name> challenges you" in the page, the OG image and the head-to-head verdict; the name is sanitised both when written and when parsed back. Adds an opt-in daily reminder (Web Push without payload, so no content encryption) and a PWA install prompt shown once the day is finished, plus first-party event ingestion at /api/events that mirrors GA4 so the funnel survives ad blockers. Wires the missing N8N_PLAYS_WEBHOOK_URL into the deployment along with the new events, push and search-verification secrets, and ships the n8n workflows, the Postgres schema with funnel/retention views, and an IndexNow submitter. Refs #2 #3 #5 #6 #7 #8 #9 #10 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QvQ1ErZNRUcWgCEkJ9uyrn
84 lines
2.7 KiB
JavaScript
84 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
// Submits the sitemap URLs to IndexNow (Bing, Yandex, Seznam, Naver).
|
|
//
|
|
// Google dropped its sitemap ping endpoint in 2023, so the Search Console
|
|
// submission stays a one-off manual step; IndexNow is the part that can run
|
|
// unattended, which is why the daily n8n job calls this.
|
|
//
|
|
// Usage:
|
|
// node scripts/submit-indexnow.mjs # every URL in the sitemap
|
|
// node scripts/submit-indexnow.mjs --today # only today-sensitive URLs
|
|
// INDEXNOW_KEY=... SITE_URL=... node scripts/...
|
|
|
|
const SITE_URL = (process.env.SITE_URL ?? "https://hidden11.app").replace(/\/$/, "");
|
|
const KEY = process.env.INDEXNOW_KEY;
|
|
const ENDPOINT = "https://api.indexnow.org/indexnow";
|
|
|
|
if (!KEY) {
|
|
console.error("INDEXNOW_KEY is required (see pass show hidden11/indexnow-key)");
|
|
process.exit(1);
|
|
}
|
|
|
|
async function sitemapUrls() {
|
|
const response = await fetch(`${SITE_URL}/sitemap.xml`);
|
|
if (!response.ok) {
|
|
throw new Error(`sitemap.xml responded ${response.status}`);
|
|
}
|
|
const xml = await response.text();
|
|
return [...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((match) => match[1]);
|
|
}
|
|
|
|
async function main() {
|
|
const all = await sitemapUrls();
|
|
|
|
// The daily run only needs the surfaces that changed overnight: the home and
|
|
// archive pages, plus the answer pages for the day that just became public.
|
|
const puzzleNumber = (url) => {
|
|
const match = url.match(/\/puzzle\/(\d+)$/);
|
|
return match ? Number(match[1]) : null;
|
|
};
|
|
const newest = Math.max(0, ...all.map(puzzleNumber).filter((value) => value !== null));
|
|
const urls = process.argv.includes("--today")
|
|
? all.filter((url) => {
|
|
const number = puzzleNumber(url);
|
|
return number === null || number === newest;
|
|
})
|
|
: all;
|
|
|
|
if (urls.length === 0) {
|
|
console.log("nothing to submit");
|
|
return;
|
|
}
|
|
|
|
// IndexNow caps a batch at 10 000 URLs.
|
|
const batches = [];
|
|
for (let index = 0; index < urls.length; index += 10000) {
|
|
batches.push(urls.slice(index, index + 10000));
|
|
}
|
|
|
|
for (const [index, batch] of batches.entries()) {
|
|
const response = await fetch(ENDPOINT, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json; charset=utf-8" },
|
|
body: JSON.stringify({
|
|
host: new URL(SITE_URL).host,
|
|
key: KEY,
|
|
keyLocation: `${SITE_URL}/${KEY}.txt`,
|
|
urlList: batch,
|
|
}),
|
|
});
|
|
|
|
// 200 = accepted, 202 = accepted but key still being validated.
|
|
console.log(`batch ${index + 1}/${batches.length}: ${batch.length} urls → ${response.status}`);
|
|
if (!response.ok && response.status !== 202) {
|
|
console.error(await response.text());
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|