Files
termi-blog/frontend/scripts/submit-indexnow.mjs
limitcool 3628a46ed1
Some checks failed
docker-images / resolve-build-targets (push) Successful in 7s
ui-regression / playwright-regression (push) Failing after 13m47s
docker-images / build-and-push (push) Failing after 7s
docker-images / submit-indexnow (push) Has been skipped
feat: add SharePanel component for social sharing with QR code support
- Implemented SharePanel component in `SharePanel.astro` for sharing content on social media platforms.
- Integrated QR code generation for WeChat sharing using the `qrcode` library.
- Added localization support for English and Chinese languages.
- Created utility functions in `seo.ts` for building article summaries and FAQs.
- Introduced API routes for serving IndexNow key and generating full LLM catalog and summaries.
- Enhanced SEO capabilities with structured data for articles and pages.
2026-04-02 14:15:21 +08:00

135 lines
3.8 KiB
JavaScript

const DEFAULT_API_PATH = '/api'
const INDEXNOW_ENDPOINT = 'https://api.indexnow.org/indexnow'
function normalizeBase(value) {
return String(value || '').trim().replace(/\/$/, '')
}
function ensureAbsolute(base, path) {
return `${normalizeBase(base)}${String(path).startsWith('/') ? path : `/${path}`}`
}
async function fetchJson(url) {
const response = await fetch(url, {
headers: {
Accept: 'application/json',
},
})
if (!response.ok) {
throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`)
}
return response.json()
}
function collectStaticRoutes(siteSettings) {
const routes = [
'/',
'/about',
'/articles',
'/categories',
'/tags',
'/timeline',
'/reviews',
'/friends',
'/rss.xml',
'/sitemap.xml',
'/llms.txt',
'/llms-full.txt',
'/indexnow-key.txt',
]
if (siteSettings?.ai_enabled || siteSettings?.ai?.enabled) {
routes.push('/ask')
}
return routes
}
async function main() {
const indexNowKey = String(process.env.INDEXNOW_KEY || '').trim()
if (!indexNowKey) {
throw new Error('Missing INDEXNOW_KEY environment variable.')
}
const configuredSiteUrl = normalizeBase(process.env.SITE_URL || process.env.PUBLIC_SITE_URL || '')
const configuredApiBaseUrl = normalizeBase(
process.env.INTERNAL_API_BASE_URL || process.env.PUBLIC_API_BASE_URL || '',
)
const bootstrapApiBaseUrl = configuredApiBaseUrl || `${configuredSiteUrl}${DEFAULT_API_PATH}`
if (!bootstrapApiBaseUrl) {
throw new Error('Missing SITE_URL/PUBLIC_SITE_URL or API base URL for IndexNow submission.')
}
const siteSettings = await fetchJson(`${bootstrapApiBaseUrl}/site_settings`).catch(() => null)
const siteUrl = normalizeBase(configuredSiteUrl || siteSettings?.site_url || '')
if (!siteUrl) {
throw new Error('Unable to determine canonical SITE_URL for IndexNow submission.')
}
const apiBaseUrl = configuredApiBaseUrl || `${siteUrl}${DEFAULT_API_PATH}`
const [posts, categories, tags, reviews] = await Promise.all([
fetchJson(`${apiBaseUrl}/posts`).catch(() => []),
fetchJson(`${apiBaseUrl}/categories`).catch(() => []),
fetchJson(`${apiBaseUrl}/tags`).catch(() => []),
fetchJson(`${apiBaseUrl}/reviews`).catch(() => []),
])
const urls = new Set(collectStaticRoutes(siteSettings).map((path) => ensureAbsolute(siteUrl, path)))
for (const post of posts) {
if (!post?.slug || post?.noindex === true) continue
urls.add(ensureAbsolute(siteUrl, `/articles/${encodeURIComponent(post.slug)}`))
}
for (const category of categories) {
const token = category?.slug || category?.name
if (!token) continue
urls.add(ensureAbsolute(siteUrl, `/categories/${encodeURIComponent(token)}`))
}
for (const tag of tags) {
const token = tag?.slug || tag?.name
if (!token) continue
urls.add(ensureAbsolute(siteUrl, `/tags/${encodeURIComponent(token)}`))
}
for (const review of reviews) {
if (!review?.id) continue
urls.add(ensureAbsolute(siteUrl, `/reviews/${review.id}`))
}
const payload = {
host: new URL(siteUrl).host,
key: indexNowKey,
keyLocation: ensureAbsolute(siteUrl, '/indexnow-key.txt'),
urlList: [...urls],
}
const response = await fetch(INDEXNOW_ENDPOINT, {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body: JSON.stringify(payload),
})
const responseText = await response.text().catch(() => '')
if (!response.ok) {
throw new Error(`IndexNow submission failed: ${response.status} ${response.statusText}\n${responseText}`)
}
console.log(`IndexNow submitted ${payload.urlList.length} URLs for ${siteUrl}`)
if (responseText) {
console.log(responseText)
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : error)
process.exitCode = 1
})