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