e521cabbcfdcf192f3e74693b6fcbf9c9e08a667
[oweals/peertube.git] / server / lib / activitypub / send / send-create.ts
1 import { Transaction } from 'sequelize'
2 import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
3 import { VideoPrivacy } from '../../../../shared/models/videos'
4 import { VideoCommentModel } from '../../../models/video/video-comment'
5 import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
6 import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
7 import { logger } from '../../../helpers/logger'
8 import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model'
9 import {
10   MActorLight,
11   MCommentOwnerVideo,
12   MVideoAccountLight,
13   MVideoAP,
14   MVideoPlaylistFull,
15   MVideoRedundancyFileVideo,
16   MVideoRedundancyStreamingPlaylistVideo
17 } from '../../../typings/models'
18 import { getServerActor } from '@server/models/application/application'
19 import { ContextType } from '@shared/models/activitypub/context'
20
21 async function sendCreateVideo (video: MVideoAP, t: Transaction) {
22   if (!video.hasPrivacyForFederation()) return undefined
23
24   logger.info('Creating job to send video creation of %s.', video.url)
25
26   const byActor = video.VideoChannel.Account.Actor
27   const videoObject = video.toActivityPubObject()
28
29   const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
30   const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience)
31
32   return broadcastToFollowers(createActivity, byActor, [ byActor ], t)
33 }
34
35 async function sendCreateCacheFile (
36   byActor: MActorLight,
37   video: MVideoAccountLight,
38   fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
39 ) {
40   logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
41
42   return sendVideoRelatedCreateActivity({
43     byActor,
44     video,
45     url: fileRedundancy.url,
46     object: fileRedundancy.toActivityPubObject(),
47     contextType: 'CacheFile'
48   })
49 }
50
51 async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) {
52   if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
53
54   logger.info('Creating job to send create video playlist of %s.', playlist.url)
55
56   const byActor = playlist.OwnerAccount.Actor
57   const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
58
59   const object = await playlist.toActivityPubObject(null, t)
60   const createActivity = buildCreateActivity(playlist.url, byActor, object, audience)
61
62   const serverActor = await getServerActor()
63   const toFollowersOf = [ byActor, serverActor ]
64
65   if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor)
66
67   return broadcastToFollowers(createActivity, byActor, toFollowersOf, t)
68 }
69
70 async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) {
71   logger.info('Creating job to send comment %s.', comment.url)
72
73   const isOrigin = comment.Video.isOwned()
74
75   const byActor = comment.Account.Actor
76   const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t)
77   const commentObject = comment.toActivityPubObject(threadParentComments)
78
79   const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
80   // Add the actor that commented too
81   actorsInvolvedInComment.push(byActor)
82
83   const parentsCommentActors = threadParentComments.filter(c => !c.isDeleted())
84                                                    .map(c => c.Account.Actor)
85
86   let audience: ActivityAudience
87   if (isOrigin) {
88     audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment, isOrigin)
89   } else {
90     audience = getAudienceFromFollowersOf(actorsInvolvedInComment.concat(parentsCommentActors))
91   }
92
93   const createActivity = buildCreateActivity(comment.url, byActor, commentObject, audience)
94
95   // This was a reply, send it to the parent actors
96   const actorsException = [ byActor ]
97   await broadcastToActors(createActivity, byActor, parentsCommentActors, t, actorsException)
98
99   // Broadcast to our followers
100   await broadcastToFollowers(createActivity, byActor, [ byActor ], t)
101
102   // Send to actors involved in the comment
103   if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException)
104
105   // Send to origin
106   t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox()))
107 }
108
109 function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
110   if (!audience) audience = getAudience(byActor)
111
112   return audiencify(
113     {
114       type: 'Create' as 'Create',
115       id: url + '/activity',
116       actor: byActor.url,
117       object: audiencify(object, audience)
118     },
119     audience
120   )
121 }
122
123 // ---------------------------------------------------------------------------
124
125 export {
126   sendCreateVideo,
127   buildCreateActivity,
128   sendCreateVideoComment,
129   sendCreateVideoPlaylist,
130   sendCreateCacheFile
131 }
132
133 // ---------------------------------------------------------------------------
134
135 async function sendVideoRelatedCreateActivity (options: {
136   byActor: MActorLight
137   video: MVideoAccountLight
138   url: string
139   object: any
140   transaction?: Transaction
141   contextType?: ContextType
142 }) {
143   const activityBuilder = (audience: ActivityAudience) => {
144     return buildCreateActivity(options.url, options.byActor, options.object, audience)
145   }
146
147   return sendVideoRelatedActivity(activityBuilder, options)
148 }