// IndexNow: уведомить Yandex/Bing о новых/обновлённых URL. // Запускается после деплоя из CI или вручную: // node scripts/indexnow.js // // Источник URL — sitemap. Сначала пробуем локальный dist/sitemap*.xml // (если запускаем сразу после `npm run build`), иначе тянем // https://anotherreflections.ru/sitemap-index.xml через сеть. // // IndexNow ключ хранится в файле public/.txt — он должен быть доступен // по тому же URL что и сайт, чтобы поисковики могли подтвердить ownership. import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const root = path.resolve(__dirname, '..'); const BASE = 'https://anotherreflections.ru'; const HOST = 'anotherreflections.ru'; const KEY = '15455d9f2c7b473bb04336055b792ec9'; // 32-char hex // Создать key-файл если кто-то его удалил. const keyFile = path.join(root, 'public', `${KEY}.txt`); if (!fs.existsSync(keyFile)) { fs.writeFileSync(keyFile, KEY, 'utf-8'); console.log(`created key file: public/${KEY}.txt`); } function extractLocs(xml) { return [...xml.matchAll(/([^<]+)<\/loc>/g)].map((m) => m[1]); } async function fetchText(url) { const r = await fetch(url); if (!r.ok) throw new Error(`HTTP ${r.status} for ${url}`); return r.text(); } async function collectFromRemoteIndex(indexUrl) { const indexXml = await fetchText(indexUrl); const locs = extractLocs(indexXml); // Если это sitemap-index — locs указывают на дочерние sitemap-N.xml. // Если это обычный sitemap — locs уже URL'ы страниц. const looksLikeIndex = / /^sitemap-\d+\.xml$/.test(f)); let urls = []; if (sitemapFiles.length > 0) { for (const f of sitemapFiles) { const xml = fs.readFileSync(path.join(distDir, f), 'utf-8'); urls.push(...extractLocs(xml)); } return urls; } for (const c of ['sitemap-index.xml', 'sitemap.xml']) { const p = path.join(distDir, c); if (fs.existsSync(p)) { const xml = fs.readFileSync(p, 'utf-8'); return extractLocs(xml); } } return null; } let urls = collectFromLocalDist(); if (urls === null || urls.length === 0) { console.log('no local dist/sitemap*.xml — fetching remote sitemap-index.xml'); try { urls = await collectFromRemoteIndex(`${BASE}/sitemap-index.xml`); } catch (e) { console.error(`failed to fetch remote sitemap: ${e.message}`); process.exit(0); } } // Уникальные URL только нашего хоста. urls = [...new Set(urls)].filter((u) => { try { return new URL(u).host === HOST; } catch { return false; } }); if (urls.length === 0) { console.error('no URLs found; nothing to submit'); process.exit(0); } console.log(`Submitting ${urls.length} URLs to IndexNow…`); const payload = { host: HOST, key: KEY, keyLocation: `${BASE}/${KEY}.txt`, urlList: urls, }; async function submit(endpoint) { try { const r = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json; charset=utf-8' }, body: JSON.stringify(payload), }); console.log(` ${endpoint}: HTTP ${r.status}`); } catch (e) { console.error(` ${endpoint}: ${e.message}`); } } await Promise.all([ submit('https://yandex.com/indexnow'), submit('https://api.indexnow.org/indexnow'), ]);