ニジカ投稿局 https://tv.nizika.tv
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

112 lines
3.3 KiB

  1. import express from 'express'
  2. import truncate from 'lodash-es/truncate.js'
  3. import { ErrorLevel, SitemapStream, streamToPromise } from 'sitemap'
  4. import { logger } from '@server/helpers/logger.js'
  5. import { getServerActor } from '@server/models/application/application.js'
  6. import { buildNSFWFilter } from '../helpers/express-utils.js'
  7. import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants.js'
  8. import { apiRateLimiter, asyncMiddleware } from '../middlewares/index.js'
  9. import { cacheRoute } from '../middlewares/cache/cache.js'
  10. import { AccountModel } from '../models/account/account.js'
  11. import { VideoModel } from '../models/video/video.js'
  12. import { VideoChannelModel } from '../models/video/video-channel.js'
  13. const sitemapRouter = express.Router()
  14. sitemapRouter.use('/sitemap.xml',
  15. apiRateLimiter,
  16. cacheRoute(ROUTE_CACHE_LIFETIME.SITEMAP),
  17. asyncMiddleware(getSitemap)
  18. )
  19. // ---------------------------------------------------------------------------
  20. export {
  21. sitemapRouter
  22. }
  23. // ---------------------------------------------------------------------------
  24. async function getSitemap (req: express.Request, res: express.Response) {
  25. let urls = getSitemapBasicUrls()
  26. urls = urls.concat(await getSitemapLocalVideoUrls())
  27. urls = urls.concat(await getSitemapVideoChannelUrls())
  28. urls = urls.concat(await getSitemapAccountUrls())
  29. const sitemapStream = new SitemapStream({
  30. hostname: WEBSERVER.URL,
  31. errorHandler: (err: Error, level: ErrorLevel) => {
  32. if (level === 'warn') {
  33. logger.warn('Warning in sitemap generation.', { err })
  34. } else if (level === 'throw') {
  35. logger.error('Error in sitemap generation.', { err })
  36. throw err
  37. }
  38. }
  39. })
  40. for (const urlObj of urls) {
  41. sitemapStream.write(urlObj)
  42. }
  43. sitemapStream.end()
  44. const xml = await streamToPromise(sitemapStream)
  45. res.header('Content-Type', 'application/xml')
  46. res.send(xml)
  47. }
  48. async function getSitemapVideoChannelUrls () {
  49. const rows = await VideoChannelModel.listLocalsForSitemap('createdAt')
  50. return rows.map(channel => ({ url: channel.getClientUrl() }))
  51. }
  52. async function getSitemapAccountUrls () {
  53. const rows = await AccountModel.listLocalsForSitemap('createdAt')
  54. return rows.map(account => ({ url: account.getClientUrl() }))
  55. }
  56. async function getSitemapLocalVideoUrls () {
  57. const serverActor = await getServerActor()
  58. const { data } = await VideoModel.listForApi({
  59. start: 0,
  60. count: undefined,
  61. sort: 'createdAt',
  62. displayOnlyForFollower: {
  63. actorId: serverActor.id,
  64. orLocalVideos: true
  65. },
  66. isLocal: true,
  67. nsfw: buildNSFWFilter(),
  68. countVideos: false
  69. })
  70. return data.map(v => ({
  71. url: WEBSERVER.URL + v.getWatchStaticPath(),
  72. video: [
  73. {
  74. // Sitemap title should be < 100 characters
  75. title: truncate(v.name, { length: 100, omission: '...' }),
  76. // Sitemap description should be < 2000 characters
  77. description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
  78. player_loc: WEBSERVER.URL + v.getEmbedStaticPath(),
  79. thumbnail_loc: WEBSERVER.URL + v.getMiniatureStaticPath()
  80. }
  81. ]
  82. }))
  83. }
  84. function getSitemapBasicUrls () {
  85. const paths = [
  86. '/about/instance',
  87. '/videos/local'
  88. ]
  89. return paths.map(p => ({ url: WEBSERVER.URL + p }))
  90. }