121 lines
4.2 KiB
JavaScript
121 lines
4.2 KiB
JavaScript
// IndexNow: уведомить Yandex/Bing о новых/обновлённых URL.
|
||
// Запускается после деплоя из CI или вручную:
|
||
// node scripts/indexnow.js
|
||
//
|
||
// IndexNow ключ хранится в файле public/<key>.txt — он должен быть доступен по
|
||
// тому же URL что и сайт, чтобы поисковики могли подтвердить ownership.
|
||
//
|
||
// Источник URL (по приоритету):
|
||
// 1) dist/sitemap-index.xml (Astro @astrojs/sitemap) + входящие sitemap-*.xml
|
||
// 2) dist/sitemap.txt / dist/sitemap.xml
|
||
// 3) fetch с прод-сайта: https://pushkinohistory.ru/sitemap-index.xml
|
||
// (для запуска из CI после удалённого деплоя — без локального build)
|
||
|
||
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://pushkinohistory.ru';
|
||
const HOST = 'pushkinohistory.ru';
|
||
const KEY = '9018cf11050b4f379b8cec01ae3239bb';
|
||
|
||
// На случай если кто-то удалит файл — пересоздадим автоматически.
|
||
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>([^<]+)<\/loc>/g)].map((m) => m[1].trim());
|
||
}
|
||
|
||
let urls = [];
|
||
const distDir = path.join(root, 'dist');
|
||
const sitemapIndex = path.join(distDir, 'sitemap-index.xml');
|
||
const sitemapTxt = path.join(distDir, 'sitemap.txt');
|
||
const fallbackTxt = path.join(distDir, 'sitemap.xml');
|
||
|
||
if (fs.existsSync(sitemapIndex)) {
|
||
const idx = fs.readFileSync(sitemapIndex, 'utf-8');
|
||
const subs = extractLocs(idx);
|
||
for (const sub of subs) {
|
||
// sub — абсолютный URL вида https://pushkinohistory.ru/sitemap-0.xml
|
||
const fname = sub.split('/').pop();
|
||
const localPath = path.join(distDir, fname);
|
||
if (fs.existsSync(localPath)) {
|
||
const subXml = fs.readFileSync(localPath, 'utf-8');
|
||
urls.push(...extractLocs(subXml));
|
||
} else {
|
||
console.warn(` sub-sitemap not found locally: ${localPath}`);
|
||
}
|
||
}
|
||
} else if (fs.existsSync(sitemapTxt)) {
|
||
urls = fs
|
||
.readFileSync(sitemapTxt, 'utf-8')
|
||
.split(/\r?\n/)
|
||
.map((l) => l.trim())
|
||
.filter((l) => l.startsWith('http'));
|
||
} else if (fs.existsSync(fallbackTxt)) {
|
||
const xml = fs.readFileSync(fallbackTxt, 'utf-8');
|
||
urls = extractLocs(xml);
|
||
} else {
|
||
console.log('no local sitemap found, fetching from production…');
|
||
try {
|
||
const idxResp = await fetch(`${BASE}/sitemap-index.xml`);
|
||
if (!idxResp.ok) throw new Error(`HTTP ${idxResp.status}`);
|
||
const idxXml = await idxResp.text();
|
||
const subs = extractLocs(idxXml);
|
||
for (const sub of subs) {
|
||
const subResp = await fetch(sub);
|
||
if (!subResp.ok) {
|
||
console.warn(` ${sub}: HTTP ${subResp.status}`);
|
||
continue;
|
||
}
|
||
const subXml = await subResp.text();
|
||
urls.push(...extractLocs(subXml));
|
||
}
|
||
} catch (e) {
|
||
console.error(`failed to fetch sitemap from ${BASE}: ${e.message}`);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Дедупликация и фильтрация на всякий случай.
|
||
urls = [...new Set(urls)].filter((u) => u.startsWith(BASE));
|
||
|
||
if (urls.length === 0) {
|
||
console.error('no URLs collected — nothing to submit');
|
||
process.exit(1);
|
||
}
|
||
|
||
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'),
|
||
]);
|