]>
Commit | Line | Data |
---|---|---|
e4f97bab C |
1 | // Intercept ActivityPub client requests |
2 | import * as express from 'express' | |
8fffe21a | 3 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' |
d6e99e53 | 4 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' |
8fffe21a | 5 | import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers' |
1e7eb25f | 6 | import { buildAnnounceWithVideoAudience, buildLikeActivity } from '../../lib/activitypub/send' |
e251f170 | 7 | import { audiencify, getAudience } from '../../lib/activitypub/audience' |
c48e82b5 | 8 | import { buildCreateActivity } from '../../lib/activitypub/send/send-create' |
96f29c0f C |
9 | import { |
10 | asyncMiddleware, | |
11 | executeIfActivityPub, | |
12 | localAccountValidator, | |
13 | localVideoChannelValidator, | |
1e7eb25f C |
14 | videosCustomGetValidator, |
15 | videosShareValidator | |
96f29c0f | 16 | } from '../../middlewares' |
418d092a | 17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' |
3fd3ab2d | 18 | import { AccountModel } from '../../models/account/account' |
7006bc63 | 19 | import { ActorModel } from '../../models/activitypub/actor' |
50d6de9c | 20 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
3fd3ab2d C |
21 | import { VideoModel } from '../../models/video/video' |
22 | import { VideoChannelModel } from '../../models/video/video-channel' | |
da854ddd | 23 | import { VideoCommentModel } from '../../models/video/video-comment' |
3fd3ab2d | 24 | import { VideoShareModel } from '../../models/video/video-share' |
98d3324d | 25 | import { cacheRoute } from '../../middlewares/cache' |
8fffe21a C |
26 | import { activityPubResponse } from './utils' |
27 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | |
28 | import { | |
5c6d985f | 29 | getRateUrl, |
8fffe21a C |
30 | getVideoCommentsActivityPubUrl, |
31 | getVideoDislikesActivityPubUrl, | |
32 | getVideoLikesActivityPubUrl, | |
33 | getVideoSharesActivityPubUrl | |
34 | } from '../../lib/activitypub' | |
40e87e9e | 35 | import { VideoCaptionModel } from '../../models/video/video-caption' |
09209296 | 36 | import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy' |
c48e82b5 | 37 | import { getServerActor } from '../../helpers/utils' |
8d1fa36a | 38 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
1e7eb25f | 39 | import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' |
418d092a C |
40 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' |
41 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | |
42 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | |
43 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | |
e4f97bab C |
44 | |
45 | const activityPubClientRouter = express.Router() | |
46 | ||
108af661 | 47 | activityPubClientRouter.get('/accounts?/:name', |
a2431b7d | 48 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), |
4e50b6a1 | 49 | executeIfActivityPub(accountController) |
e4f97bab | 50 | ) |
7006bc63 | 51 | activityPubClientRouter.get('/accounts?/:name/followers', |
a2431b7d | 52 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), |
e4f97bab C |
53 | executeIfActivityPub(asyncMiddleware(accountFollowersController)) |
54 | ) | |
7006bc63 | 55 | activityPubClientRouter.get('/accounts?/:name/following', |
a2431b7d | 56 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), |
e4f97bab C |
57 | executeIfActivityPub(asyncMiddleware(accountFollowingController)) |
58 | ) | |
418d092a C |
59 | activityPubClientRouter.get('/accounts?/:name/playlists', |
60 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), | |
61 | executeIfActivityPub(asyncMiddleware(accountPlaylistsController)) | |
62 | ) | |
5c6d985f C |
63 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', |
64 | executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('like'))), | |
65 | executeIfActivityPub(getAccountVideoRate('like')) | |
66 | ) | |
67 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | |
68 | executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('dislike'))), | |
69 | executeIfActivityPub(getAccountVideoRate('dislike')) | |
70 | ) | |
e4f97bab | 71 | |
20494f12 | 72 | activityPubClientRouter.get('/videos/watch/:id', |
98d3324d | 73 | executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))), |
09209296 | 74 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video-with-rights'))), |
da854ddd | 75 | executeIfActivityPub(asyncMiddleware(videoController)) |
4e50b6a1 | 76 | ) |
296c0905 | 77 | activityPubClientRouter.get('/videos/watch/:id/activity', |
09209296 | 78 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video-with-rights'))), |
296c0905 C |
79 | executeIfActivityPub(asyncMiddleware(videoController)) |
80 | ) | |
46531a0a | 81 | activityPubClientRouter.get('/videos/watch/:id/announces', |
96f29c0f | 82 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
83 | executeIfActivityPub(asyncMiddleware(videoAnnouncesController)) |
84 | ) | |
5c6d985f | 85 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', |
4e50b6a1 C |
86 | executeIfActivityPub(asyncMiddleware(videosShareValidator)), |
87 | executeIfActivityPub(asyncMiddleware(videoAnnounceController)) | |
20494f12 | 88 | ) |
46531a0a | 89 | activityPubClientRouter.get('/videos/watch/:id/likes', |
96f29c0f | 90 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
91 | executeIfActivityPub(asyncMiddleware(videoLikesController)) |
92 | ) | |
93 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | |
96f29c0f | 94 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
95 | executeIfActivityPub(asyncMiddleware(videoDislikesController)) |
96 | ) | |
97 | activityPubClientRouter.get('/videos/watch/:id/comments', | |
96f29c0f | 98 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
99 | executeIfActivityPub(asyncMiddleware(videoCommentsController)) |
100 | ) | |
da854ddd C |
101 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', |
102 | executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), | |
103 | executeIfActivityPub(asyncMiddleware(videoCommentController)) | |
104 | ) | |
296c0905 C |
105 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', |
106 | executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), | |
107 | executeIfActivityPub(asyncMiddleware(videoCommentController)) | |
108 | ) | |
da854ddd | 109 | |
8a19bee1 C |
110 | activityPubClientRouter.get('/video-channels/:name', |
111 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | |
20494f12 C |
112 | executeIfActivityPub(asyncMiddleware(videoChannelController)) |
113 | ) | |
8a19bee1 C |
114 | activityPubClientRouter.get('/video-channels/:name/followers', |
115 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | |
7006bc63 C |
116 | executeIfActivityPub(asyncMiddleware(videoChannelFollowersController)) |
117 | ) | |
8a19bee1 C |
118 | activityPubClientRouter.get('/video-channels/:name/following', |
119 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | |
7006bc63 C |
120 | executeIfActivityPub(asyncMiddleware(videoChannelFollowingController)) |
121 | ) | |
20494f12 | 122 | |
c48e82b5 | 123 | activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', |
09209296 C |
124 | executeIfActivityPub(asyncMiddleware(videoFileRedundancyGetValidator)), |
125 | executeIfActivityPub(asyncMiddleware(videoRedundancyController)) | |
126 | ) | |
127 | activityPubClientRouter.get('/redundancy/video-playlists/:streamingPlaylistType/:videoId', | |
128 | executeIfActivityPub(asyncMiddleware(videoPlaylistRedundancyGetValidator)), | |
c48e82b5 C |
129 | executeIfActivityPub(asyncMiddleware(videoRedundancyController)) |
130 | ) | |
131 | ||
418d092a C |
132 | activityPubClientRouter.get('/video-playlists/:playlistId', |
133 | executeIfActivityPub(asyncMiddleware(videoPlaylistsGetValidator)), | |
134 | executeIfActivityPub(asyncMiddleware(videoPlaylistController)) | |
135 | ) | |
136 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', | |
137 | executeIfActivityPub(asyncMiddleware(videoPlaylistElementAPGetValidator)), | |
138 | executeIfActivityPub(asyncMiddleware(videoPlaylistElementController)) | |
139 | ) | |
140 | ||
e4f97bab C |
141 | // --------------------------------------------------------------------------- |
142 | ||
143 | export { | |
144 | activityPubClientRouter | |
145 | } | |
146 | ||
147 | // --------------------------------------------------------------------------- | |
148 | ||
418d092a | 149 | function accountController (req: express.Request, res: express.Response) { |
3fd3ab2d | 150 | const account: AccountModel = res.locals.account |
e4f97bab | 151 | |
4b8f09fa | 152 | return activityPubResponse(activityPubContextify(account.toActivityPubObject()), res) |
e4f97bab C |
153 | } |
154 | ||
418d092a | 155 | async function accountFollowersController (req: express.Request, res: express.Response) { |
3fd3ab2d | 156 | const account: AccountModel = res.locals.account |
7006bc63 | 157 | const activityPubResult = await actorFollowers(req, account.Actor) |
e4f97bab | 158 | |
4b8f09fa | 159 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
e4f97bab C |
160 | } |
161 | ||
418d092a | 162 | async function accountFollowingController (req: express.Request, res: express.Response) { |
3fd3ab2d | 163 | const account: AccountModel = res.locals.account |
7006bc63 | 164 | const activityPubResult = await actorFollowing(req, account.Actor) |
e4f97bab | 165 | |
4b8f09fa | 166 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
e4f97bab | 167 | } |
20494f12 | 168 | |
418d092a C |
169 | async function accountPlaylistsController (req: express.Request, res: express.Response) { |
170 | const account: AccountModel = res.locals.account | |
171 | const activityPubResult = await actorPlaylists(req, account) | |
172 | ||
173 | return activityPubResponse(activityPubContextify(activityPubResult), res) | |
174 | } | |
175 | ||
5c6d985f C |
176 | function getAccountVideoRate (rateType: VideoRateType) { |
177 | return (req: express.Request, res: express.Response) => { | |
178 | const accountVideoRate: AccountVideoRateModel = res.locals.accountVideoRate | |
179 | ||
180 | const byActor = accountVideoRate.Account.Actor | |
181 | const url = getRateUrl(rateType, byActor, accountVideoRate.Video) | |
182 | const APObject = rateType === 'like' | |
183 | ? buildLikeActivity(url, byActor, accountVideoRate.Video) | |
1e7eb25f | 184 | : buildDislikeActivity(url, byActor, accountVideoRate.Video) |
5c6d985f C |
185 | |
186 | return activityPubResponse(activityPubContextify(APObject), res) | |
187 | } | |
188 | } | |
189 | ||
1a8dd4da | 190 | async function videoController (req: express.Request, res: express.Response) { |
09209296 C |
191 | // We need more attributes |
192 | const video: VideoModel = await VideoModel.loadForGetAPI(res.locals.video.id) | |
20494f12 | 193 | |
1a8dd4da | 194 | if (video.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(video.url) |
8d1fa36a | 195 | |
40e87e9e C |
196 | // We need captions to render AP object |
197 | video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) | |
198 | ||
2186386c | 199 | const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) |
8fffe21a | 200 | const videoObject = audiencify(video.toActivityPubObject(), audience) |
2ccaeeb3 | 201 | |
296c0905 | 202 | if (req.path.endsWith('/activity')) { |
c48e82b5 | 203 | const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience) |
4b8f09fa | 204 | return activityPubResponse(activityPubContextify(data), res) |
296c0905 C |
205 | } |
206 | ||
4b8f09fa | 207 | return activityPubResponse(activityPubContextify(videoObject), res) |
20494f12 C |
208 | } |
209 | ||
1a8dd4da | 210 | async function videoAnnounceController (req: express.Request, res: express.Response) { |
3fd3ab2d | 211 | const share = res.locals.videoShare as VideoShareModel |
8d1fa36a | 212 | |
1a8dd4da | 213 | if (share.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(share.url) |
8d1fa36a | 214 | |
c48e82b5 | 215 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) |
4e50b6a1 | 216 | |
c48e82b5 | 217 | return activityPubResponse(activityPubContextify(activity), res) |
4e50b6a1 C |
218 | } |
219 | ||
1a8dd4da | 220 | async function videoAnnouncesController (req: express.Request, res: express.Response) { |
46531a0a C |
221 | const video: VideoModel = res.locals.video |
222 | ||
8fffe21a C |
223 | const handler = async (start: number, count: number) => { |
224 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | |
225 | return { | |
226 | total: result.count, | |
227 | data: result.rows.map(r => r.url) | |
228 | } | |
229 | } | |
230 | const json = await activityPubCollectionPagination(getVideoSharesActivityPubUrl(video), handler, req.query.page) | |
46531a0a | 231 | |
8fffe21a | 232 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
233 | } |
234 | ||
1a8dd4da | 235 | async function videoLikesController (req: express.Request, res: express.Response) { |
46531a0a | 236 | const video: VideoModel = res.locals.video |
8fffe21a | 237 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) |
46531a0a | 238 | |
8fffe21a | 239 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
240 | } |
241 | ||
1a8dd4da | 242 | async function videoDislikesController (req: express.Request, res: express.Response) { |
46531a0a | 243 | const video: VideoModel = res.locals.video |
8fffe21a | 244 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) |
46531a0a | 245 | |
8fffe21a | 246 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
247 | } |
248 | ||
1a8dd4da | 249 | async function videoCommentsController (req: express.Request, res: express.Response) { |
46531a0a C |
250 | const video: VideoModel = res.locals.video |
251 | ||
8fffe21a C |
252 | const handler = async (start: number, count: number) => { |
253 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) | |
254 | return { | |
255 | total: result.count, | |
256 | data: result.rows.map(r => r.url) | |
257 | } | |
258 | } | |
259 | const json = await activityPubCollectionPagination(getVideoCommentsActivityPubUrl(video), handler, req.query.page) | |
46531a0a | 260 | |
8fffe21a | 261 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
262 | } |
263 | ||
1a8dd4da | 264 | async function videoChannelController (req: express.Request, res: express.Response) { |
3fd3ab2d | 265 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
20494f12 | 266 | |
4b8f09fa | 267 | return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) |
20494f12 | 268 | } |
da854ddd | 269 | |
1a8dd4da | 270 | async function videoChannelFollowersController (req: express.Request, res: express.Response) { |
7006bc63 C |
271 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
272 | const activityPubResult = await actorFollowers(req, videoChannel.Actor) | |
273 | ||
4b8f09fa | 274 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
7006bc63 C |
275 | } |
276 | ||
1a8dd4da | 277 | async function videoChannelFollowingController (req: express.Request, res: express.Response) { |
7006bc63 C |
278 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
279 | const activityPubResult = await actorFollowing(req, videoChannel.Actor) | |
280 | ||
4b8f09fa | 281 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
7006bc63 C |
282 | } |
283 | ||
1a8dd4da | 284 | async function videoCommentController (req: express.Request, res: express.Response) { |
da854ddd C |
285 | const videoComment: VideoCommentModel = res.locals.videoComment |
286 | ||
1a8dd4da | 287 | if (videoComment.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(videoComment.url) |
8d1fa36a | 288 | |
d7e70384 | 289 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined) |
d6e99e53 | 290 | const isPublic = true // Comments are always public |
2186386c | 291 | const audience = getAudience(videoComment.Account.Actor, isPublic) |
d6e99e53 C |
292 | |
293 | const videoCommentObject = audiencify(videoComment.toActivityPubObject(threadParentComments), audience) | |
294 | ||
296c0905 | 295 | if (req.path.endsWith('/activity')) { |
c48e82b5 | 296 | const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience) |
4b8f09fa | 297 | return activityPubResponse(activityPubContextify(data), res) |
296c0905 C |
298 | } |
299 | ||
4b8f09fa | 300 | return activityPubResponse(activityPubContextify(videoCommentObject), res) |
da854ddd | 301 | } |
7006bc63 | 302 | |
c48e82b5 | 303 | async function videoRedundancyController (req: express.Request, res: express.Response) { |
8d1fa36a | 304 | const videoRedundancy: VideoRedundancyModel = res.locals.videoRedundancy |
1a8dd4da | 305 | if (videoRedundancy.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(videoRedundancy.url) |
8d1fa36a | 306 | |
c48e82b5 C |
307 | const serverActor = await getServerActor() |
308 | ||
309 | const audience = getAudience(serverActor) | |
310 | const object = audiencify(videoRedundancy.toActivityPubObject(), audience) | |
311 | ||
312 | if (req.path.endsWith('/activity')) { | |
313 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) | |
314 | return activityPubResponse(activityPubContextify(data), res) | |
315 | } | |
316 | ||
317 | return activityPubResponse(activityPubContextify(object), res) | |
318 | } | |
319 | ||
418d092a C |
320 | async function videoPlaylistController (req: express.Request, res: express.Response) { |
321 | const playlist: VideoPlaylistModel = res.locals.videoPlaylist | |
322 | ||
323 | const json = await playlist.toActivityPubObject() | |
324 | const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) | |
325 | const object = audiencify(json, audience) | |
326 | ||
327 | return activityPubResponse(activityPubContextify(object), res) | |
328 | } | |
329 | ||
330 | async function videoPlaylistElementController (req: express.Request, res: express.Response) { | |
331 | const videoPlaylistElement: VideoPlaylistElementModel = res.locals.videoPlaylistElement | |
332 | ||
333 | const json = videoPlaylistElement.toActivityPubObject() | |
334 | return activityPubResponse(activityPubContextify(json), res) | |
335 | } | |
336 | ||
7006bc63 C |
337 | // --------------------------------------------------------------------------- |
338 | ||
339 | async function actorFollowing (req: express.Request, actor: ActorModel) { | |
8fffe21a C |
340 | const handler = (start: number, count: number) => { |
341 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) | |
342 | } | |
7006bc63 | 343 | |
babecc3c | 344 | return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) |
7006bc63 C |
345 | } |
346 | ||
347 | async function actorFollowers (req: express.Request, actor: ActorModel) { | |
8fffe21a | 348 | const handler = (start: number, count: number) => { |
418d092a C |
349 | return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) |
350 | } | |
351 | ||
352 | return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) | |
353 | } | |
354 | ||
355 | async function actorPlaylists (req: express.Request, account: AccountModel) { | |
356 | const handler = (start: number, count: number) => { | |
357 | return VideoPlaylistModel.listUrlsOfForAP(account.id, start, count) | |
8fffe21a | 358 | } |
7006bc63 | 359 | |
babecc3c | 360 | return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) |
7006bc63 | 361 | } |
4b8f09fa | 362 | |
8fffe21a C |
363 | function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { |
364 | const handler = async (start: number, count: number) => { | |
365 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | |
366 | return { | |
367 | total: result.count, | |
5c6d985f | 368 | data: result.rows.map(r => r.url) |
8fffe21a C |
369 | } |
370 | } | |
371 | return activityPubCollectionPagination(url, handler, req.query.page) | |
4b8f09fa | 372 | } |