Skip to content

Commit b4b3646

Browse files
committed
Seo
1 parent d04f69b commit b4b3646

4 files changed

Lines changed: 139 additions & 0 deletions

File tree

.github/workflows/pages.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,22 @@ jobs:
5959
- name: Deploy to GitHub Pages
6060
id: deployment
6161
uses: actions/deploy-pages@v4
62+
63+
indexnow:
64+
runs-on: ubuntu-latest
65+
needs: deploy
66+
steps:
67+
- name: Checkout
68+
uses: actions/checkout@v4
69+
70+
- name: Set up Node
71+
uses: actions/setup-node@v4
72+
with:
73+
node-version: "22"
74+
75+
- name: Wait for Pages propagation
76+
run: sleep 30
77+
78+
- name: Submit sitemap URLs to Yandex IndexNow
79+
continue-on-error: true
80+
run: node tools/submit-indexnow.mjs --sitemap https://www.javajub.com/sitemap.xml

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
site/
22
.DS_Store
3+
.idea/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
e83be258c353fa1282249ebe3e69ab3595ad9667f969b329449721fdcaab3b8b

tools/submit-indexnow.mjs

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import fs from "node:fs/promises";
2+
import path from "node:path";
3+
import { parseArgs } from "node:util";
4+
5+
const DEFAULT_HOST = "www.javajub.com";
6+
const DEFAULT_ENDPOINT = "https://yandex.com/indexnow";
7+
const DEFAULT_KEY = "e83be258c353fa1282249ebe3e69ab3595ad9667f969b329449721fdcaab3b8b";
8+
const EXCLUDED_PATHS = new Set(["/404/", "/404.html"]);
9+
10+
const { values } = parseArgs({
11+
options: {
12+
"dry-run": { type: "boolean", default: false },
13+
endpoint: { type: "string", default: DEFAULT_ENDPOINT },
14+
host: { type: "string", default: process.env.INDEXNOW_HOST || DEFAULT_HOST },
15+
key: { type: "string", default: process.env.INDEXNOW_KEY || DEFAULT_KEY },
16+
"key-location": { type: "string" },
17+
limit: { type: "string" },
18+
sitemap: { type: "string" },
19+
},
20+
});
21+
22+
const host = values.host.replace(/^https?:\/\//, "").replace(/\/$/, "");
23+
const siteUrl = `https://${host}`;
24+
const key = values.key;
25+
const keyLocation = values["key-location"] || `${siteUrl}/${key}.txt`;
26+
const sitemapLocation = values.sitemap || `${siteUrl}/sitemap.xml`;
27+
const limit = values.limit ? Number.parseInt(values.limit, 10) : undefined;
28+
29+
if (!/^[a-z0-9_-]{8,128}$/i.test(key)) {
30+
throw new Error("IndexNow key must be 8-128 URL-safe characters.");
31+
}
32+
33+
if (limit !== undefined && (!Number.isInteger(limit) || limit <= 0)) {
34+
throw new Error("--limit must be a positive integer.");
35+
}
36+
37+
async function readText(location) {
38+
if (/^https?:\/\//i.test(location)) {
39+
const response = await fetch(location, {
40+
headers: {
41+
"User-Agent": "JavaJub-IndexNow/1.0",
42+
},
43+
});
44+
if (!response.ok) {
45+
throw new Error(`Failed to fetch ${location}: ${response.status} ${response.statusText}`);
46+
}
47+
return response.text();
48+
}
49+
50+
return fs.readFile(path.resolve(location), "utf8");
51+
}
52+
53+
function decodeXml(value) {
54+
return value
55+
.replaceAll("&amp;", "&")
56+
.replaceAll("&lt;", "<")
57+
.replaceAll("&gt;", ">")
58+
.replaceAll("&quot;", '"')
59+
.replaceAll("&apos;", "'");
60+
}
61+
62+
function extractUrls(sitemapXml) {
63+
const urls = [...sitemapXml.matchAll(/<loc>\s*([^<]+?)\s*<\/loc>/gi)]
64+
.map((match) => decodeXml(match[1].trim()))
65+
.filter((url) => {
66+
try {
67+
const parsedUrl = new URL(url);
68+
return parsedUrl.host === host && !EXCLUDED_PATHS.has(parsedUrl.pathname);
69+
} catch {
70+
return false;
71+
}
72+
});
73+
74+
return [...new Set(urls)].slice(0, limit);
75+
}
76+
77+
async function submit(urlList) {
78+
const payload = {
79+
host,
80+
key,
81+
keyLocation,
82+
urlList,
83+
};
84+
85+
if (values["dry-run"]) {
86+
console.log(`IndexNow dry-run: ${urlList.length} URL(s) would be sent to ${values.endpoint}`);
87+
console.log(`Key location: ${keyLocation}`);
88+
console.log(urlList.slice(0, 20).join("\n"));
89+
if (urlList.length > 20) console.log(`...and ${urlList.length - 20} more`);
90+
return;
91+
}
92+
93+
const response = await fetch(values.endpoint, {
94+
method: "POST",
95+
headers: {
96+
"Content-Type": "application/json; charset=utf-8",
97+
"User-Agent": "JavaJub-IndexNow/1.0",
98+
},
99+
body: JSON.stringify(payload),
100+
});
101+
102+
const body = await response.text();
103+
if (!response.ok) {
104+
throw new Error(`IndexNow request failed: ${response.status} ${response.statusText}\n${body}`);
105+
}
106+
107+
console.log(`IndexNow submitted ${urlList.length} URL(s) to ${values.endpoint}`);
108+
if (body.trim()) console.log(body.trim());
109+
}
110+
111+
const sitemapXml = await readText(sitemapLocation);
112+
const urls = extractUrls(sitemapXml);
113+
114+
if (urls.length === 0) {
115+
throw new Error(`No ${host} URLs found in ${sitemapLocation}`);
116+
}
117+
118+
await submit(urls);

0 commit comments

Comments
 (0)