Fix private video download
[oweals/peertube.git] / server / controllers / api / search.ts
1 import * as express from 'express'
2 import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
3 import { getFormattedObjects, getServerActor } from '../../helpers/utils'
4 import { VideoModel } from '../../models/video/video'
5 import {
6   asyncMiddleware,
7   commonVideosFiltersValidator,
8   optionalAuthenticate,
9   paginationValidator,
10   setDefaultPagination,
11   setDefaultSearchSort,
12   videoChannelsSearchSortValidator,
13   videoChannelsSearchValidator,
14   videosSearchSortValidator,
15   videosSearchValidator
16 } from '../../middlewares'
17 import { VideoChannelsSearchQuery, VideosSearchQuery } from '../../../shared/models/search'
18 import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel } from '../../lib/activitypub'
19 import { logger } from '../../helpers/logger'
20 import { VideoChannelModel } from '../../models/video/video-channel'
21 import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
22 import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models'
23
24 const searchRouter = express.Router()
25
26 searchRouter.get('/videos',
27   paginationValidator,
28   setDefaultPagination,
29   videosSearchSortValidator,
30   setDefaultSearchSort,
31   optionalAuthenticate,
32   commonVideosFiltersValidator,
33   videosSearchValidator,
34   asyncMiddleware(searchVideos)
35 )
36
37 searchRouter.get('/video-channels',
38   paginationValidator,
39   setDefaultPagination,
40   videoChannelsSearchSortValidator,
41   setDefaultSearchSort,
42   optionalAuthenticate,
43   videoChannelsSearchValidator,
44   asyncMiddleware(searchVideoChannels)
45 )
46
47 // ---------------------------------------------------------------------------
48
49 export { searchRouter }
50
51 // ---------------------------------------------------------------------------
52
53 function searchVideoChannels (req: express.Request, res: express.Response) {
54   const query: VideoChannelsSearchQuery = req.query
55   const search = query.search
56
57   const isURISearch = search.startsWith('http://') || search.startsWith('https://')
58
59   const parts = search.split('@')
60
61   // Handle strings like @toto@example.com
62   if (parts.length === 3 && parts[0].length === 0) parts.shift()
63   const isWebfingerSearch = parts.length === 2 && parts.every(p => p && p.indexOf(' ') === -1)
64
65   if (isURISearch || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res)
66
67   // @username -> username to search in DB
68   if (query.search.startsWith('@')) query.search = query.search.replace(/^@/, '')
69   return searchVideoChannelsDB(query, res)
70 }
71
72 async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: express.Response) {
73   const serverActor = await getServerActor()
74
75   const options = {
76     actorId: serverActor.id,
77     search: query.search,
78     start: query.start,
79     count: query.count,
80     sort: query.sort
81   }
82   const resultList = await VideoChannelModel.searchForApi(options)
83
84   return res.json(getFormattedObjects(resultList.data, resultList.total))
85 }
86
87 async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) {
88   let videoChannel: MChannelAccountDefault
89   let uri = search
90
91   if (isWebfingerSearch) {
92     try {
93       uri = await loadActorUrlOrGetFromWebfinger(search)
94     } catch (err) {
95       logger.warn('Cannot load actor URL or get from webfinger.', { search, err })
96
97       return res.json({ total: 0, data: [] })
98     }
99   }
100
101   if (isUserAbleToSearchRemoteURI(res)) {
102     try {
103       const actor = await getOrCreateActorAndServerAndModel(uri, 'all', true, true)
104       videoChannel = actor.VideoChannel
105     } catch (err) {
106       logger.info('Cannot search remote video channel %s.', uri, { err })
107     }
108   } else {
109     videoChannel = await VideoChannelModel.loadByUrlAndPopulateAccount(uri)
110   }
111
112   return res.json({
113     total: videoChannel ? 1 : 0,
114     data: videoChannel ? [ videoChannel.toFormattedJSON() ] : []
115   })
116 }
117
118 function searchVideos (req: express.Request, res: express.Response) {
119   const query: VideosSearchQuery = req.query
120   const search = query.search
121   if (search && (search.startsWith('http://') || search.startsWith('https://'))) {
122     return searchVideoURI(search, res)
123   }
124
125   return searchVideosDB(query, res)
126 }
127
128 async function searchVideosDB (query: VideosSearchQuery, res: express.Response) {
129   const options = Object.assign(query, {
130     includeLocalVideos: true,
131     nsfw: buildNSFWFilter(res, query.nsfw),
132     filter: query.filter,
133     user: res.locals.oauth ? res.locals.oauth.token.User : undefined
134   })
135   const resultList = await VideoModel.searchAndPopulateAccountAndServer(options)
136
137   return res.json(getFormattedObjects(resultList.data, resultList.total))
138 }
139
140 async function searchVideoURI (url: string, res: express.Response) {
141   let video: MVideoAccountLightBlacklistAllFiles
142
143   // Check if we can fetch a remote video with the URL
144   if (isUserAbleToSearchRemoteURI(res)) {
145     try {
146       const syncParam = {
147         likes: false,
148         dislikes: false,
149         shares: false,
150         comments: false,
151         thumbnail: true,
152         refreshVideo: false
153       }
154
155       const result = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam })
156       video = result ? result.video : undefined
157     } catch (err) {
158       logger.info('Cannot search remote video %s.', url, { err })
159     }
160   } else {
161     video = await VideoModel.loadByUrlAndPopulateAccount(url)
162   }
163
164   return res.json({
165     total: video ? 1 : 0,
166     data: video ? [ video.toFormattedJSON() ] : []
167   })
168 }