SEO: add bing index-now
Some checks failed
Submit New URLs to Bing / submit-urls (push) Has been cancelled
Tests / test (push) Has been cancelled

This commit is contained in:
selfboot 2024-11-01 13:40:55 +08:00
parent 25cbbc20cd
commit 5334d4b142
8 changed files with 266 additions and 19 deletions

31
.github/workflows/submit-to-bing.yml vendored Normal file
View File

@ -0,0 +1,31 @@
name: Submit New URLs to Bing
on:
push:
branches:
- main
workflow_dispatch:
jobs:
submit-urls:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Install dependencies
run: pnpm install
- name: Submit new URLs to Bing
run: node scripts/submit-to-bing.mjs

View File

@ -37,6 +37,7 @@
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.416.0",
"next": "14.2.12",
"node-fetch": "^3.3.2",
"papaparse": "^5.4.1",
"pizzip": "^3.1.7",
"react": "^18.3.1",

View File

@ -86,6 +86,9 @@ importers:
next:
specifier: 14.2.12
version: 14.2.12(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
node-fetch:
specifier: ^3.3.2
version: 3.3.2
papaparse:
specifier: ^5.4.1
version: 5.4.1
@ -1614,6 +1617,10 @@ packages:
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
data-uri-to-buffer@4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
data-urls@3.0.2:
resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==}
engines: {node: '>=12'}
@ -2001,6 +2008,10 @@ packages:
fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
fflate@0.3.11:
resolution: {integrity: sha512-Rr5QlUeGN1mbOHlaqcSYMKVpPbgLy0AWT/W0EHxA6NGI12yO1jpoui2zBBvU2G824ltM6Ut8BFgfHSBGfkmS0A==}
@ -2047,6 +2058,10 @@ packages:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
formdata-polyfill@4.0.10:
resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==}
engines: {node: '>=12.20.0'}
frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'}
@ -3065,6 +3080,14 @@ packages:
sass:
optional: true
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
node-fetch@3.3.2:
resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
node-int64@0.4.0:
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
@ -3972,6 +3995,10 @@ packages:
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
web-streams-polyfill@3.3.3:
resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==}
engines: {node: '>= 8'}
web-vitals@4.2.2:
resolution: {integrity: sha512-nYfoOqb4EmElljyXU2qdeE76KsvoHdftQKY4DzA9Aw8DervCg2bG634pHLrJ/d6+B4mE3nWTSJv8Mo7B2mbZkw==}
@ -5899,6 +5926,8 @@ snapshots:
damerau-levenshtein@1.0.8: {}
data-uri-to-buffer@4.0.1: {}
data-urls@3.0.2:
dependencies:
abab: 2.0.6
@ -6455,6 +6484,11 @@ snapshots:
dependencies:
bser: 2.1.1
fetch-blob@3.2.0:
dependencies:
node-domexception: 1.0.0
web-streams-polyfill: 3.3.3
fflate@0.3.11: {}
fflate@0.6.10: {}
@ -6504,6 +6538,10 @@ snapshots:
combined-stream: 1.0.8
mime-types: 2.1.13
formdata-polyfill@4.0.10:
dependencies:
fetch-blob: 3.2.0
frac@1.1.2: {}
fs-constants@1.0.0: {}
@ -7938,6 +7976,14 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
node-domexception@1.0.0: {}
node-fetch@3.3.2:
dependencies:
data-uri-to-buffer: 4.0.1
fetch-blob: 3.2.0
formdata-polyfill: 4.0.10
node-int64@0.4.0: {}
node-releases@2.0.18: {}
@ -8954,6 +9000,8 @@ snapshots:
web-namespaces@2.0.1: {}
web-streams-polyfill@3.3.3: {}
web-vitals@4.2.2: {}
webgl-constants@1.1.1: {}

View File

@ -0,0 +1 @@
90d513c720ec40e3a57f488239db260c

View File

