]>
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' |
1e7eb25f | 17 | import { getAccountVideoRateValidator, videoCommentGetValidator, videosGetValidator } 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' |
e4f97bab C |
40 | |
41 | const activityPubClientRouter = express.Router() | |
42 | ||
108af661 | 43 | activityPubClientRouter.get('/accounts?/:name', |
a2431b7d | 44 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), |
4e50b6a1 | 45 | executeIfActivityPub(accountController) |
e4f97bab | 46 | ) |
7006bc63 | 47 | activityPubClientRouter.get('/accounts?/:name/followers', |
a2431b7d | 48 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), |
e4f97bab C |
49 | executeIfActivityPub(asyncMiddleware(accountFollowersController)) |
50 | ) | |
7006bc63 | 51 | activityPubClientRouter.get('/accounts?/:name/following', |
a2431b7d | 52 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), |
e4f97bab C |
53 | executeIfActivityPub(asyncMiddleware(accountFollowingController)) |
54 | ) | |
5c6d985f C |
55 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', |
56 | executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('like'))), | |
57 | executeIfActivityPub(getAccountVideoRate('like')) | |
58 | ) | |
59 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | |
60 | executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('dislike'))), | |
61 | executeIfActivityPub(getAccountVideoRate('dislike')) | |
62 | ) | |
e4f97bab | 63 | |
20494f12 | 64 | activityPubClientRouter.get('/videos/watch/:id', |
98d3324d | 65 | executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))), |
09209296 | 66 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video-with-rights'))), |
da854ddd | 67 | executeIfActivityPub(asyncMiddleware(videoController)) |
4e50b6a1 | 68 | ) |
296c0905 | 69 | activityPubClientRouter.get('/videos/watch/:id/activity', |
09209296 | 70 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video-with-rights'))), |
296c0905 C |
71 | executeIfActivityPub(asyncMiddleware(videoController)) |
72 | ) | |
46531a0a | 73 | activityPubClientRouter.get('/videos/watch/:id/announces', |
96f29c0f | 74 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
75 | executeIfActivityPub(asyncMiddleware(videoAnnouncesController)) |
76 | ) | |
5c6d985f | 77 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', |
4e50b6a1 C |
78 | executeIfActivityPub(asyncMiddleware(videosShareValidator)), |
79 | executeIfActivityPub(asyncMiddleware(videoAnnounceController)) | |
20494f12 | 80 | ) |
46531a0a | 81 | activityPubClientRouter.get('/videos/watch/:id/likes', |
96f29c0f | 82 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
83 | executeIfActivityPub(asyncMiddleware(videoLikesController)) |
84 | ) | |
85 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | |
96f29c0f | 86 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
87 | executeIfActivityPub(asyncMiddleware(videoDislikesController)) |
88 | ) | |
89 | activityPubClientRouter.get('/videos/watch/:id/comments', | |
96f29c0f | 90 | executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), |
46531a0a C |
91 | executeIfActivityPub(asyncMiddleware(videoCommentsController)) |
92 | ) | |
da854ddd C |
93 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', |
94 | executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), | |
95 | executeIfActivityPub(asyncMiddleware(videoCommentController)) | |
96 | ) | |
296c0905 C |
97 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', |
98 | executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), | |
99 | executeIfActivityPub(asyncMiddleware(videoCommentController)) | |
100 | ) | |
da854ddd | 101 | |
8a19bee1 C |
102 | activityPubClientRouter.get('/video-channels/:name', |
103 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | |
20494f12 C |
104 | executeIfActivityPub(asyncMiddleware(videoChannelController)) |
105 | ) | |
8a19bee1 C |
106 | activityPubClientRouter.get('/video-channels/:name/followers', |
107 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | |
7006bc63 C |
108 | executeIfActivityPub(asyncMiddleware(videoChannelFollowersController)) |
109 | ) | |
8a19bee1 C |
110 | activityPubClientRouter.get('/video-channels/:name/following', |
111 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | |
7006bc63 C |
112 | executeIfActivityPub(asyncMiddleware(videoChannelFollowingController)) |
113 | ) | |
20494f12 | 114 | |
c48e82b5 | 115 | activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', |
09209296 C |
116 | executeIfActivityPub(asyncMiddleware(videoFileRedundancyGetValidator)), |
117 | executeIfActivityPub(asyncMiddleware(videoRedundancyController)) | |
118 | ) | |
119 | activityPubClientRouter.get('/redundancy/video-playlists/:streamingPlaylistType/:videoId', | |
120 | executeIfActivityPub(asyncMiddleware(videoPlaylistRedundancyGetValidator)), | |
c48e82b5 C |
121 | executeIfActivityPub(asyncMiddleware(videoRedundancyController)) |
122 | ) | |
123 | ||
e4f97bab C |
124 | // --------------------------------------------------------------------------- |
125 | ||
126 | export { | |
127 | activityPubClientRouter | |
128 | } | |
129 | ||
130 | // --------------------------------------------------------------------------- | |
131 | ||
4e50b6a1 | 132 | function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { |
3fd3ab2d | 133 | const account: AccountModel = res.locals.account |
e4f97bab | 134 | |
4b8f09fa | 135 | return activityPubResponse(activityPubContextify(account.toActivityPubObject()), res) |
e4f97bab C |
136 | } |
137 | ||
138 | async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) { | |
3fd3ab2d | 139 | const account: AccountModel = res.locals.account |
7006bc63 | 140 | const activityPubResult = await actorFollowers(req, account.Actor) |
e4f97bab | 141 | |
4b8f09fa | 142 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
e4f97bab C |
143 | } |
144 | ||
145 | async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) { | |
3fd3ab2d | 146 | const account: AccountModel = res.locals.account |
7006bc63 | 147 | const activityPubResult = await actorFollowing(req, account.Actor) |
e4f97bab | 148 | |
4b8f09fa | 149 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
e4f97bab | 150 | } |
20494f12 | 151 | |
5c6d985f C |
152 | function getAccountVideoRate (rateType: VideoRateType) { |
153 | return (req: express.Request, res: express.Response) => { | |
154 | const accountVideoRate: AccountVideoRateModel = res.locals.accountVideoRate | |
155 | ||
156 | const byActor = accountVideoRate.Account.Actor | |
157 | const url = getRateUrl(rateType, byActor, accountVideoRate.Video) | |
158 | const APObject = rateType === 'like' | |
159 | ? buildLikeActivity(url, byActor, accountVideoRate.Video) | |
1e7eb25f | 160 | : buildDislikeActivity(url, byActor, accountVideoRate.Video) |
5c6d985f C |
161 | |
162 | return activityPubResponse(activityPubContextify(APObject), res) | |
163 | } | |
164 | } | |
165 | ||
1a8dd4da | 166 | async function videoController (req: express.Request, res: express.Response) { |
09209296 C |
167 | // We need more attributes |
168 | const video: VideoModel = await VideoModel.loadForGetAPI(res.locals.video.id) | |
20494f12 | 169 | |
1a8dd4da | 170 | if (video.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(video.url) |
8d1fa36a | 171 | |
40e87e9e C |
172 | // We need captions to render AP object |
173 | video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) | |
174 | ||
2186386c | 175 | const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) |
8fffe21a | 176 | const videoObject = audiencify(video.toActivityPubObject(), audience) |
2ccaeeb3 | 177 | |
296c0905 | 178 | if (req.path.endsWith('/activity')) { |
c48e82b5 | 179 | const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience) |
4b8f09fa | 180 | return activityPubResponse(activityPubContextify(data), res) |
296c0905 C |
181 | } |
182 | ||
4b8f09fa | 183 | return activityPubResponse(activityPubContextify(videoObject), res) |
20494f12 C |
184 | } |
185 | ||
1a8dd4da | 186 | async function videoAnnounceController (req: express.Request, res: express.Response) { |
3fd3ab2d | 187 | const share = res.locals.videoShare as VideoShareModel |
8d1fa36a | 188 | |
1a8dd4da | 189 | if (share.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(share.url) |
8d1fa36a | 190 | |
c48e82b5 | 191 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) |
4e50b6a1 | 192 | |
c48e82b5 | 193 | return activityPubResponse(activityPubContextify(activity), res) |
4e50b6a1 C |
194 | } |
195 | ||
1a8dd4da | 196 | async function videoAnnouncesController (req: express.Request, res: express.Response) { |
46531a0a C |
197 | const video: VideoModel = res.locals.video |
198 | ||
8fffe21a C |
199 | const handler = async (start: number, count: number) => { |
200 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | |
201 | return { | |
202 | total: result.count, | |
203 | data: result.rows.map(r => r.url) | |
204 | } | |
205 | } | |
206 | const json = await activityPubCollectionPagination(getVideoSharesActivityPubUrl(video), handler, req.query.page) | |
46531a0a | 207 | |
8fffe21a | 208 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
209 | } |
210 | ||
1a8dd4da | 211 | async function videoLikesController (req: express.Request, res: express.Response) { |
46531a0a | 212 | const video: VideoModel = res.locals.video |
8fffe21a | 213 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) |
46531a0a | 214 | |
8fffe21a | 215 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
216 | } |
217 | ||
1a8dd4da | 218 | async function videoDislikesController (req: express.Request, res: express.Response) { |
46531a0a | 219 | const video: VideoModel = res.locals.video |
8fffe21a | 220 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) |
46531a0a | 221 | |
8fffe21a | 222 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
223 | } |
224 | ||
1a8dd4da | 225 | async function videoCommentsController (req: express.Request, res: express.Response) { |
46531a0a C |
226 | const video: VideoModel = res.locals.video |
227 | ||
8fffe21a C |
228 | const handler = async (start: number, count: number) => { |
229 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) | |
230 | return { | |
231 | total: result.count, | |
232 | data: result.rows.map(r => r.url) | |
233 | } | |
234 | } | |
235 | const json = await activityPubCollectionPagination(getVideoCommentsActivityPubUrl(video), handler, req.query.page) | |
46531a0a | 236 | |
8fffe21a | 237 | return activityPubResponse(activityPubContextify(json), res) |
46531a0a C |
238 | } |
239 | ||
1a8dd4da | 240 | async function videoChannelController (req: express.Request, res: express.Response) { |
3fd3ab2d | 241 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
20494f12 | 242 | |
4b8f09fa | 243 | return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) |
20494f12 | 244 | } |
da854ddd | 245 | |
1a8dd4da | 246 | async function videoChannelFollowersController (req: express.Request, res: express.Response) { |
7006bc63 C |
247 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
248 | const activityPubResult = await actorFollowers(req, videoChannel.Actor) | |
249 | ||
4b8f09fa | 250 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
7006bc63 C |
251 | } |
252 | ||
1a8dd4da | 253 | async function videoChannelFollowingController (req: express.Request, res: express.Response) { |
7006bc63 C |
254 | const videoChannel: VideoChannelModel = res.locals.videoChannel |
255 | const activityPubResult = await actorFollowing(req, videoChannel.Actor) | |
256 | ||
4b8f09fa | 257 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
7006bc63 C |
258 | } |
259 | ||
1a8dd4da | 260 | async function videoCommentController (req: express.Request, res: express.Response) { |
da854ddd C |
261 | const videoComment: VideoCommentModel = res.locals.videoComment |
262 | ||
1a8dd4da | 263 | if (videoComment.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(videoComment.url) |
8d1fa36a | 264 | |
d7e70384 | 265 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined) |
d6e99e53 | 266 | const isPublic = true // Comments are always public |
2186386c | 267 | const audience = getAudience(videoComment.Account.Actor, isPublic) |
d6e99e53 C |
268 | |
269 | const videoCommentObject = audiencify(videoComment.toActivityPubObject(threadParentComments), audience) | |
270 | ||
296c0905 | 271 | if (req.path.endsWith('/activity')) { |
c48e82b5 | 272 | const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience) |
4b8f09fa | 273 | return activityPubResponse(activityPubContextify(data), res) |
296c0905 C |
274 | } |
275 | ||
4b8f09fa | 276 | return activityPubResponse(activityPubContextify(videoCommentObject), res) |
da854ddd | 277 | } |
7006bc63 | 278 | |
c48e82b5 | 279 | async function videoRedundancyController (req: express.Request, res: express.Response) { |
8d1fa36a | 280 | const videoRedundancy: VideoRedundancyModel = res.locals.videoRedundancy |
1a8dd4da | 281 | if (videoRedundancy.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(videoRedundancy.url) |
8d1fa36a | 282 | |
c48e82b5 C |
283 | const serverActor = await getServerActor() |
284 | ||
285 | const audience = getAudience(serverActor) | |
286 | const object = audiencify(videoRedundancy.toActivityPubObject(), audience) | |
287 | ||
288 | if (req.path.endsWith('/activity')) { | |
289 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) | |
290 | return activityPubResponse(activityPubContextify(data), res) | |
291 | } | |
292 | ||
293 | return activityPubResponse(activityPubContextify(object), res) | |
294 | } | |
295 | ||
7006bc63 C |
296 | // --------------------------------------------------------------------------- |
297 | ||
298 | async function actorFollowing (req: express.Request, actor: ActorModel) { | |
8fffe21a C |
299 | const handler = (start: number, count: number) => { |
300 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) | |
301 | } | |
7006bc63 | 302 | |
babecc3c | 303 | return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) |
7006bc63 C |
304 | } |
305 | ||
306 | async function actorFollowers (req: express.Request, actor: ActorModel) { | |
8fffe21a C |
307 | const handler = (start: number, count: number) => { |
308 | return ActorFollowModel.listAcceptedFollowerUrlsForApi([ actor.id ], undefined, start, count) | |
309 | } | |
7006bc63 | 310 | |
babecc3c | 311 | return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) |
7006bc63 | 312 | } |
4b8f09fa | 313 | |
8fffe21a C |
314 | function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { |
315 | const handler = async (start: number, count: number) => { | |
316 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | |
317 | return { | |
318 | total: result.count, | |
5c6d985f | 319 | data: result.rows.map(r => r.url) |
8fffe21a C |
320 | } |
321 | } | |
322 | return activityPubCollectionPagination(url, handler, req.query.page) | |
4b8f09fa | 323 | } |