|
- import axios from 'axios'
- import crypto from 'crypto'
- import fs from 'fs'
- import path from 'path'
-
- const formatDate = (date = new Date) => {
- const year = date.getFullYear ()
- const month = (date.getMonth () + 1).toString ().padStart (2, '0')
- const day = date.getDate ().toString ().padStart (2, '0')
-
- return `${ year }-${ month }-${ day }`
- }
-
- const SITE_TITLE = 'ぼざクリ タグ広場'
- const DOMAIN = 'https://hub.nizika.monster'
- const API_BASE_URL = 'https://hub.nizika.monster/api'
-
- const fetchPosts = async tagName => (await axios.get (`${ API_BASE_URL }/posts`,
- { params: { ...(tagName && { tags: tagName,
- match: 'all',
- limit: '20' }) } })).data.posts
- const fetchPostIds = async () => (await fetchPosts ()).map (post => post.id)
-
- const fetchTags = async () => (await axios.get (`${ API_BASE_URL }/tags`)).data
- const fetchTagNames = async () => (await fetchTags ()).map (tag => tag.name)
-
- const fetchWikiPages = async () => (await axios.get (`${ API_BASE_URL }/wiki`)).data
- const fetchWikiTitles = async () => (await fetchWikiPages ()).map (page => page.title)
-
- const createPostListOutlet = async tagName => `
- <main class="flex-1 overflow-y-auto p-4">
- <div class="mt-4">
- <div class="flex gap-4"><a href="#" class="font-bold">広場</a></div>
- <div class="mt-2">
- <div class="flex flex-wrap gap-6 p-4">
- ${ (await fetchPosts (tagName)).map (post => `
- <a class="w-40 h-40 overflow-hidden rounded-lg shadow-md hover:shadow-lg"
- href="/posts/${ post.id }">
- <img alt="${ post.title }"
- title="${ post.title }"
- loading="eager"
- fetchpriority="high"
- decoding="async"
- class="object-none w-full h-full"
- src="${ post.url }" />
- </a>`).join ('') }
- </div>
- </div>
- </div>
- </main>`
-
- const createPostDetailOutlet = post => `
- <div class="md:flex md:flex-1">
- <div>
- <div>
- ${ ['deerjikist', 'meme', 'character', 'general', 'material', 'meta', 'nico']
- .map (cat => '<ul>' + post.tags.filter (tag => tag.category === cat).map (tag => `
- <li>
- <span>
- <a href="/wiki/${ encodeURIComponent (tag.name) }">?</a>
- </span>
- <a href="/posts?${ new URLSearchParams ({ tags: tag.name }) }">
- ${ tag.name }
- </a>
- <span>${ tag['post_count'] }</span>
- </li>`).join ('') + '</ul>').join ('') }
- </div>
- </div>
- <main class="flex-1 overflow-y-auto p-4">
- <img src="${ post.thumbnail }" alt="${ post.url }" />
- </main>
- </div>`
-
- const posts = (await fetchPosts ()).map (post => [
- `/posts/${ post.id }`,
- `${ post.title || post.url } | ${ SITE_TITLE }`,
- createPostDetailOutlet (post)])
-
- const tags = []
- for (const tag of await fetchTags ())
- {
- tags.push ([`/posts?${ new URLSearchParams ({ tags: tag.name }) }`,
- `${ tag.name } | ${ SITE_TITLE }`,
- await createPostListOutlet (tag.name)])
- }
-
- const wikiPages = (await fetchWikiPages ()).map (page => [
- `/wiki/${ encodeURIComponent (page.title) }`,
- `${ page.title } Wiki | ${ SITE_TITLE }`])
-
- const routes = [
- ['/', `${ SITE_TITLE } 〜 ぼざろクリーチャーシリーズ綜合リンク集`,
- await createPostListOutlet (), `
- <script type="application/ld+json">
- {
- "@context": "http://schema.org",
- "@type": "WebSite",
- "url": "https://hub.nizika.monster",
- "potentialAction": {
- "@type": "SearchAction",
- "target": "https://hub.nizika.monster/posts?tags={search_term}",
- "query-input": "required name=search_term"
- }
- }
- </script>`],
- ...tags,
- ...posts,
- ['/wiki', `Wiki | ${ SITE_TITLE }`],
- ...wikiPages]
-
- const xml = `<?xml version="1.0" encoding="UTF-8"?>
- <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
- ${ routes.map (([route]) => ` <url>
- <loc>${ DOMAIN }${ route }</loc>
- <lastmod>${ formatDate () }</lastmod>
- <changefreq>daily</changefreq>
- </url>`).join ('\n') }
- </urlset>`
-
- const rss = `<?xml version="1.0" encoding="UTF-8"?>
- <rss version="2.0">
- <channel>
- <title>The Series of Bocchi the Rock! Creatures Hub Feed</title>
- <link>${ DOMAIN }/</link>
- <description>最新の投稿、タグ、Wiki ページの一覧</description>
- <language>ja</language>
- <pubDate>${ (new Date).toUTCString () }</pubDate>
- ${ routes.map (([route, title]) => ` <item>
- <title>${ title }</title>
- <link>${ DOMAIN }${ route }</link>
- <guid>${ DOMAIN }${ route }</guid>
- <pubDate>${ (new Date).toUTCString () }</pubDate>
- </item>`).join ('\n') }
- </channel>
- </rss>`
-
- fs.writeFileSync (path.resolve ('dist/sitemap.xml'), xml)
- fs.writeFileSync (path.resolve ('dist/rss.xml'), rss)
-
- const baseHTML = fs.readFileSync (path.resolve ('dist/index.html'), 'utf8')
- let htaccess = fs.readFileSync (path.resolve ('dist/.htaccess'), 'utf8')
- routes.forEach (([route, title, outlet, helmet]) => {
- const fileName = (
- route === '/'
- ? 'index.html'
- : `${ crypto.createHash ('sha256').update (encodeURIComponent (route)).digest ('hex') }.html`)
-
- let html = baseHTML.replace (/(?<=<title>).*?(?=<\/title>)/, title)
- html = html.replace ('<!-- outlet -->', outlet || '')
- html = html.replace ('<!-- helmet -->', helmet || '')
- fs.writeFileSync (path.resolve (`dist/${ fileName }`), html)
-
- const [routeBase, q] = route.split ('?')
- if (route !== '/')
- {
- htaccess = htaccess.replace ('# routes #', `
- RewriteCond %{REQUEST_URI} ^${ routeBase }$
- ${ q ? `RewriteCond %{QUERY_STRING} (^|&)${ q }(&|$)` : '' }
- RewriteRule ^ /${ fileName } [L]
- # routes #
- `)
- }
- })
- fs.writeFileSync (path.resolve ('dist/.htaccess'), htaccess.replace ('# routes #', ''))
|