search-videos.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. import express from 'express'
  2. import { sanitizeUrl } from '@server/helpers/core-utils.js'
  3. import { pickSearchVideoQuery } from '@server/helpers/query.js'
  4. import { doJSONRequest } from '@server/helpers/requests.js'
  5. import { CONFIG } from '@server/initializers/config.js'
  6. import { WEBSERVER } from '@server/initializers/constants.js'
  7. import { findLatestAPRedirection } from '@server/lib/activitypub/activity.js'
  8. import { getOrCreateAPVideo } from '@server/lib/activitypub/videos/index.js'
  9. import { Hooks } from '@server/lib/plugins/hooks.js'
  10. import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search.js'
  11. import { getServerActor } from '@server/models/application/application.js'
  12. import { HttpStatusCode, ResultList, Video, VideosSearchQueryAfterSanitize } from '@peertube/peertube-models'
  13. import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils.js'
  14. import { logger } from '../../../helpers/logger.js'
  15. import { getFormattedObjects } from '../../../helpers/utils.js'
  16. import {
  17. asyncMiddleware,
  18. commonVideosFiltersValidator,
  19. openapiOperationDoc,
  20. optionalAuthenticate,
  21. paginationValidator,
  22. setDefaultPagination,
  23. setDefaultSearchSort,
  24. videosSearchSortValidator,
  25. videosSearchValidator
  26. } from '../../../middlewares/index.js'
  27. import { guessAdditionalAttributesFromQuery } from '../../../models/video/formatter/index.js'
  28. import { VideoModel } from '../../../models/video/video.js'
  29. import { MVideoAccountLightBlacklistAllFiles } from '../../../types/models/index.js'
  30. import { searchLocalUrl } from './shared/index.js'
  31. const searchVideosRouter = express.Router()
  32. searchVideosRouter.get('/videos',
  33. openapiOperationDoc({ operationId: 'searchVideos' }),
  34. paginationValidator,
  35. setDefaultPagination,
  36. videosSearchSortValidator,
  37. setDefaultSearchSort,
  38. optionalAuthenticate,
  39. commonVideosFiltersValidator,
  40. videosSearchValidator,
  41. asyncMiddleware(searchVideos)
  42. )
  43. // ---------------------------------------------------------------------------
  44. export { searchVideosRouter }
  45. // ---------------------------------------------------------------------------
  46. function searchVideos (req: express.Request, res: express.Response) {
  47. const query = pickSearchVideoQuery(req.query)
  48. const search = query.search
  49. if (isURISearch(search)) {
  50. return searchVideoURI(search, res)
  51. }
  52. if (isSearchIndexSearch(query)) {
  53. return searchVideosIndex(query, res)
  54. }
  55. return searchVideosDB(query, req, res)
  56. }
  57. async function searchVideosIndex (query: VideosSearchQueryAfterSanitize, res: express.Response) {
  58. const result = await buildMutedForSearchIndex(res)
  59. let body = { ...query, ...result }
  60. // Use the default instance NSFW policy if not specified
  61. if (!body.nsfw) {
  62. const nsfwPolicy = res.locals.oauth
  63. ? res.locals.oauth.token.User.nsfwPolicy
  64. : CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
  65. body.nsfw = nsfwPolicy === 'do_not_list'
  66. ? 'false'
  67. : 'both'
  68. }
  69. body = await Hooks.wrapObject(body, 'filter:api.search.videos.index.list.params')
  70. const url = sanitizeUrl(CONFIG.SEARCH.SEARCH_INDEX.URL) + '/api/v1/search/videos'
  71. try {
  72. logger.debug('Doing videos search index request on %s.', url, { body })
  73. const { body: searchIndexResult } = await doJSONRequest<ResultList<Video>>(url, { method: 'POST', json: body })
  74. const jsonResult = await Hooks.wrapObject(searchIndexResult, 'filter:api.search.videos.index.list.result')
  75. return res.json(jsonResult)
  76. } catch (err) {
  77. logger.warn('Cannot use search index to make video search.', { err })
  78. return res.fail({
  79. status: HttpStatusCode.INTERNAL_SERVER_ERROR_500,
  80. message: 'Cannot use search index to make video search'
  81. })
  82. }
  83. }
  84. async function searchVideosDB (query: VideosSearchQueryAfterSanitize, req: express.Request, res: express.Response) {
  85. const serverActor = await getServerActor()
  86. const apiOptions = await Hooks.wrapObject({
  87. ...query,
  88. displayOnlyForFollower: {
  89. actorId: serverActor.id,
  90. orLocalVideos: true
  91. },
  92. countVideos: getCountVideos(req),
  93. nsfw: buildNSFWFilter(res, query.nsfw),
  94. user: res.locals.oauth
  95. ? res.locals.oauth.token.User
  96. : undefined
  97. }, 'filter:api.search.videos.local.list.params')
  98. const resultList = await Hooks.wrapPromiseFun(
  99. VideoModel.searchAndPopulateAccountAndServer,
  100. apiOptions,
  101. 'filter:api.search.videos.local.list.result'
  102. )
  103. return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
  104. }
  105. async function searchVideoURI (url: string, res: express.Response) {
  106. let video: MVideoAccountLightBlacklistAllFiles
  107. // Check if we can fetch a remote video with the URL
  108. if (isUserAbleToSearchRemoteURI(res)) {
  109. try {
  110. const syncParam = {
  111. rates: false,
  112. shares: false,
  113. comments: false,
  114. refreshVideo: false
  115. }
  116. const result = await getOrCreateAPVideo({
  117. videoObject: await findLatestAPRedirection(url),
  118. syncParam
  119. })
  120. video = result ? result.video : undefined
  121. } catch (err) {
  122. logger.info('Cannot search remote video %s.', url, { err })
  123. }
  124. } else {
  125. video = await searchLocalUrl(sanitizeLocalUrl(url), url => VideoModel.loadByUrlAndPopulateAccountAndFiles(url))
  126. }
  127. return res.json({
  128. total: video ? 1 : 0,
  129. data: video ? [ video.toFormattedJSON() ] : []
  130. })
  131. }
  132. function sanitizeLocalUrl (url: string) {
  133. if (!url) return ''
  134. // Handle alternative video URLs
  135. return url.replace(new RegExp('^' + WEBSERVER.URL + '/w/'), WEBSERVER.URL + '/videos/watch/')
  136. }