diff --git a/.github/workflows/submit-to-bing.yml b/.github/workflows/submit-to-bing.yml new file mode 100644 index 0000000..10501e1 --- /dev/null +++ b/.github/workflows/submit-to-bing.yml @@ -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 diff --git a/package.json b/package.json index b699933..e94f746 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d9f032d..dd7281e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/public/90d513c720ec40e3a57f488239db260c.txt b/public/90d513c720ec40e3a57f488239db260c.txt new file mode 100644 index 0000000..76f524e --- /dev/null +++ b/public/90d513c720ec40e3a57f488239db260c.txt @@ -0,0 +1 @@ +90d513c720ec40e3a57f488239db260c \ No newline at end of file diff --git a/scripts/generate-sitemap-rss.mjs b/scripts/generate-sitemap-rss.mjs index d934be5..871e2d4 100644 --- a/scripts/generate-sitemap-rss.mjs +++ b/scripts/generate-sitemap-rss.mjs @@ -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; diff --git a/scripts/submit-to-bing.mjs b/scripts/submit-to-bing.mjs new file mode 100644 index 0000000..49f11fc --- /dev/null +++ b/scripts/submit-to-bing.mjs @@ -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 }; diff --git a/scripts/track-page-changes.mjs b/scripts/track-page-changes.mjs new file mode 100644 index 0000000..b04e927 --- /dev/null +++ b/scripts/track-page-changes.mjs @@ -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 }; diff --git a/src/middleware.js b/src/middleware.js index e31afff..b985696 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -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).*)', ], };