@ -37,7 +37,7 @@ async function generateSitemapAndRss() {
`!src/app/[lang]/blog/[slug]`,
]);
console.log(`Found pages for ${lang}:`, pages);
// console.log(`Found pages for ${lang}:`, pages);
let sitemapItems = [];
let rssItems = [];
@ -45,14 +45,14 @@ async function generateSitemapAndRss() {
const dict = getDictionary(lang);
for (const page of pages) {
console.log(`Processing page: ${page}`);
// console.log(`Processing page: ${page}`);
const route = page
.replace("src/app/[lang]", "")
.replace("/page.js", "")
.replace("/index", "");
console.log(`Route: ${route}`);
// console.log(`Route: ${route}`);
try {
const content = fs.readFileSync(page, "utf8");
@ -182,7 +182,7 @@ async function generateSitemapAndRss() {
}
function addToSitemapAndRss(sitemapItems, rssItems, metadata) {
console.log("Metadata:", metadata);
// console.log("Metadata:", metadata);
const url = metadata.canonicalUrl;

View File

@ -0,0 +1,80 @@
import fetch from 'node-fetch';
const DOMAIN = 'gallery.selfboot.cn';
const BING_KEY = '90d513c720ec40e3a57f488239db260c';
const KEY_LOCATION = `https://${DOMAIN}/${BING_KEY}.txt`;
async function submitToBing(urls) {
if (!urls || urls.length === 0) {
console.log('没有URL需要提交');
return;
}
console.log('准备提交以下URL到Bing:', urls);
const payload = {
host: DOMAIN,
key: BING_KEY,
keyLocation: KEY_LOCATION,
urlList: urls
};
try {
console.log('提交payload:', payload);
const response = await fetch('https://api.indexnow.org/IndexNow', {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(payload)
});
const status = response.status;
console.log(`提交状态码: ${status}`);
switch (status) {
case 200:
console.log('URL提交成功');
break;
case 400:
console.error('提交格式错误');
break;
case 403:
console.error('API密钥无效');
break;
case 422:
console.error('URL不属于指定域名或密钥格式不匹配');
break;
case 429:
console.error('请求过于频繁,可能被视为垃圾请求');
break;
default:
console.error(`未知错误: ${status}`);
}
} catch (error) {
console.error('提交过程中发生错误:', error);
}
}
async function submitNewUrlsToBing() {
console.log('开始获取今天更新的页面URL...');
const { getRecentPageUrls } = await import('./track-page-changes.mjs');
const urls = await getRecentPageUrls();
if (urls.length > 0) {
await submitToBing(urls);
} else {
console.log('今天没有更新的页面,不需要提交');
}
}
// 直接执行主函数
console.log('开始执行 bing index now脚本...');
submitNewUrlsToBing().catch((error) => {
console.error('执行过程中发生错误:', error);
process.exit(1);
});
export { submitToBing };

View File

@ -0,0 +1,74 @@
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const DOMAIN = "https://gallery.selfboot.cn";
const LANGUAGES = ["en", "zh"];
async function getRecentPageUrls() {
const { globby } = await import("globby");
let urls = new Set();
const today = new Date().toISOString().split('T')[0]; // 获取今天的日期 YYYY-MM-DD
for (const lang of LANGUAGES) {
// 获取基础页面
const pages = await globby([
`src/app/[lang]/**/page.js`,
`!src/app/[lang]/api`,
`!src/app/[lang]/blog/[slug]`,
]);
// 处理基础页面
for (const page of pages) {
const content = fs.readFileSync(page, "utf8");
const updatedDateMatch = content.match(/updatedDate:\s*"([^"]+)"/);
const updatedDate = updatedDateMatch ? updatedDateMatch[1].split('T')[0] : null;
// 只处理今天更新的页面
if (updatedDate === today) {
const route = page
.replace("src/app/[lang]", "")
.replace("/page.js", "")
.replace("/index", "");
if (route.includes('[chartId]')) {
// 处理动态图表路由
const { dynamicChartConfigs } = await import('../src/app/[lang]/tools/chartrace/dynamicChartConfigs.js');
for (const config of dynamicChartConfigs) {
if (config.updatedDate?.split('T')[0] === today) {
const dynamicRoute = route.replace('[chartId]', config.id);
urls.add(`${DOMAIN}/${lang}${dynamicRoute}`);
}
}
} else {
urls.add(`${DOMAIN}/${lang}${route}`);
}
}
}
// 获取博客文章
const blogPosts = await globby([`src/posts/*/${lang}.md`]);
for (const post of blogPosts) {
const content = fs.readFileSync(post, "utf8");
const { data } = matter(content);
const postDate = new Date(data.date).toISOString().split('T')[0];
// 只处理今天发布的文章
if (postDate === today) {
const slug = path.basename(path.dirname(post));
urls.add(`${DOMAIN}/${lang}/blog/${slug}`);
}
}
}
const recentUrls = Array.from(urls);
if (recentUrls.length > 0) {
console.log('今天有更新的页面');
} else {
console.log('今天没有更新的页面');
}
return recentUrls;
}
export { getRecentPageUrls };

View File

@ -1,38 +1,51 @@
// middleware.js
import { NextResponse } from "next/server";
import { NextResponse } from 'next/server';
const LOCALES = ["en", "zh"];
const LOCALES = ['en', 'zh'];
function getPreferredLocale(request) {
// 检查 cookie
const localeCookie = request.cookies.get("NEXT_LOCALE")?.value;
const localeCookie = request.cookies.get('NEXT_LOCALE')?.value;
if (localeCookie && LOCALES.includes(localeCookie)) {
return localeCookie;
}
// 检查 Accept-Language 头
const acceptLanguage = request.headers.get("Accept-Language");
const acceptLanguage = request.headers.get('Accept-Language');
if (acceptLanguage) {
const preferredLocale = acceptLanguage.split(",")[0].split("-")[0];
const preferredLocale = acceptLanguage.split(',')[0].split('-')[0];
if (LOCALES.includes(preferredLocale)) {
return preferredLocale;
}
}
// 默认语言
return "en";
return 'en';
}
export function middleware(request) {
const pathname = request.nextUrl.pathname;
let response;
// 首先检查是否为静态文件
if (
pathname.endsWith('.xml') ||
pathname.endsWith('.js') ||
pathname.endsWith('.json') ||
pathname.endsWith('.csv') ||
pathname.endsWith('.xlsx') ||
pathname.endsWith('.docx') ||
pathname.endsWith('.txt') ||
pathname.endsWith('.ico')
) {
// 对于静态文件,直接返回,不做任何处理
return NextResponse.next();
}
let response;
// 处理根路径
if (pathname === "/") {
if (pathname === '/') {
const preferredLocale = getPreferredLocale(request);
response = NextResponse.redirect(new URL(`/${preferredLocale}/games`, request.url));
} else if (pathname.endsWith(".xml") || pathname.endsWith(".js") || pathname.endsWith(".json") || pathname.endsWith(".csv") || pathname.endsWith(".xlsx") || pathname.endsWith(".docx")) {
response = NextResponse.next();
} else {
// 处理缺少语言前缀的路径
const pathnameIsMissingLocale = LOCALES.every(
@ -48,13 +61,13 @@ export function middleware(request) {
// 添加自定义 header
if (response) {
response.headers.set("x-pathname", pathname);
response.headers.set('x-pathname', pathname);
} else {
response = NextResponse.next();
response.headers.set("x-pathname", pathname);
response.headers.set('x-pathname', pathname);
}
response.headers.set("X-Robots-Tag", "index,follow");
response.headers.set('X-Robots-Tag', 'index,follow');
return response;
}
@ -65,8 +78,7 @@ export const config = {
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico|robots.txt).*)",
'/((?!api|_next/static|_next/image|robots.txt).*)',
],
};