diff options
| author | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-07 06:39:31 +0900 |
|---|---|---|
| committer | Yasutake Yohei <61961825+yasutakeyohei@users.noreply.github.com> | 2026-10-07 06:39:31 +0900 |
| commit | cb743f53e7f720d54e9dce944a3ad41617a79f83 (patch) | |
| tree | 62a3951b8f3923a31f937d3ecaecff000fecf3a1 /scripts/indexnow.mjs | |
| parent | 6ce064dbec45dac5b890c3dc9a42dc42dccecaa8 (diff) | |
astro.config.mjs, package.json, scripts/indexnow.mjs: IndexNowとサイトマップlastmodを追加
IndexNowのキーファイルと送信スクリプト、npm run indexnow、各ページの最終更新日をlastmodに反映。
Diffstat (limited to 'scripts/indexnow.mjs')
| -rw-r--r-- | scripts/indexnow.mjs | 71 |
1 files changed, 71 insertions, 0 deletions
diff --git a/scripts/indexnow.mjs b/scripts/indexnow.mjs new file mode 100644 index 0000000..25bc768 --- /dev/null +++ b/scripts/indexnow.mjs @@ -0,0 +1,71 @@ +// IndexNow: URL を検索エンジン(Bing・Yandex・Seznam・Naver 等)へ即時通知する。 +// +// 使い方(デプロイしてキーファイルが公開された状態で実行する): +// 1) サイトマップの全URLを送る: npm run indexnow +// 2) 特定URLだけ送る: node scripts/indexnow.mjs https://yasutakeyohei.com/foo/ ... +// +// 仕様: https://www.indexnow.org/documentation +// キーは public/<key>.txt(名前=中身)として公開する。 +import { readFileSync, readdirSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); +const HOST = "yasutakeyohei.com"; +const KEY_RE = /^[A-Za-z0-9-]{8,128}$/; + +// public/ から IndexNow キーファイル(ファイル名=中身の .txt)を探す。 +function findKey() { + if (process.env.INDEXNOW_KEY) return process.env.INDEXNOW_KEY; + for (const name of readdirSync(join(root, "public"))) { + if (!name.endsWith(".txt")) continue; + const stem = name.slice(0, -4); + if (!KEY_RE.test(stem)) continue; + if (readFileSync(join(root, "public", name), "utf-8").trim() === stem) { + return stem; + } + } + throw new Error("IndexNow キーファイル(public/<key>.txt)が見つかりません"); +} + +// 送るURLを集める(引数があればそれ、無ければ dist のサイトマップ)。 +function collectUrls() { + const args = process.argv.slice(2).filter((a) => a.startsWith("http")); + if (args.length) return args; + const dir = join(root, "dist"); + const urls = []; + for (const name of readdirSync(dir)) { + if (!/^sitemap.*\.xml$/.test(name) || name === "sitemap-index.xml") continue; + const xml = readFileSync(join(dir, name), "utf-8"); + for (const m of xml.matchAll(/<loc>([^<]+)<\/loc>/g)) urls.push(m[1].trim()); + } + return urls; +} + +async function submit(endpoint, key, urls) { + const res = await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json; charset=utf-8" }, + body: JSON.stringify({ + host: HOST, + key, + keyLocation: `https://${HOST}/${key}.txt`, + urlList: urls, + }), + }); + return `${endpoint} -> ${res.status} ${res.statusText}`; +} + +const key = findKey(); +const urls = collectUrls(); +console.log(`IndexNow: key=${key} / urls=${urls.length}`); +for (const ep of [ + "https://api.indexnow.org/indexnow", + "https://www.bing.com/indexnow", +]) { + try { + console.log(" ", await submit(ep, key, urls)); + } catch (e) { + console.error(` ${ep} FAILED:`, e.message); + } +} |
