diff options
Diffstat (limited to 'server/server/controllers/activitypub/client.ts')
-rw-r--r-- | server/server/controllers/activitypub/client.ts | 485 |
1 files changed, 485 insertions, 0 deletions
diff --git a/server/server/controllers/activitypub/client.ts b/server/server/controllers/activitypub/client.ts new file mode 100644 index 000000000..5d5e43bf5 --- /dev/null +++ b/server/server/controllers/activitypub/client.ts | |||
@@ -0,0 +1,485 @@ | |||
1 | import cors from 'cors' | ||
2 | import express from 'express' | ||
3 | import { VideoCommentObject, VideoPlaylistPrivacy, VideoPrivacy, VideoRateType } from '@peertube/peertube-models' | ||
4 | import { activityPubCollectionPagination } from '@server/lib/activitypub/collection.js' | ||
5 | import { getContextFilter } from '@server/lib/activitypub/context.js' | ||
6 | import { getServerActor } from '@server/models/application/application.js' | ||
7 | import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models/index.js' | ||
8 | import { activityPubContextify } from '../../helpers/activity-pub-utils.js' | ||
9 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants.js' | ||
10 | import { audiencify, getAudience } from '../../lib/activitypub/audience.js' | ||
11 | import { buildAnnounceWithVideoAudience, buildLikeActivity } from '../../lib/activitypub/send/index.js' | ||
12 | import { buildCreateActivity } from '../../lib/activitypub/send/send-create.js' | ||
13 | import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike.js' | ||
14 | import { | ||
15 | getLocalVideoCommentsActivityPubUrl, | ||
16 | getLocalVideoDislikesActivityPubUrl, | ||
17 | getLocalVideoLikesActivityPubUrl, | ||
18 | getLocalVideoSharesActivityPubUrl | ||
19 | } from '../../lib/activitypub/url.js' | ||
20 | import { cacheRoute } from '../../middlewares/cache/cache.js' | ||
21 | import { | ||
22 | activityPubRateLimiter, | ||
23 | asyncMiddleware, | ||
24 | ensureIsLocalChannel, | ||
25 | executeIfActivityPub, | ||
26 | localAccountValidator, | ||
27 | videoChannelsNameWithHostValidator, | ||
28 | videosCustomGetValidator, | ||
29 | videosShareValidator | ||
30 | } from '../../middlewares/index.js' | ||
31 | import { | ||
32 | getAccountVideoRateValidatorFactory, | ||
33 | getVideoLocalViewerValidator, | ||
34 | videoCommentGetValidator | ||
35 | } from '../../middlewares/validators/index.js' | ||
36 | import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy.js' | ||
37 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists.js' | ||
38 | import { AccountVideoRateModel } from '../../models/account/account-video-rate.js' | ||
39 | import { AccountModel } from '../../models/account/account.js' | ||
40 | import { ActorFollowModel } from '../../models/actor/actor-follow.js' | ||
41 | import { VideoCommentModel } from '../../models/video/video-comment.js' | ||
42 | import { VideoPlaylistModel } from '../../models/video/video-playlist.js' | ||
43 | import { VideoShareModel } from '../../models/video/video-share.js' | ||
44 | import { activityPubResponse } from './utils.js' | ||
45 | |||
46 | const activityPubClientRouter = express.Router() | ||
47 | activityPubClientRouter.use(cors()) | ||
48 | |||
49 | // Intercept ActivityPub client requests | ||
50 | |||
51 | activityPubClientRouter.get( | ||
52 | [ '/accounts?/:name', '/accounts?/:name/video-channels', '/a/:name', '/a/:name/video-channels' ], | ||
53 | executeIfActivityPub, | ||
54 | activityPubRateLimiter, | ||
55 | asyncMiddleware(localAccountValidator), | ||
56 | asyncMiddleware(accountController) | ||
57 | ) | ||
58 | activityPubClientRouter.get('/accounts?/:name/followers', | ||
59 | executeIfActivityPub, | ||
60 | activityPubRateLimiter, | ||
61 | asyncMiddleware(localAccountValidator), | ||
62 | asyncMiddleware(accountFollowersController) | ||
63 | ) | ||
64 | activityPubClientRouter.get('/accounts?/:name/following', | ||
65 | executeIfActivityPub, | ||
66 | activityPubRateLimiter, | ||
67 | asyncMiddleware(localAccountValidator), | ||
68 | asyncMiddleware(accountFollowingController) | ||
69 | ) | ||
70 | activityPubClientRouter.get('/accounts?/:name/playlists', | ||
71 | executeIfActivityPub, | ||
72 | activityPubRateLimiter, | ||
73 | asyncMiddleware(localAccountValidator), | ||
74 | asyncMiddleware(accountPlaylistsController) | ||
75 | ) | ||
76 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', | ||
77 | executeIfActivityPub, | ||
78 | activityPubRateLimiter, | ||
79 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), | ||
80 | asyncMiddleware(getAccountVideoRateValidatorFactory('like')), | ||
81 | asyncMiddleware(getAccountVideoRateFactory('like')) | ||
82 | ) | ||
83 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | ||
84 | executeIfActivityPub, | ||
85 | activityPubRateLimiter, | ||
86 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), | ||
87 | asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')), | ||
88 | asyncMiddleware(getAccountVideoRateFactory('dislike')) | ||
89 | ) | ||
90 | |||
91 | activityPubClientRouter.get( | ||
92 | [ '/videos/watch/:id', '/w/:id' ], | ||
93 | executeIfActivityPub, | ||
94 | activityPubRateLimiter, | ||
95 | cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS), | ||
96 | asyncMiddleware(videosCustomGetValidator('all')), | ||
97 | asyncMiddleware(videoController) | ||
98 | ) | ||
99 | activityPubClientRouter.get('/videos/watch/:id/activity', | ||
100 | executeIfActivityPub, | ||
101 | activityPubRateLimiter, | ||
102 | asyncMiddleware(videosCustomGetValidator('all')), | ||
103 | asyncMiddleware(videoController) | ||
104 | ) | ||
105 | activityPubClientRouter.get('/videos/watch/:id/announces', | ||
106 | executeIfActivityPub, | ||
107 | activityPubRateLimiter, | ||
108 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | ||
109 | asyncMiddleware(videoAnnouncesController) | ||
110 | ) | ||
111 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | ||
112 | executeIfActivityPub, | ||
113 | activityPubRateLimiter, | ||
114 | asyncMiddleware(videosShareValidator), | ||
115 | asyncMiddleware(videoAnnounceController) | ||
116 | ) | ||
117 | activityPubClientRouter.get('/videos/watch/:id/likes', | ||
118 | executeIfActivityPub, | ||
119 | activityPubRateLimiter, | ||
120 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | ||
121 | asyncMiddleware(videoLikesController) | ||
122 | ) | ||
123 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | ||
124 | executeIfActivityPub, | ||
125 | activityPubRateLimiter, | ||
126 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | ||
127 | asyncMiddleware(videoDislikesController) | ||
128 | ) | ||
129 | activityPubClientRouter.get('/videos/watch/:id/comments', | ||
130 | executeIfActivityPub, | ||
131 | activityPubRateLimiter, | ||
132 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), | ||
133 | asyncMiddleware(videoCommentsController) | ||
134 | ) | ||
135 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', | ||
136 | executeIfActivityPub, | ||
137 | activityPubRateLimiter, | ||
138 | asyncMiddleware(videoCommentGetValidator), | ||
139 | asyncMiddleware(videoCommentController) | ||
140 | ) | ||
141 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', | ||
142 | executeIfActivityPub, | ||
143 | activityPubRateLimiter, | ||
144 | asyncMiddleware(videoCommentGetValidator), | ||
145 | asyncMiddleware(videoCommentController) | ||
146 | ) | ||
147 | |||
148 | activityPubClientRouter.get( | ||
149 | [ '/video-channels/:nameWithHost', '/video-channels/:nameWithHost/videos', '/c/:nameWithHost', '/c/:nameWithHost/videos' ], | ||
150 | executeIfActivityPub, | ||
151 | activityPubRateLimiter, | ||
152 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
153 | ensureIsLocalChannel, | ||
154 | asyncMiddleware(videoChannelController) | ||
155 | ) | ||
156 | activityPubClientRouter.get('/video-channels/:nameWithHost/followers', | ||
157 | executeIfActivityPub, | ||
158 | activityPubRateLimiter, | ||
159 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
160 | ensureIsLocalChannel, | ||
161 | asyncMiddleware(videoChannelFollowersController) | ||
162 | ) | ||
163 | activityPubClientRouter.get('/video-channels/:nameWithHost/following', | ||
164 | executeIfActivityPub, | ||
165 | activityPubRateLimiter, | ||
166 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
167 | ensureIsLocalChannel, | ||
168 | asyncMiddleware(videoChannelFollowingController) | ||
169 | ) | ||
170 | activityPubClientRouter.get('/video-channels/:nameWithHost/playlists', | ||
171 | executeIfActivityPub, | ||
172 | activityPubRateLimiter, | ||
173 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
174 | ensureIsLocalChannel, | ||
175 | asyncMiddleware(videoChannelPlaylistsController) | ||
176 | ) | ||
177 | |||
178 | activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', | ||
179 | executeIfActivityPub, | ||
180 | activityPubRateLimiter, | ||
181 | asyncMiddleware(videoFileRedundancyGetValidator), | ||
182 | asyncMiddleware(videoRedundancyController) | ||
183 | ) | ||
184 | activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId', | ||
185 | executeIfActivityPub, | ||
186 | activityPubRateLimiter, | ||
187 | asyncMiddleware(videoPlaylistRedundancyGetValidator), | ||
188 | asyncMiddleware(videoRedundancyController) | ||
189 | ) | ||
190 | |||
191 | activityPubClientRouter.get( | ||
192 | [ '/video-playlists/:playlistId', '/videos/watch/playlist/:playlistId', '/w/p/:playlistId' ], | ||
193 | executeIfActivityPub, | ||
194 | activityPubRateLimiter, | ||
195 | asyncMiddleware(videoPlaylistsGetValidator('all')), | ||
196 | asyncMiddleware(videoPlaylistController) | ||
197 | ) | ||
198 | activityPubClientRouter.get('/video-playlists/:playlistId/videos/:playlistElementId', | ||
199 | executeIfActivityPub, | ||
200 | activityPubRateLimiter, | ||
201 | asyncMiddleware(videoPlaylistElementAPGetValidator), | ||
202 | asyncMiddleware(videoPlaylistElementController) | ||
203 | ) | ||
204 | |||
205 | activityPubClientRouter.get('/videos/local-viewer/:localViewerId', | ||
206 | executeIfActivityPub, | ||
207 | activityPubRateLimiter, | ||
208 | asyncMiddleware(getVideoLocalViewerValidator), | ||
209 | asyncMiddleware(getVideoLocalViewerController) | ||
210 | ) | ||
211 | |||
212 | // --------------------------------------------------------------------------- | ||
213 | |||
214 | export { | ||
215 | activityPubClientRouter | ||
216 | } | ||
217 | |||
218 | // --------------------------------------------------------------------------- | ||
219 | |||
220 | async function accountController (req: express.Request, res: express.Response) { | ||
221 | const account = res.locals.account | ||
222 | |||
223 | return activityPubResponse(activityPubContextify(await account.toActivityPubObject(), 'Actor', getContextFilter()), res) | ||
224 | } | ||
225 | |||
226 | async function accountFollowersController (req: express.Request, res: express.Response) { | ||
227 | const account = res.locals.account | ||
228 | const activityPubResult = await actorFollowers(req, account.Actor) | ||
229 | |||
230 | return activityPubResponse(activityPubContextify(activityPubResult, 'Collection', getContextFilter()), res) | ||
231 | } | ||
232 | |||
233 | async function accountFollowingController (req: express.Request, res: express.Response) { | ||
234 | const account = res.locals.account | ||
235 | const activityPubResult = await actorFollowing(req, account.Actor) | ||
236 | |||
237 | return activityPubResponse(activityPubContextify(activityPubResult, 'Collection', getContextFilter()), res) | ||
238 | } | ||
239 | |||
240 | async function accountPlaylistsController (req: express.Request, res: express.Response) { | ||
241 | const account = res.locals.account | ||
242 | const activityPubResult = await actorPlaylists(req, { account }) | ||
243 | |||
244 | return activityPubResponse(activityPubContextify(activityPubResult, 'Collection', getContextFilter()), res) | ||
245 | } | ||
246 | |||
247 | async function videoChannelPlaylistsController (req: express.Request, res: express.Response) { | ||
248 | const channel = res.locals.videoChannel | ||
249 | const activityPubResult = await actorPlaylists(req, { channel }) | ||
250 | |||
251 | return activityPubResponse(activityPubContextify(activityPubResult, 'Collection', getContextFilter()), res) | ||
252 | } | ||
253 | |||
254 | function getAccountVideoRateFactory (rateType: VideoRateType) { | ||
255 | return (req: express.Request, res: express.Response) => { | ||
256 | const accountVideoRate = res.locals.accountVideoRate | ||
257 | |||
258 | const byActor = accountVideoRate.Account.Actor | ||
259 | const APObject = rateType === 'like' | ||
260 | ? buildLikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video) | ||
261 | : buildDislikeActivity(accountVideoRate.url, byActor, accountVideoRate.Video) | ||
262 | |||
263 | return activityPubResponse(activityPubContextify(APObject, 'Rate', getContextFilter()), res) | ||
264 | } | ||
265 | } | ||
266 | |||
267 | async function videoController (req: express.Request, res: express.Response) { | ||
268 | const video = res.locals.videoAll | ||
269 | |||
270 | if (redirectIfNotOwned(video.url, res)) return | ||
271 | |||
272 | // We need captions to render AP object | ||
273 | const videoAP = await video.lightAPToFullAP(undefined) | ||
274 | |||
275 | const audience = getAudience(videoAP.VideoChannel.Account.Actor, videoAP.privacy === VideoPrivacy.PUBLIC) | ||
276 | const videoObject = audiencify(await videoAP.toActivityPubObject(), audience) | ||
277 | |||
278 | if (req.path.endsWith('/activity')) { | ||
279 | const data = buildCreateActivity(videoAP.url, video.VideoChannel.Account.Actor, videoObject, audience) | ||
280 | return activityPubResponse(activityPubContextify(data, 'Video', getContextFilter()), res) | ||
281 | } | ||
282 | |||
283 | return activityPubResponse(activityPubContextify(videoObject, 'Video', getContextFilter()), res) | ||
284 | } | ||
285 | |||
286 | async function videoAnnounceController (req: express.Request, res: express.Response) { | ||
287 | const share = res.locals.videoShare | ||
288 | |||
289 | if (redirectIfNotOwned(share.url, res)) return | ||
290 | |||
291 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) | ||
292 | |||
293 | return activityPubResponse(activityPubContextify(activity, 'Announce', getContextFilter()), res) | ||
294 | } | ||
295 | |||
296 | async function videoAnnouncesController (req: express.Request, res: express.Response) { | ||
297 | const video = res.locals.onlyImmutableVideo | ||
298 | |||
299 | if (redirectIfNotOwned(video.url, res)) return | ||
300 | |||
301 | const handler = async (start: number, count: number) => { | ||
302 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | ||
303 | return { | ||
304 | total: result.total, | ||
305 | data: result.data.map(r => r.url) | ||
306 | } | ||
307 | } | ||
308 | const json = await activityPubCollectionPagination(getLocalVideoSharesActivityPubUrl(video), handler, req.query.page) | ||
309 | |||
310 | return activityPubResponse(activityPubContextify(json, 'Collection', getContextFilter()), res) | ||
311 | } | ||
312 | |||
313 | async function videoLikesController (req: express.Request, res: express.Response) { | ||
314 | const video = res.locals.onlyImmutableVideo | ||
315 | |||
316 | if (redirectIfNotOwned(video.url, res)) return | ||
317 | |||
318 | const json = await videoRates(req, 'like', video, getLocalVideoLikesActivityPubUrl(video)) | ||
319 | |||
320 | return activityPubResponse(activityPubContextify(json, 'Collection', getContextFilter()), res) | ||
321 | } | ||
322 | |||
323 | async function videoDislikesController (req: express.Request, res: express.Response) { | ||
324 | const video = res.locals.onlyImmutableVideo | ||
325 | |||
326 | if (redirectIfNotOwned(video.url, res)) return | ||
327 | |||
328 | const json = await videoRates(req, 'dislike', video, getLocalVideoDislikesActivityPubUrl(video)) | ||
329 | |||
330 | return activityPubResponse(activityPubContextify(json, 'Collection', getContextFilter()), res) | ||
331 | } | ||
332 | |||
333 | async function videoCommentsController (req: express.Request, res: express.Response) { | ||
334 | const video = res.locals.onlyImmutableVideo | ||
335 | |||
336 | if (redirectIfNotOwned(video.url, res)) return | ||
337 | |||
338 | const handler = async (start: number, count: number) => { | ||
339 | const result = await VideoCommentModel.listAndCountByVideoForAP({ video, start, count }) | ||
340 | |||
341 | return { | ||
342 | total: result.total, | ||
343 | data: result.data.map(r => r.url) | ||
344 | } | ||
345 | } | ||
346 | const json = await activityPubCollectionPagination(getLocalVideoCommentsActivityPubUrl(video), handler, req.query.page) | ||
347 | |||
348 | return activityPubResponse(activityPubContextify(json, 'Collection', getContextFilter()), res) | ||
349 | } | ||
350 | |||
351 | async function videoChannelController (req: express.Request, res: express.Response) { | ||
352 | const videoChannel = res.locals.videoChannel | ||
353 | |||
354 | return activityPubResponse(activityPubContextify(await videoChannel.toActivityPubObject(), 'Actor', getContextFilter()), res) | ||
355 | } | ||
356 | |||
357 | async function videoChannelFollowersController (req: express.Request, res: express.Response) { | ||
358 | const videoChannel = res.locals.videoChannel | ||
359 | const activityPubResult = await actorFollowers(req, videoChannel.Actor) | ||
360 | |||
361 | return activityPubResponse(activityPubContextify(activityPubResult, 'Collection', getContextFilter()), res) | ||
362 | } | ||
363 | |||
364 | async function videoChannelFollowingController (req: express.Request, res: express.Response) { | ||
365 | const videoChannel = res.locals.videoChannel | ||
366 | const activityPubResult = await actorFollowing(req, videoChannel.Actor) | ||
367 | |||
368 | return activityPubResponse(activityPubContextify(activityPubResult, 'Collection', getContextFilter()), res) | ||
369 | } | ||
370 | |||
371 | async function videoCommentController (req: express.Request, res: express.Response) { | ||
372 | const videoComment = res.locals.videoCommentFull | ||
373 | |||
374 | if (redirectIfNotOwned(videoComment.url, res)) return | ||
375 | |||
376 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined) | ||
377 | const isPublic = true // Comments are always public | ||
378 | let videoCommentObject = videoComment.toActivityPubObject(threadParentComments) | ||
379 | |||
380 | if (videoComment.Account) { | ||
381 | const audience = getAudience(videoComment.Account.Actor, isPublic) | ||
382 | videoCommentObject = audiencify(videoCommentObject, audience) | ||
383 | |||
384 | if (req.path.endsWith('/activity')) { | ||
385 | const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject as VideoCommentObject, audience) | ||
386 | return activityPubResponse(activityPubContextify(data, 'Comment', getContextFilter()), res) | ||
387 | } | ||
388 | } | ||
389 | |||
390 | return activityPubResponse(activityPubContextify(videoCommentObject, 'Comment', getContextFilter()), res) | ||
391 | } | ||
392 | |||
393 | async function videoRedundancyController (req: express.Request, res: express.Response) { | ||
394 | const videoRedundancy = res.locals.videoRedundancy | ||
395 | |||
396 | if (redirectIfNotOwned(videoRedundancy.url, res)) return | ||
397 | |||
398 | const serverActor = await getServerActor() | ||
399 | |||
400 | const audience = getAudience(serverActor) | ||
401 | const object = audiencify(videoRedundancy.toActivityPubObject(), audience) | ||
402 | |||
403 | if (req.path.endsWith('/activity')) { | ||
404 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) | ||
405 | return activityPubResponse(activityPubContextify(data, 'CacheFile', getContextFilter()), res) | ||
406 | } | ||
407 | |||
408 | return activityPubResponse(activityPubContextify(object, 'CacheFile', getContextFilter()), res) | ||
409 | } | ||
410 | |||
411 | async function videoPlaylistController (req: express.Request, res: express.Response) { | ||
412 | const playlist = res.locals.videoPlaylistFull | ||
413 | |||
414 | if (redirectIfNotOwned(playlist.url, res)) return | ||
415 | |||
416 | // We need more attributes | ||
417 | playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) | ||
418 | |||
419 | const json = await playlist.toActivityPubObject(req.query.page, null) | ||
420 | const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) | ||
421 | const object = audiencify(json, audience) | ||
422 | |||
423 | return activityPubResponse(activityPubContextify(object, 'Playlist', getContextFilter()), res) | ||
424 | } | ||
425 | |||
426 | function videoPlaylistElementController (req: express.Request, res: express.Response) { | ||
427 | const videoPlaylistElement = res.locals.videoPlaylistElementAP | ||
428 | |||
429 | if (redirectIfNotOwned(videoPlaylistElement.url, res)) return | ||
430 | |||
431 | const json = videoPlaylistElement.toActivityPubObject() | ||
432 | return activityPubResponse(activityPubContextify(json, 'Playlist', getContextFilter()), res) | ||
433 | } | ||
434 | |||
435 | function getVideoLocalViewerController (req: express.Request, res: express.Response) { | ||
436 | const localViewer = res.locals.localViewerFull | ||
437 | |||
438 | return activityPubResponse(activityPubContextify(localViewer.toActivityPubObject(), 'WatchAction', getContextFilter()), res) | ||
439 | } | ||
440 | |||
441 | // --------------------------------------------------------------------------- | ||
442 | |||
443 | function actorFollowing (req: express.Request, actor: MActorId) { | ||
444 | const handler = (start: number, count: number) => { | ||
445 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) | ||
446 | } | ||
447 | |||
448 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | ||
449 | } | ||
450 | |||
451 | function actorFollowers (req: express.Request, actor: MActorId) { | ||
452 | const handler = (start: number, count: number) => { | ||
453 | return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) | ||
454 | } | ||
455 | |||
456 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | ||
457 | } | ||
458 | |||
459 | function actorPlaylists (req: express.Request, options: { account: MAccountId } | { channel: MChannelId }) { | ||
460 | const handler = (start: number, count: number) => { | ||
461 | return VideoPlaylistModel.listPublicUrlsOfForAP(options, start, count) | ||
462 | } | ||
463 | |||
464 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | ||
465 | } | ||
466 | |||
467 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideoId, url: string) { | ||
468 | const handler = async (start: number, count: number) => { | ||
469 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | ||
470 | return { | ||
471 | total: result.total, | ||
472 | data: result.data.map(r => r.url) | ||
473 | } | ||
474 | } | ||
475 | return activityPubCollectionPagination(url, handler, req.query.page) | ||
476 | } | ||
477 | |||
478 | function redirectIfNotOwned (url: string, res: express.Response) { | ||
479 | if (url.startsWith(WEBSERVER.URL) === false) { | ||
480 | res.redirect(url) | ||
481 | return true | ||
482 | } | ||
483 | |||
484 | return false | ||
485 | } | ||