diff options
Diffstat (limited to 'server/lib/activitypub/send')
-rw-r--r-- | server/lib/activitypub/send/http.ts | 73 | ||||
-rw-r--r-- | server/lib/activitypub/send/index.ts | 10 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-accept.ts | 47 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-announce.ts | 58 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-create.ts | 226 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-delete.ts | 158 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-dislike.ts | 40 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-flag.ts | 42 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-follow.ts | 37 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-like.ts | 40 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-reject.ts | 39 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-undo.ts | 172 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-update.ts | 157 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-view.ts | 62 | ||||
-rw-r--r-- | server/lib/activitypub/send/shared/audience-utils.ts | 74 | ||||
-rw-r--r-- | server/lib/activitypub/send/shared/index.ts | 2 | ||||
-rw-r--r-- | server/lib/activitypub/send/shared/send-utils.ts | 291 |
17 files changed, 0 insertions, 1528 deletions
diff --git a/server/lib/activitypub/send/http.ts b/server/lib/activitypub/send/http.ts deleted file mode 100644 index b461aa55d..000000000 --- a/server/lib/activitypub/send/http.ts +++ /dev/null | |||
@@ -1,73 +0,0 @@ | |||
1 | import { buildDigest, signJsonLDObject } from '@server/helpers/peertube-crypto' | ||
2 | import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants' | ||
3 | import { ActorModel } from '@server/models/actor/actor' | ||
4 | import { getServerActor } from '@server/models/application/application' | ||
5 | import { MActor } from '@server/types/models' | ||
6 | import { ContextType } from '@shared/models/activitypub/context' | ||
7 | import { activityPubContextify } from '../context' | ||
8 | |||
9 | type Payload <T> = { body: T, contextType: ContextType, signatureActorId?: number } | ||
10 | |||
11 | async function computeBody <T> ( | ||
12 | payload: Payload<T> | ||
13 | ): Promise<T | T & { type: 'RsaSignature2017', creator: string, created: string }> { | ||
14 | let body = payload.body | ||
15 | |||
16 | if (payload.signatureActorId) { | ||
17 | const actorSignature = await ActorModel.load(payload.signatureActorId) | ||
18 | if (!actorSignature) throw new Error('Unknown signature actor id.') | ||
19 | |||
20 | body = await signAndContextify(actorSignature, payload.body, payload.contextType) | ||
21 | } | ||
22 | |||
23 | return body | ||
24 | } | ||
25 | |||
26 | async function buildSignedRequestOptions (options: { | ||
27 | signatureActorId?: number | ||
28 | hasPayload: boolean | ||
29 | }) { | ||
30 | let actor: MActor | null | ||
31 | |||
32 | if (options.signatureActorId) { | ||
33 | actor = await ActorModel.load(options.signatureActorId) | ||
34 | if (!actor) throw new Error('Unknown signature actor id.') | ||
35 | } else { | ||
36 | // We need to sign the request, so use the server | ||
37 | actor = await getServerActor() | ||
38 | } | ||
39 | |||
40 | const keyId = actor.url | ||
41 | return { | ||
42 | algorithm: HTTP_SIGNATURE.ALGORITHM, | ||
43 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, | ||
44 | keyId, | ||
45 | key: actor.privateKey, | ||
46 | headers: options.hasPayload | ||
47 | ? HTTP_SIGNATURE.HEADERS_TO_SIGN_WITH_PAYLOAD | ||
48 | : HTTP_SIGNATURE.HEADERS_TO_SIGN_WITHOUT_PAYLOAD | ||
49 | } | ||
50 | } | ||
51 | |||
52 | function buildGlobalHeaders (body: any) { | ||
53 | return { | ||
54 | 'digest': buildDigest(body), | ||
55 | 'content-type': 'application/activity+json', | ||
56 | 'accept': ACTIVITY_PUB.ACCEPT_HEADER | ||
57 | } | ||
58 | } | ||
59 | |||
60 | async function signAndContextify <T> (byActor: MActor, data: T, contextType: ContextType | null) { | ||
61 | const activity = contextType | ||
62 | ? await activityPubContextify(data, contextType) | ||
63 | : data | ||
64 | |||
65 | return signJsonLDObject(byActor, activity) | ||
66 | } | ||
67 | |||
68 | export { | ||
69 | buildGlobalHeaders, | ||
70 | computeBody, | ||
71 | buildSignedRequestOptions, | ||
72 | signAndContextify | ||
73 | } | ||
diff --git a/server/lib/activitypub/send/index.ts b/server/lib/activitypub/send/index.ts deleted file mode 100644 index 852ea2e74..000000000 --- a/server/lib/activitypub/send/index.ts +++ /dev/null | |||
@@ -1,10 +0,0 @@ | |||
1 | export * from './http' | ||
2 | export * from './send-accept' | ||
3 | export * from './send-announce' | ||
4 | export * from './send-create' | ||
5 | export * from './send-delete' | ||
6 | export * from './send-follow' | ||
7 | export * from './send-like' | ||
8 | export * from './send-reject' | ||
9 | export * from './send-undo' | ||
10 | export * from './send-update' | ||
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts deleted file mode 100644 index 4c9bcbb0b..000000000 --- a/server/lib/activitypub/send/send-accept.ts +++ /dev/null | |||
@@ -1,47 +0,0 @@ | |||
1 | import { ActivityAccept, ActivityFollow } from '@shared/models' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { MActor, MActorFollowActors } from '../../../types/models' | ||
4 | import { getLocalActorFollowAcceptActivityPubUrl } from '../url' | ||
5 | import { buildFollowActivity } from './send-follow' | ||
6 | import { unicastTo } from './shared/send-utils' | ||
7 | |||
8 | function sendAccept (actorFollow: MActorFollowActors) { | ||
9 | const follower = actorFollow.ActorFollower | ||
10 | const me = actorFollow.ActorFollowing | ||
11 | |||
12 | if (!follower.serverId) { // This should never happen | ||
13 | logger.warn('Do not sending accept to local follower.') | ||
14 | return | ||
15 | } | ||
16 | |||
17 | logger.info('Creating job to accept follower %s.', follower.url) | ||
18 | |||
19 | const followData = buildFollowActivity(actorFollow.url, follower, me) | ||
20 | |||
21 | const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow) | ||
22 | const data = buildAcceptActivity(url, me, followData) | ||
23 | |||
24 | return unicastTo({ | ||
25 | data, | ||
26 | byActor: me, | ||
27 | toActorUrl: follower.inboxUrl, | ||
28 | contextType: 'Accept' | ||
29 | }) | ||
30 | } | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | export { | ||
35 | sendAccept | ||
36 | } | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept { | ||
41 | return { | ||
42 | type: 'Accept', | ||
43 | id: url, | ||
44 | actor: byActor.url, | ||
45 | object: followActivityData | ||
46 | } | ||
47 | } | ||
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts deleted file mode 100644 index 6c078b047..000000000 --- a/server/lib/activitypub/send/send-announce.ts +++ /dev/null | |||
@@ -1,58 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAnnounce, ActivityAudience } from '@shared/models' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MActorLight, MVideo } from '../../../types/models' | ||
5 | import { MVideoShare } from '../../../types/models/video' | ||
6 | import { audiencify, getAudience } from '../audience' | ||
7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf } from './shared' | ||
8 | import { broadcastToFollowers } from './shared/send-utils' | ||
9 | |||
10 | async function buildAnnounceWithVideoAudience ( | ||
11 | byActor: MActorLight, | ||
12 | videoShare: MVideoShare, | ||
13 | video: MVideo, | ||
14 | t: Transaction | ||
15 | ) { | ||
16 | const announcedObject = video.url | ||
17 | |||
18 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) | ||
19 | const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo) | ||
20 | |||
21 | const activity = buildAnnounceActivity(videoShare.url, byActor, announcedObject, audience) | ||
22 | |||
23 | return { activity, actorsInvolvedInVideo } | ||
24 | } | ||
25 | |||
26 | async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) { | ||
27 | const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction) | ||
28 | |||
29 | logger.info('Creating job to send announce %s.', videoShare.url) | ||
30 | |||
31 | return broadcastToFollowers({ | ||
32 | data: activity, | ||
33 | byActor, | ||
34 | toFollowersOf: actorsInvolvedInVideo, | ||
35 | transaction, | ||
36 | actorsException: [ byActor ], | ||
37 | contextType: 'Announce' | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { | ||
42 | if (!audience) audience = getAudience(byActor) | ||
43 | |||
44 | return audiencify({ | ||
45 | type: 'Announce' as 'Announce', | ||
46 | id: url, | ||
47 | actor: byActor.url, | ||
48 | object | ||
49 | }, audience) | ||
50 | } | ||
51 | |||
52 | // --------------------------------------------------------------------------- | ||
53 | |||
54 | export { | ||
55 | sendVideoAnnounce, | ||
56 | buildAnnounceActivity, | ||
57 | buildAnnounceWithVideoAudience | ||
58 | } | ||
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts deleted file mode 100644 index 2cd4db14d..000000000 --- a/server/lib/activitypub/send/send-create.ts +++ /dev/null | |||
@@ -1,226 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { getServerActor } from '@server/models/application/application' | ||
3 | import { | ||
4 | ActivityAudience, | ||
5 | ActivityCreate, | ||
6 | ActivityCreateObject, | ||
7 | ContextType, | ||
8 | VideoCommentObject, | ||
9 | VideoPlaylistPrivacy, | ||
10 | VideoPrivacy | ||
11 | } from '@shared/models' | ||
12 | import { logger, loggerTagsFactory } from '../../../helpers/logger' | ||
13 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
14 | import { | ||
15 | MActorLight, | ||
16 | MCommentOwnerVideo, | ||
17 | MLocalVideoViewerWithWatchSections, | ||
18 | MVideoAccountLight, | ||
19 | MVideoAP, | ||
20 | MVideoPlaylistFull, | ||
21 | MVideoRedundancyFileVideo, | ||
22 | MVideoRedundancyStreamingPlaylistVideo | ||
23 | } from '../../../types/models' | ||
24 | import { audiencify, getAudience } from '../audience' | ||
25 | import { | ||
26 | broadcastToActors, | ||
27 | broadcastToFollowers, | ||
28 | getActorsInvolvedInVideo, | ||
29 | getAudienceFromFollowersOf, | ||
30 | getVideoCommentAudience, | ||
31 | sendVideoActivityToOrigin, | ||
32 | sendVideoRelatedActivity, | ||
33 | unicastTo | ||
34 | } from './shared' | ||
35 | |||
36 | const lTags = loggerTagsFactory('ap', 'create') | ||
37 | |||
38 | async function sendCreateVideo (video: MVideoAP, transaction: Transaction) { | ||
39 | if (!video.hasPrivacyForFederation()) return undefined | ||
40 | |||
41 | logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid)) | ||
42 | |||
43 | const byActor = video.VideoChannel.Account.Actor | ||
44 | const videoObject = await video.toActivityPubObject() | ||
45 | |||
46 | const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) | ||
47 | const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience) | ||
48 | |||
49 | return broadcastToFollowers({ | ||
50 | data: createActivity, | ||
51 | byActor, | ||
52 | toFollowersOf: [ byActor ], | ||
53 | transaction, | ||
54 | contextType: 'Video' | ||
55 | }) | ||
56 | } | ||
57 | |||
58 | async function sendCreateCacheFile ( | ||
59 | byActor: MActorLight, | ||
60 | video: MVideoAccountLight, | ||
61 | fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo | ||
62 | ) { | ||
63 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url, lTags(video.uuid)) | ||
64 | |||
65 | return sendVideoRelatedCreateActivity({ | ||
66 | byActor, | ||
67 | video, | ||
68 | url: fileRedundancy.url, | ||
69 | object: fileRedundancy.toActivityPubObject(), | ||
70 | contextType: 'CacheFile' | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) { | ||
75 | logger.info('Creating job to send create watch action %s.', stats.url, lTags(stats.uuid)) | ||
76 | |||
77 | const byActor = await getServerActor() | ||
78 | |||
79 | const activityBuilder = (audience: ActivityAudience) => { | ||
80 | return buildCreateActivity(stats.url, byActor, stats.toActivityPubObject(), audience) | ||
81 | } | ||
82 | |||
83 | return sendVideoActivityToOrigin(activityBuilder, { byActor, video: stats.Video, transaction, contextType: 'WatchAction' }) | ||
84 | } | ||
85 | |||
86 | async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) { | ||
87 | if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | ||
88 | |||
89 | logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid)) | ||
90 | |||
91 | const byActor = playlist.OwnerAccount.Actor | ||
92 | const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) | ||
93 | |||
94 | const object = await playlist.toActivityPubObject(null, transaction) | ||
95 | const createActivity = buildCreateActivity(playlist.url, byActor, object, audience) | ||
96 | |||
97 | const serverActor = await getServerActor() | ||
98 | const toFollowersOf = [ byActor, serverActor ] | ||
99 | |||
100 | if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor) | ||
101 | |||
102 | return broadcastToFollowers({ | ||
103 | data: createActivity, | ||
104 | byActor, | ||
105 | toFollowersOf, | ||
106 | transaction, | ||
107 | contextType: 'Playlist' | ||
108 | }) | ||
109 | } | ||
110 | |||
111 | async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) { | ||
112 | logger.info('Creating job to send comment %s.', comment.url) | ||
113 | |||
114 | const isOrigin = comment.Video.isOwned() | ||
115 | |||
116 | const byActor = comment.Account.Actor | ||
117 | const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction) | ||
118 | const commentObject = comment.toActivityPubObject(threadParentComments) as VideoCommentObject | ||
119 | |||
120 | const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction) | ||
121 | // Add the actor that commented too | ||
122 | actorsInvolvedInComment.push(byActor) | ||
123 | |||
124 | const parentsCommentActors = threadParentComments.filter(c => !c.isDeleted()) | ||
125 | .map(c => c.Account.Actor) | ||
126 | |||
127 | let audience: ActivityAudience | ||
128 | if (isOrigin) { | ||
129 | audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment, isOrigin) | ||
130 | } else { | ||
131 | audience = getAudienceFromFollowersOf(actorsInvolvedInComment.concat(parentsCommentActors)) | ||
132 | } | ||
133 | |||
134 | const createActivity = buildCreateActivity(comment.url, byActor, commentObject, audience) | ||
135 | |||
136 | // This was a reply, send it to the parent actors | ||
137 | const actorsException = [ byActor ] | ||
138 | await broadcastToActors({ | ||
139 | data: createActivity, | ||
140 | byActor, | ||
141 | toActors: parentsCommentActors, | ||
142 | transaction, | ||
143 | actorsException, | ||
144 | contextType: 'Comment' | ||
145 | }) | ||
146 | |||
147 | // Broadcast to our followers | ||
148 | await broadcastToFollowers({ | ||
149 | data: createActivity, | ||
150 | byActor, | ||
151 | toFollowersOf: [ byActor ], | ||
152 | transaction, | ||
153 | contextType: 'Comment' | ||
154 | }) | ||
155 | |||
156 | // Send to actors involved in the comment | ||
157 | if (isOrigin) { | ||
158 | return broadcastToFollowers({ | ||
159 | data: createActivity, | ||
160 | byActor, | ||
161 | toFollowersOf: actorsInvolvedInComment, | ||
162 | transaction, | ||
163 | actorsException, | ||
164 | contextType: 'Comment' | ||
165 | }) | ||
166 | } | ||
167 | |||
168 | // Send to origin | ||
169 | return transaction.afterCommit(() => { | ||
170 | return unicastTo({ | ||
171 | data: createActivity, | ||
172 | byActor, | ||
173 | toActorUrl: comment.Video.VideoChannel.Account.Actor.getSharedInbox(), | ||
174 | contextType: 'Comment' | ||
175 | }) | ||
176 | }) | ||
177 | } | ||
178 | |||
179 | function buildCreateActivity <T extends ActivityCreateObject> ( | ||
180 | url: string, | ||
181 | byActor: MActorLight, | ||
182 | object: T, | ||
183 | audience?: ActivityAudience | ||
184 | ): ActivityCreate<T> { | ||
185 | if (!audience) audience = getAudience(byActor) | ||
186 | |||
187 | return audiencify( | ||
188 | { | ||
189 | type: 'Create' as 'Create', | ||
190 | id: url + '/activity', | ||
191 | actor: byActor.url, | ||
192 | object: typeof object === 'string' | ||
193 | ? object | ||
194 | : audiencify(object, audience) | ||
195 | }, | ||
196 | audience | ||
197 | ) | ||
198 | } | ||
199 | |||
200 | // --------------------------------------------------------------------------- | ||
201 | |||
202 | export { | ||
203 | sendCreateVideo, | ||
204 | buildCreateActivity, | ||
205 | sendCreateVideoComment, | ||
206 | sendCreateVideoPlaylist, | ||
207 | sendCreateCacheFile, | ||
208 | sendCreateWatchAction | ||
209 | } | ||
210 | |||
211 | // --------------------------------------------------------------------------- | ||
212 | |||
213 | async function sendVideoRelatedCreateActivity (options: { | ||
214 | byActor: MActorLight | ||
215 | video: MVideoAccountLight | ||
216 | url: string | ||
217 | object: any | ||
218 | contextType: ContextType | ||
219 | transaction?: Transaction | ||
220 | }) { | ||
221 | const activityBuilder = (audience: ActivityAudience) => { | ||
222 | return buildCreateActivity(options.url, options.byActor, options.object, audience) | ||
223 | } | ||
224 | |||
225 | return sendVideoRelatedActivity(activityBuilder, options) | ||
226 | } | ||
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts deleted file mode 100644 index 0d85d9001..000000000 --- a/server/lib/activitypub/send/send-delete.ts +++ /dev/null | |||
@@ -1,158 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { getServerActor } from '@server/models/application/application' | ||
3 | import { ActivityAudience, ActivityDelete } from '@shared/models' | ||
4 | import { logger } from '../../../helpers/logger' | ||
5 | import { ActorModel } from '../../../models/actor/actor' | ||
6 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
7 | import { VideoShareModel } from '../../../models/video/video-share' | ||
8 | import { MActorUrl } from '../../../types/models' | ||
9 | import { MCommentOwnerVideo, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../types/models/video' | ||
10 | import { audiencify } from '../audience' | ||
11 | import { getDeleteActivityPubUrl } from '../url' | ||
12 | import { getActorsInvolvedInVideo, getVideoCommentAudience } from './shared' | ||
13 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './shared/send-utils' | ||
14 | |||
15 | async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) { | ||
16 | logger.info('Creating job to broadcast delete of video %s.', video.url) | ||
17 | |||
18 | const byActor = video.VideoChannel.Account.Actor | ||
19 | |||
20 | const activityBuilder = (audience: ActivityAudience) => { | ||
21 | const url = getDeleteActivityPubUrl(video.url) | ||
22 | |||
23 | return buildDeleteActivity(url, video.url, byActor, audience) | ||
24 | } | ||
25 | |||
26 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'Delete', transaction }) | ||
27 | } | ||
28 | |||
29 | async function sendDeleteActor (byActor: ActorModel, transaction: Transaction) { | ||
30 | logger.info('Creating job to broadcast delete of actor %s.', byActor.url) | ||
31 | |||
32 | const url = getDeleteActivityPubUrl(byActor.url) | ||
33 | const activity = buildDeleteActivity(url, byActor.url, byActor) | ||
34 | |||
35 | const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction) | ||
36 | |||
37 | // In case the actor did not have any videos | ||
38 | const serverActor = await getServerActor() | ||
39 | actorsInvolved.push(serverActor) | ||
40 | |||
41 | actorsInvolved.push(byActor) | ||
42 | |||
43 | return broadcastToFollowers({ | ||
44 | data: activity, | ||
45 | byActor, | ||
46 | toFollowersOf: actorsInvolved, | ||
47 | contextType: 'Delete', | ||
48 | transaction | ||
49 | }) | ||
50 | } | ||
51 | |||
52 | async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, transaction: Transaction) { | ||
53 | logger.info('Creating job to send delete of comment %s.', videoComment.url) | ||
54 | |||
55 | const isVideoOrigin = videoComment.Video.isOwned() | ||
56 | |||
57 | const url = getDeleteActivityPubUrl(videoComment.url) | ||
58 | const byActor = videoComment.isOwned() | ||
59 | ? videoComment.Account.Actor | ||
60 | : videoComment.Video.VideoChannel.Account.Actor | ||
61 | |||
62 | const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, transaction) | ||
63 | const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted()) | ||
64 | |||
65 | const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, transaction) | ||
66 | actorsInvolvedInComment.push(byActor) // Add the actor that commented the video | ||
67 | |||
68 | const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin) | ||
69 | const activity = buildDeleteActivity(url, videoComment.url, byActor, audience) | ||
70 | |||
71 | // This was a reply, send it to the parent actors | ||
72 | const actorsException = [ byActor ] | ||
73 | await broadcastToActors({ | ||
74 | data: activity, | ||
75 | byActor, | ||
76 | toActors: threadParentCommentsFiltered.map(c => c.Account.Actor), | ||
77 | transaction, | ||
78 | contextType: 'Delete', | ||
79 | actorsException | ||
80 | }) | ||
81 | |||
82 | // Broadcast to our followers | ||
83 | await broadcastToFollowers({ | ||
84 | data: activity, | ||
85 | byActor, | ||
86 | toFollowersOf: [ byActor ], | ||
87 | contextType: 'Delete', | ||
88 | transaction | ||
89 | }) | ||
90 | |||
91 | // Send to actors involved in the comment | ||
92 | if (isVideoOrigin) { | ||
93 | return broadcastToFollowers({ | ||
94 | data: activity, | ||
95 | byActor, | ||
96 | toFollowersOf: actorsInvolvedInComment, | ||
97 | transaction, | ||
98 | contextType: 'Delete', | ||
99 | actorsException | ||
100 | }) | ||
101 | } | ||
102 | |||
103 | // Send to origin | ||
104 | return transaction.afterCommit(() => { | ||
105 | return unicastTo({ | ||
106 | data: activity, | ||
107 | byActor, | ||
108 | toActorUrl: videoComment.Video.VideoChannel.Account.Actor.getSharedInbox(), | ||
109 | contextType: 'Delete' | ||
110 | }) | ||
111 | }) | ||
112 | } | ||
113 | |||
114 | async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, transaction: Transaction) { | ||
115 | logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) | ||
116 | |||
117 | const byActor = videoPlaylist.OwnerAccount.Actor | ||
118 | |||
119 | const url = getDeleteActivityPubUrl(videoPlaylist.url) | ||
120 | const activity = buildDeleteActivity(url, videoPlaylist.url, byActor) | ||
121 | |||
122 | const serverActor = await getServerActor() | ||
123 | const toFollowersOf = [ byActor, serverActor ] | ||
124 | |||
125 | if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) | ||
126 | |||
127 | return broadcastToFollowers({ | ||
128 | data: activity, | ||
129 | byActor, | ||
130 | toFollowersOf, | ||
131 | contextType: 'Delete', | ||
132 | transaction | ||
133 | }) | ||
134 | } | ||
135 | |||
136 | // --------------------------------------------------------------------------- | ||
137 | |||
138 | export { | ||
139 | sendDeleteVideo, | ||
140 | sendDeleteActor, | ||
141 | sendDeleteVideoComment, | ||
142 | sendDeleteVideoPlaylist | ||
143 | } | ||
144 | |||
145 | // --------------------------------------------------------------------------- | ||
146 | |||
147 | function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete { | ||
148 | const activity = { | ||
149 | type: 'Delete' as 'Delete', | ||
150 | id: url, | ||
151 | actor: byActor.url, | ||
152 | object | ||
153 | } | ||
154 | |||
155 | if (audience) return audiencify(activity, audience) | ||
156 | |||
157 | return activity | ||
158 | } | ||
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts deleted file mode 100644 index 959e74823..000000000 --- a/server/lib/activitypub/send/send-dislike.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAudience, ActivityDislike } from '@shared/models' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../types/models' | ||
5 | import { audiencify, getAudience } from '../audience' | ||
6 | import { getVideoDislikeActivityPubUrlByLocalActor } from '../url' | ||
7 | import { sendVideoActivityToOrigin } from './shared/send-utils' | ||
8 | |||
9 | function sendDislike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) { | ||
10 | logger.info('Creating job to dislike %s.', video.url) | ||
11 | |||
12 | const activityBuilder = (audience: ActivityAudience) => { | ||
13 | const url = getVideoDislikeActivityPubUrlByLocalActor(byActor, video) | ||
14 | |||
15 | return buildDislikeActivity(url, byActor, video, audience) | ||
16 | } | ||
17 | |||
18 | return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' }) | ||
19 | } | ||
20 | |||
21 | function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike { | ||
22 | if (!audience) audience = getAudience(byActor) | ||
23 | |||
24 | return audiencify( | ||
25 | { | ||
26 | id: url, | ||
27 | type: 'Dislike' as 'Dislike', | ||
28 | actor: byActor.url, | ||
29 | object: video.url | ||
30 | }, | ||
31 | audience | ||
32 | ) | ||
33 | } | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | export { | ||
38 | sendDislike, | ||
39 | buildDislikeActivity | ||
40 | } | ||
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts deleted file mode 100644 index 138eb5adc..000000000 --- a/server/lib/activitypub/send/send-flag.ts +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAudience, ActivityFlag } from '@shared/models' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MAbuseAP, MAccountLight, MActor } from '../../../types/models' | ||
5 | import { audiencify, getAudience } from '../audience' | ||
6 | import { getLocalAbuseActivityPubUrl } from '../url' | ||
7 | import { unicastTo } from './shared/send-utils' | ||
8 | |||
9 | function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLight, t: Transaction) { | ||
10 | if (!flaggedAccount.Actor.serverId) return // Local user | ||
11 | |||
12 | const url = getLocalAbuseActivityPubUrl(abuse) | ||
13 | |||
14 | logger.info('Creating job to send abuse %s.', url) | ||
15 | |||
16 | // Custom audience, we only send the abuse to the origin instance | ||
17 | const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } | ||
18 | const flagActivity = buildFlagActivity(url, byActor, abuse, audience) | ||
19 | |||
20 | return t.afterCommit(() => { | ||
21 | return unicastTo({ | ||
22 | data: flagActivity, | ||
23 | byActor, | ||
24 | toActorUrl: flaggedAccount.Actor.getSharedInbox(), | ||
25 | contextType: 'Flag' | ||
26 | }) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { | ||
31 | if (!audience) audience = getAudience(byActor) | ||
32 | |||
33 | const activity = { id: url, actor: byActor.url, ...abuse.toActivityPubObject() } | ||
34 | |||
35 | return audiencify(activity, audience) | ||
36 | } | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | export { | ||
41 | sendAbuse | ||
42 | } | ||
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts deleted file mode 100644 index 57501dadb..000000000 --- a/server/lib/activitypub/send/send-follow.ts +++ /dev/null | |||
@@ -1,37 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityFollow } from '@shared/models' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MActor, MActorFollowActors } from '../../../types/models' | ||
5 | import { unicastTo } from './shared/send-utils' | ||
6 | |||
7 | function sendFollow (actorFollow: MActorFollowActors, t: Transaction) { | ||
8 | const me = actorFollow.ActorFollower | ||
9 | const following = actorFollow.ActorFollowing | ||
10 | |||
11 | // Same server as ours | ||
12 | if (!following.serverId) return | ||
13 | |||
14 | logger.info('Creating job to send follow request to %s.', following.url) | ||
15 | |||
16 | const data = buildFollowActivity(actorFollow.url, me, following) | ||
17 | |||
18 | return t.afterCommit(() => { | ||
19 | return unicastTo({ data, byActor: me, toActorUrl: following.inboxUrl, contextType: 'Follow' }) | ||
20 | }) | ||
21 | } | ||
22 | |||
23 | function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow { | ||
24 | return { | ||
25 | type: 'Follow', | ||
26 | id: url, | ||
27 | actor: byActor.url, | ||
28 | object: targetActor.url | ||
29 | } | ||
30 | } | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | export { | ||
35 | sendFollow, | ||
36 | buildFollowActivity | ||
37 | } | ||
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts deleted file mode 100644 index 46c9fdec9..000000000 --- a/server/lib/activitypub/send/send-like.ts +++ /dev/null | |||
@@ -1,40 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAudience, ActivityLike } from '@shared/models' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../types/models' | ||
5 | import { audiencify, getAudience } from '../audience' | ||
6 | import { getVideoLikeActivityPubUrlByLocalActor } from '../url' | ||
7 | import { sendVideoActivityToOrigin } from './shared/send-utils' | ||
8 | |||
9 | function sendLike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) { | ||
10 | logger.info('Creating job to like %s.', video.url) | ||
11 | |||
12 | const activityBuilder = (audience: ActivityAudience) => { | ||
13 | const url = getVideoLikeActivityPubUrlByLocalActor(byActor, video) | ||
14 | |||
15 | return buildLikeActivity(url, byActor, video, audience) | ||
16 | } | ||
17 | |||
18 | return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' }) | ||
19 | } | ||
20 | |||
21 | function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike { | ||
22 | if (!audience) audience = getAudience(byActor) | ||
23 | |||
24 | return audiencify( | ||
25 | { | ||
26 | id: url, | ||
27 | type: 'Like' as 'Like', | ||
28 | actor: byActor.url, | ||
29 | object: video.url | ||
30 | }, | ||
31 | audience | ||
32 | ) | ||
33 | } | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | export { | ||
38 | sendLike, | ||
39 | buildLikeActivity | ||
40 | } | ||
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts deleted file mode 100644 index a5f8c2ecf..000000000 --- a/server/lib/activitypub/send/send-reject.ts +++ /dev/null | |||
@@ -1,39 +0,0 @@ | |||
1 | import { ActivityFollow, ActivityReject } from '@shared/models' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { MActor } from '../../../types/models' | ||
4 | import { getLocalActorFollowRejectActivityPubUrl } from '../url' | ||
5 | import { buildFollowActivity } from './send-follow' | ||
6 | import { unicastTo } from './shared/send-utils' | ||
7 | |||
8 | function sendReject (followUrl: string, follower: MActor, following: MActor) { | ||
9 | if (!follower.serverId) { // This should never happen | ||
10 | logger.warn('Do not sending reject to local follower.') | ||
11 | return | ||
12 | } | ||
13 | |||
14 | logger.info('Creating job to reject follower %s.', follower.url) | ||
15 | |||
16 | const followData = buildFollowActivity(followUrl, follower, following) | ||
17 | |||
18 | const url = getLocalActorFollowRejectActivityPubUrl() | ||
19 | const data = buildRejectActivity(url, following, followData) | ||
20 | |||
21 | return unicastTo({ data, byActor: following, toActorUrl: follower.inboxUrl, contextType: 'Reject' }) | ||
22 | } | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | export { | ||
27 | sendReject | ||
28 | } | ||
29 | |||
30 | // --------------------------------------------------------------------------- | ||
31 | |||
32 | function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject { | ||
33 | return { | ||
34 | type: 'Reject', | ||
35 | id: url, | ||
36 | actor: byActor.url, | ||
37 | object: followActivityData | ||
38 | } | ||
39 | } | ||
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts deleted file mode 100644 index b0b48c9c4..000000000 --- a/server/lib/activitypub/send/send-undo.ts +++ /dev/null | |||
@@ -1,172 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAudience, ActivityDislike, ActivityLike, ActivityUndo, ActivityUndoObject, ContextType } from '@shared/models' | ||
3 | import { logger } from '../../../helpers/logger' | ||
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { | ||
6 | MActor, | ||
7 | MActorAudience, | ||
8 | MActorFollowActors, | ||
9 | MActorLight, | ||
10 | MVideo, | ||
11 | MVideoAccountLight, | ||
12 | MVideoRedundancyVideo, | ||
13 | MVideoShare | ||
14 | } from '../../../types/models' | ||
15 | import { audiencify, getAudience } from '../audience' | ||
16 | import { getUndoActivityPubUrl, getVideoDislikeActivityPubUrlByLocalActor, getVideoLikeActivityPubUrlByLocalActor } from '../url' | ||
17 | import { buildAnnounceWithVideoAudience } from './send-announce' | ||
18 | import { buildCreateActivity } from './send-create' | ||
19 | import { buildDislikeActivity } from './send-dislike' | ||
20 | import { buildFollowActivity } from './send-follow' | ||
21 | import { buildLikeActivity } from './send-like' | ||
22 | import { broadcastToFollowers, sendVideoActivityToOrigin, sendVideoRelatedActivity, unicastTo } from './shared/send-utils' | ||
23 | |||
24 | function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { | ||
25 | const me = actorFollow.ActorFollower | ||
26 | const following = actorFollow.ActorFollowing | ||
27 | |||
28 | // Same server as ours | ||
29 | if (!following.serverId) return | ||
30 | |||
31 | logger.info('Creating job to send an unfollow request to %s.', following.url) | ||
32 | |||
33 | const undoUrl = getUndoActivityPubUrl(actorFollow.url) | ||
34 | |||
35 | const followActivity = buildFollowActivity(actorFollow.url, me, following) | ||
36 | const undoActivity = undoActivityData(undoUrl, me, followActivity) | ||
37 | |||
38 | t.afterCommit(() => { | ||
39 | return unicastTo({ | ||
40 | data: undoActivity, | ||
41 | byActor: me, | ||
42 | toActorUrl: following.inboxUrl, | ||
43 | contextType: 'Follow' | ||
44 | }) | ||
45 | }) | ||
46 | } | ||
47 | |||
48 | // --------------------------------------------------------------------------- | ||
49 | |||
50 | async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) { | ||
51 | logger.info('Creating job to undo announce %s.', videoShare.url) | ||
52 | |||
53 | const undoUrl = getUndoActivityPubUrl(videoShare.url) | ||
54 | |||
55 | const { activity: announce, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction) | ||
56 | const undoActivity = undoActivityData(undoUrl, byActor, announce) | ||
57 | |||
58 | return broadcastToFollowers({ | ||
59 | data: undoActivity, | ||
60 | byActor, | ||
61 | toFollowersOf: actorsInvolvedInVideo, | ||
62 | transaction, | ||
63 | actorsException: [ byActor ], | ||
64 | contextType: 'Announce' | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, transaction: Transaction) { | ||
69 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) | ||
70 | |||
71 | const associatedVideo = redundancyModel.getVideo() | ||
72 | if (!associatedVideo) { | ||
73 | logger.warn('Cannot send undo activity for redundancy %s: no video files associated.', redundancyModel.url) | ||
74 | return | ||
75 | } | ||
76 | |||
77 | const video = await VideoModel.loadFull(associatedVideo.id) | ||
78 | const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject()) | ||
79 | |||
80 | return sendUndoVideoRelatedActivity({ | ||
81 | byActor, | ||
82 | video, | ||
83 | url: redundancyModel.url, | ||
84 | activity: createActivity, | ||
85 | contextType: 'CacheFile', | ||
86 | transaction | ||
87 | }) | ||
88 | } | ||
89 | |||
90 | // --------------------------------------------------------------------------- | ||
91 | |||
92 | async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { | ||
93 | logger.info('Creating job to undo a like of video %s.', video.url) | ||
94 | |||
95 | const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video) | ||
96 | const likeActivity = buildLikeActivity(likeUrl, byActor, video) | ||
97 | |||
98 | return sendUndoVideoRateToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) | ||
99 | } | ||
100 | |||
101 | async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { | ||
102 | logger.info('Creating job to undo a dislike of video %s.', video.url) | ||
103 | |||
104 | const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video) | ||
105 | const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video) | ||
106 | |||
107 | return sendUndoVideoRateToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) | ||
108 | } | ||
109 | |||
110 | // --------------------------------------------------------------------------- | ||
111 | |||
112 | export { | ||
113 | sendUndoFollow, | ||
114 | sendUndoLike, | ||
115 | sendUndoDislike, | ||
116 | sendUndoAnnounce, | ||
117 | sendUndoCacheFile | ||
118 | } | ||
119 | |||
120 | // --------------------------------------------------------------------------- | ||
121 | |||
122 | function undoActivityData <T extends ActivityUndoObject> ( | ||
123 | url: string, | ||
124 | byActor: MActorAudience, | ||
125 | object: T, | ||
126 | audience?: ActivityAudience | ||
127 | ): ActivityUndo<T> { | ||
128 | if (!audience) audience = getAudience(byActor) | ||
129 | |||
130 | return audiencify( | ||
131 | { | ||
132 | type: 'Undo' as 'Undo', | ||
133 | id: url, | ||
134 | actor: byActor.url, | ||
135 | object | ||
136 | }, | ||
137 | audience | ||
138 | ) | ||
139 | } | ||
140 | |||
141 | async function sendUndoVideoRelatedActivity (options: { | ||
142 | byActor: MActor | ||
143 | video: MVideoAccountLight | ||
144 | url: string | ||
145 | activity: ActivityUndoObject | ||
146 | contextType: ContextType | ||
147 | transaction: Transaction | ||
148 | }) { | ||
149 | const activityBuilder = (audience: ActivityAudience) => { | ||
150 | const undoUrl = getUndoActivityPubUrl(options.url) | ||
151 | |||
152 | return undoActivityData(undoUrl, options.byActor, options.activity, audience) | ||
153 | } | ||
154 | |||
155 | return sendVideoRelatedActivity(activityBuilder, options) | ||
156 | } | ||
157 | |||
158 | async function sendUndoVideoRateToOriginActivity (options: { | ||
159 | byActor: MActor | ||
160 | video: MVideoAccountLight | ||
161 | url: string | ||
162 | activity: ActivityLike | ActivityDislike | ||
163 | transaction: Transaction | ||
164 | }) { | ||
165 | const activityBuilder = (audience: ActivityAudience) => { | ||
166 | const undoUrl = getUndoActivityPubUrl(options.url) | ||
167 | |||
168 | return undoActivityData(undoUrl, options.byActor, options.activity, audience) | ||
169 | } | ||
170 | |||
171 | return sendVideoActivityToOrigin(activityBuilder, { ...options, contextType: 'Rate' }) | ||
172 | } | ||
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts deleted file mode 100644 index f3fb741c6..000000000 --- a/server/lib/activitypub/send/send-update.ts +++ /dev/null | |||
@@ -1,157 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { getServerActor } from '@server/models/application/application' | ||
3 | import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' | ||
4 | import { logger } from '../../../helpers/logger' | ||
5 | import { AccountModel } from '../../../models/account/account' | ||
6 | import { VideoModel } from '../../../models/video/video' | ||
7 | import { VideoShareModel } from '../../../models/video/video-share' | ||
8 | import { | ||
9 | MAccountDefault, | ||
10 | MActor, | ||
11 | MActorLight, | ||
12 | MChannelDefault, | ||
13 | MVideoAPLight, | ||
14 | MVideoPlaylistFull, | ||
15 | MVideoRedundancyVideo | ||
16 | } from '../../../types/models' | ||
17 | import { audiencify, getAudience } from '../audience' | ||
18 | import { getUpdateActivityPubUrl } from '../url' | ||
19 | import { getActorsInvolvedInVideo } from './shared' | ||
20 | import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils' | ||
21 | |||
22 | async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) { | ||
23 | if (!videoArg.hasPrivacyForFederation()) return undefined | ||
24 | |||
25 | const video = await videoArg.lightAPToFullAP(transaction) | ||
26 | |||
27 | logger.info('Creating job to update video %s.', video.url) | ||
28 | |||
29 | const byActor = overriddenByActor || video.VideoChannel.Account.Actor | ||
30 | |||
31 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) | ||
32 | |||
33 | const videoObject = await video.toActivityPubObject() | ||
34 | const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) | ||
35 | |||
36 | const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience) | ||
37 | |||
38 | const actorsInvolved = await getActorsInvolvedInVideo(video, transaction) | ||
39 | if (overriddenByActor) actorsInvolved.push(overriddenByActor) | ||
40 | |||
41 | return broadcastToFollowers({ | ||
42 | data: updateActivity, | ||
43 | byActor, | ||
44 | toFollowersOf: actorsInvolved, | ||
45 | contextType: 'Video', | ||
46 | transaction | ||
47 | }) | ||
48 | } | ||
49 | |||
50 | async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) { | ||
51 | const byActor = accountOrChannel.Actor | ||
52 | |||
53 | logger.info('Creating job to update actor %s.', byActor.url) | ||
54 | |||
55 | const url = getUpdateActivityPubUrl(byActor.url, byActor.updatedAt.toISOString()) | ||
56 | const accountOrChannelObject = await (accountOrChannel as any).toActivityPubObject() // FIXME: typescript bug? | ||
57 | const audience = getAudience(byActor) | ||
58 | const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) | ||
59 | |||
60 | let actorsInvolved: MActor[] | ||
61 | if (accountOrChannel instanceof AccountModel) { | ||
62 | // Actors that shared my videos are involved too | ||
63 | actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction) | ||
64 | } else { | ||
65 | // Actors that shared videos of my channel are involved too | ||
66 | actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, transaction) | ||
67 | } | ||
68 | |||
69 | actorsInvolved.push(byActor) | ||
70 | |||
71 | return broadcastToFollowers({ | ||
72 | data: updateActivity, | ||
73 | byActor, | ||
74 | toFollowersOf: actorsInvolved, | ||
75 | transaction, | ||
76 | contextType: 'Actor' | ||
77 | }) | ||
78 | } | ||
79 | |||
80 | async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { | ||
81 | logger.info('Creating job to update cache file %s.', redundancyModel.url) | ||
82 | |||
83 | const associatedVideo = redundancyModel.getVideo() | ||
84 | if (!associatedVideo) { | ||
85 | logger.warn('Cannot send update activity for redundancy %s: no video files associated.', redundancyModel.url) | ||
86 | return | ||
87 | } | ||
88 | |||
89 | const video = await VideoModel.loadFull(associatedVideo.id) | ||
90 | |||
91 | const activityBuilder = (audience: ActivityAudience) => { | ||
92 | const redundancyObject = redundancyModel.toActivityPubObject() | ||
93 | const url = getUpdateActivityPubUrl(redundancyModel.url, redundancyModel.updatedAt.toISOString()) | ||
94 | |||
95 | return buildUpdateActivity(url, byActor, redundancyObject, audience) | ||
96 | } | ||
97 | |||
98 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) | ||
99 | } | ||
100 | |||
101 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) { | ||
102 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | ||
103 | |||
104 | const byActor = videoPlaylist.OwnerAccount.Actor | ||
105 | |||
106 | logger.info('Creating job to update video playlist %s.', videoPlaylist.url) | ||
107 | |||
108 | const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString()) | ||
109 | |||
110 | const object = await videoPlaylist.toActivityPubObject(null, transaction) | ||
111 | const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC) | ||
112 | |||
113 | const updateActivity = buildUpdateActivity(url, byActor, object, audience) | ||
114 | |||
115 | const serverActor = await getServerActor() | ||
116 | const toFollowersOf = [ byActor, serverActor ] | ||
117 | |||
118 | if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) | ||
119 | |||
120 | return broadcastToFollowers({ | ||
121 | data: updateActivity, | ||
122 | byActor, | ||
123 | toFollowersOf, | ||
124 | transaction, | ||
125 | contextType: 'Playlist' | ||
126 | }) | ||
127 | } | ||
128 | |||
129 | // --------------------------------------------------------------------------- | ||
130 | |||
131 | export { | ||
132 | sendUpdateActor, | ||
133 | sendUpdateVideo, | ||
134 | sendUpdateCacheFile, | ||
135 | sendUpdateVideoPlaylist | ||
136 | } | ||
137 | |||
138 | // --------------------------------------------------------------------------- | ||
139 | |||
140 | function buildUpdateActivity ( | ||
141 | url: string, | ||
142 | byActor: MActorLight, | ||
143 | object: ActivityUpdateObject, | ||
144 | audience?: ActivityAudience | ||
145 | ): ActivityUpdate<ActivityUpdateObject> { | ||
146 | if (!audience) audience = getAudience(byActor) | ||
147 | |||
148 | return audiencify( | ||
149 | { | ||
150 | type: 'Update' as 'Update', | ||
151 | id: url, | ||
152 | actor: byActor.url, | ||
153 | object: audiencify(object, audience) | ||
154 | }, | ||
155 | audience | ||
156 | ) | ||
157 | } | ||
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts deleted file mode 100644 index bf3451603..000000000 --- a/server/lib/activitypub/send/send-view.ts +++ /dev/null | |||
@@ -1,62 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { VideoViewsManager } from '@server/lib/views/video-views-manager' | ||
3 | import { MActorAudience, MActorLight, MVideoImmutable, MVideoUrl } from '@server/types/models' | ||
4 | import { ActivityAudience, ActivityView } from '@shared/models' | ||
5 | import { logger } from '../../../helpers/logger' | ||
6 | import { audiencify, getAudience } from '../audience' | ||
7 | import { getLocalVideoViewActivityPubUrl } from '../url' | ||
8 | import { sendVideoRelatedActivity } from './shared/send-utils' | ||
9 | |||
10 | type ViewType = 'view' | 'viewer' | ||
11 | |||
12 | async function sendView (options: { | ||
13 | byActor: MActorLight | ||
14 | type: ViewType | ||
15 | video: MVideoImmutable | ||
16 | viewerIdentifier: string | ||
17 | transaction?: Transaction | ||
18 | }) { | ||
19 | const { byActor, type, video, viewerIdentifier, transaction } = options | ||
20 | |||
21 | logger.info('Creating job to send %s of %s.', type, video.url) | ||
22 | |||
23 | const activityBuilder = (audience: ActivityAudience) => { | ||
24 | const url = getLocalVideoViewActivityPubUrl(byActor, video, viewerIdentifier) | ||
25 | |||
26 | return buildViewActivity({ url, byActor, video, audience, type }) | ||
27 | } | ||
28 | |||
29 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction, contextType: 'View', parallelizable: true }) | ||
30 | } | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | export { | ||
35 | sendView | ||
36 | } | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | function buildViewActivity (options: { | ||
41 | url: string | ||
42 | byActor: MActorAudience | ||
43 | video: MVideoUrl | ||
44 | type: ViewType | ||
45 | audience?: ActivityAudience | ||
46 | }): ActivityView { | ||
47 | const { url, byActor, type, video, audience = getAudience(byActor) } = options | ||
48 | |||
49 | return audiencify( | ||
50 | { | ||
51 | id: url, | ||
52 | type: 'View' as 'View', | ||
53 | actor: byActor.url, | ||
54 | object: video.url, | ||
55 | |||
56 | expires: type === 'viewer' | ||
57 | ? new Date(VideoViewsManager.Instance.buildViewerExpireTime()).toISOString() | ||
58 | : undefined | ||
59 | }, | ||
60 | audience | ||
61 | ) | ||
62 | } | ||
diff --git a/server/lib/activitypub/send/shared/audience-utils.ts b/server/lib/activitypub/send/shared/audience-utils.ts deleted file mode 100644 index 2f6b0741d..000000000 --- a/server/lib/activitypub/send/shared/audience-utils.ts +++ /dev/null | |||
@@ -1,74 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ACTIVITY_PUB } from '@server/initializers/constants' | ||
3 | import { ActorModel } from '@server/models/actor/actor' | ||
4 | import { VideoModel } from '@server/models/video/video' | ||
5 | import { VideoShareModel } from '@server/models/video/video-share' | ||
6 | import { MActorFollowersUrl, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models' | ||
7 | import { ActivityAudience } from '@shared/models' | ||
8 | |||
9 | function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience { | ||
10 | return { | ||
11 | to: [ accountActor.url ], | ||
12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) | ||
13 | } | ||
14 | } | ||
15 | |||
16 | function getVideoCommentAudience ( | ||
17 | videoComment: MCommentOwnerVideo, | ||
18 | threadParentComments: MCommentOwner[], | ||
19 | actorsInvolvedInVideo: MActorFollowersUrl[], | ||
20 | isOrigin = false | ||
21 | ): ActivityAudience { | ||
22 | const to = [ ACTIVITY_PUB.PUBLIC ] | ||
23 | const cc: string[] = [] | ||
24 | |||
25 | // Owner of the video we comment | ||
26 | if (isOrigin === false) { | ||
27 | cc.push(videoComment.Video.VideoChannel.Account.Actor.url) | ||
28 | } | ||
29 | |||
30 | // Followers of the poster | ||
31 | cc.push(videoComment.Account.Actor.followersUrl) | ||
32 | |||
33 | // Send to actors we reply to | ||
34 | for (const parentComment of threadParentComments) { | ||
35 | if (parentComment.isDeleted()) continue | ||
36 | |||
37 | cc.push(parentComment.Account.Actor.url) | ||
38 | } | ||
39 | |||
40 | return { | ||
41 | to, | ||
42 | cc: cc.concat(actorsInvolvedInVideo.map(a => a.followersUrl)) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience { | ||
47 | return { | ||
48 | to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), | ||
49 | cc: [] | ||
50 | } | ||
51 | } | ||
52 | |||
53 | async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) { | ||
54 | const actors = await VideoShareModel.listActorIdsAndFollowerUrlsByShare(video.id, t) | ||
55 | |||
56 | const videoAll = video as VideoModel | ||
57 | |||
58 | const videoActor = videoAll.VideoChannel?.Account | ||
59 | ? videoAll.VideoChannel.Account.Actor | ||
60 | : await ActorModel.loadAccountActorFollowerUrlByVideoId(video.id, t) | ||
61 | |||
62 | actors.push(videoActor) | ||
63 | |||
64 | return actors | ||
65 | } | ||
66 | |||
67 | // --------------------------------------------------------------------------- | ||
68 | |||
69 | export { | ||
70 | getOriginVideoAudience, | ||
71 | getActorsInvolvedInVideo, | ||
72 | getAudienceFromFollowersOf, | ||
73 | getVideoCommentAudience | ||
74 | } | ||
diff --git a/server/lib/activitypub/send/shared/index.ts b/server/lib/activitypub/send/shared/index.ts deleted file mode 100644 index bda579115..000000000 --- a/server/lib/activitypub/send/shared/index.ts +++ /dev/null | |||
@@ -1,2 +0,0 @@ | |||
1 | export * from './audience-utils' | ||
2 | export * from './send-utils' | ||
diff --git a/server/lib/activitypub/send/shared/send-utils.ts b/server/lib/activitypub/send/shared/send-utils.ts deleted file mode 100644 index 2bc1ef8f5..000000000 --- a/server/lib/activitypub/send/shared/send-utils.ts +++ /dev/null | |||
@@ -1,291 +0,0 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache' | ||
3 | import { getServerActor } from '@server/models/application/application' | ||
4 | import { Activity, ActivityAudience, ActivitypubHttpBroadcastPayload } from '@shared/models' | ||
5 | import { ContextType } from '@shared/models/activitypub/context' | ||
6 | import { afterCommitIfTransaction } from '../../../../helpers/database-utils' | ||
7 | import { logger } from '../../../../helpers/logger' | ||
8 | import { ActorModel } from '../../../../models/actor/actor' | ||
9 | import { ActorFollowModel } from '../../../../models/actor/actor-follow' | ||
10 | import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../../types/models' | ||
11 | import { JobQueue } from '../../../job-queue' | ||
12 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAudience } from './audience-utils' | ||
13 | |||
14 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | ||
15 | byActor: MActorLight | ||
16 | video: MVideoImmutable | MVideoAccountLight | ||
17 | contextType: ContextType | ||
18 | parallelizable?: boolean | ||
19 | transaction?: Transaction | ||
20 | }) { | ||
21 | const { byActor, video, transaction, contextType, parallelizable } = options | ||
22 | |||
23 | // Send to origin | ||
24 | if (video.isOwned() === false) { | ||
25 | return sendVideoActivityToOrigin(activityBuilder, options) | ||
26 | } | ||
27 | |||
28 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) | ||
29 | |||
30 | // Send to followers | ||
31 | const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo) | ||
32 | const activity = activityBuilder(audience) | ||
33 | |||
34 | const actorsException = [ byActor ] | ||
35 | |||
36 | return broadcastToFollowers({ | ||
37 | data: activity, | ||
38 | byActor, | ||
39 | toFollowersOf: actorsInvolvedInVideo, | ||
40 | transaction, | ||
41 | actorsException, | ||
42 | parallelizable, | ||
43 | contextType | ||
44 | }) | ||
45 | } | ||
46 | |||
47 | async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: { | ||
48 | byActor: MActorLight | ||
49 | video: MVideoImmutable | MVideoAccountLight | ||
50 | contextType: ContextType | ||
51 | |||
52 | actorsInvolvedInVideo?: MActorLight[] | ||
53 | transaction?: Transaction | ||
54 | }) { | ||
55 | const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options | ||
56 | |||
57 | if (video.isOwned()) throw new Error('Cannot send activity to owned video origin ' + video.url) | ||
58 | |||
59 | let accountActor: MActorLight = (video as MVideoAccountLight).VideoChannel?.Account?.Actor | ||
60 | if (!accountActor) accountActor = await ActorModel.loadAccountActorByVideoId(video.id, transaction) | ||
61 | |||
62 | const audience = getOriginVideoAudience(accountActor, actorsInvolvedInVideo) | ||
63 | const activity = activityBuilder(audience) | ||
64 | |||
65 | return afterCommitIfTransaction(transaction, () => { | ||
66 | return unicastTo({ | ||
67 | data: activity, | ||
68 | byActor, | ||
69 | toActorUrl: accountActor.getSharedInbox(), | ||
70 | contextType | ||
71 | }) | ||
72 | }) | ||
73 | } | ||
74 | |||
75 | // --------------------------------------------------------------------------- | ||
76 | |||
77 | async function forwardVideoRelatedActivity ( | ||
78 | activity: Activity, | ||
79 | t: Transaction, | ||
80 | followersException: MActorWithInboxes[], | ||
81 | video: MVideoId | ||
82 | ) { | ||
83 | // Mastodon does not add our announces in audience, so we forward to them manually | ||
84 | const additionalActors = await getActorsInvolvedInVideo(video, t) | ||
85 | const additionalFollowerUrls = additionalActors.map(a => a.followersUrl) | ||
86 | |||
87 | return forwardActivity(activity, t, followersException, additionalFollowerUrls) | ||
88 | } | ||
89 | |||
90 | async function forwardActivity ( | ||
91 | activity: Activity, | ||
92 | t: Transaction, | ||
93 | followersException: MActorWithInboxes[] = [], | ||
94 | additionalFollowerUrls: string[] = [] | ||
95 | ) { | ||
96 | logger.info('Forwarding activity %s.', activity.id) | ||
97 | |||
98 | const to = activity.to || [] | ||
99 | const cc = activity.cc || [] | ||
100 | |||
101 | const followersUrls = additionalFollowerUrls | ||
102 | for (const dest of to.concat(cc)) { | ||
103 | if (dest.endsWith('/followers')) { | ||
104 | followersUrls.push(dest) | ||
105 | } | ||
106 | } | ||
107 | |||
108 | const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t) | ||
109 | const uris = await computeFollowerUris(toActorFollowers, followersException, t) | ||
110 | |||
111 | if (uris.length === 0) { | ||
112 | logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', ')) | ||
113 | return undefined | ||
114 | } | ||
115 | |||
116 | logger.debug('Creating forwarding job.', { uris }) | ||
117 | |||
118 | const payload: ActivitypubHttpBroadcastPayload = { | ||
119 | uris, | ||
120 | body: activity, | ||
121 | contextType: null | ||
122 | } | ||
123 | return afterCommitIfTransaction(t, () => JobQueue.Instance.createJobAsync({ type: 'activitypub-http-broadcast', payload })) | ||
124 | } | ||
125 | |||
126 | // --------------------------------------------------------------------------- | ||
127 | |||
128 | async function broadcastToFollowers (options: { | ||
129 | data: any | ||
130 | byActor: MActorId | ||
131 | toFollowersOf: MActorId[] | ||
132 | transaction: Transaction | ||
133 | contextType: ContextType | ||
134 | |||
135 | parallelizable?: boolean | ||
136 | actorsException?: MActorWithInboxes[] | ||
137 | }) { | ||
138 | const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [], parallelizable } = options | ||
139 | |||
140 | const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction) | ||
141 | |||
142 | return afterCommitIfTransaction(transaction, () => { | ||
143 | return broadcastTo({ | ||
144 | uris, | ||
145 | data, | ||
146 | byActor, | ||
147 | parallelizable, | ||
148 | contextType | ||
149 | }) | ||
150 | }) | ||
151 | } | ||
152 | |||
153 | async function broadcastToActors (options: { | ||
154 | data: any | ||
155 | byActor: MActorId | ||
156 | toActors: MActor[] | ||
157 | transaction: Transaction | ||
158 | contextType: ContextType | ||
159 | actorsException?: MActorWithInboxes[] | ||
160 | }) { | ||
161 | const { data, byActor, toActors, transaction, contextType, actorsException = [] } = options | ||
162 | |||
163 | const uris = await computeUris(toActors, actorsException) | ||
164 | |||
165 | return afterCommitIfTransaction(transaction, () => { | ||
166 | return broadcastTo({ | ||
167 | uris, | ||
168 | data, | ||
169 | byActor, | ||
170 | contextType | ||
171 | }) | ||
172 | }) | ||
173 | } | ||
174 | |||
175 | function broadcastTo (options: { | ||
176 | uris: string[] | ||
177 | data: any | ||
178 | byActor: MActorId | ||
179 | contextType: ContextType | ||
180 | parallelizable?: boolean // default to false | ||
181 | }) { | ||
182 | const { uris, data, byActor, contextType, parallelizable } = options | ||
183 | |||
184 | if (uris.length === 0) return undefined | ||
185 | |||
186 | const broadcastUris: string[] = [] | ||
187 | const unicastUris: string[] = [] | ||
188 | |||
189 | // Bad URIs could be slow to respond, prefer to process them in a dedicated queue | ||
190 | for (const uri of uris) { | ||
191 | if (ActorFollowHealthCache.Instance.isBadInbox(uri)) { | ||
192 | unicastUris.push(uri) | ||
193 | } else { | ||
194 | broadcastUris.push(uri) | ||
195 | } | ||
196 | } | ||
197 | |||
198 | logger.debug('Creating broadcast job.', { broadcastUris, unicastUris }) | ||
199 | |||
200 | if (broadcastUris.length !== 0) { | ||
201 | const payload = { | ||
202 | uris: broadcastUris, | ||
203 | signatureActorId: byActor.id, | ||
204 | body: data, | ||
205 | contextType | ||
206 | } | ||
207 | |||
208 | JobQueue.Instance.createJobAsync({ | ||
209 | type: parallelizable | ||
210 | ? 'activitypub-http-broadcast-parallel' | ||
211 | : 'activitypub-http-broadcast', | ||
212 | |||
213 | payload | ||
214 | }) | ||
215 | } | ||
216 | |||
217 | for (const unicastUri of unicastUris) { | ||
218 | const payload = { | ||
219 | uri: unicastUri, | ||
220 | signatureActorId: byActor.id, | ||
221 | body: data, | ||
222 | contextType | ||
223 | } | ||
224 | |||
225 | JobQueue.Instance.createJobAsync({ type: 'activitypub-http-unicast', payload }) | ||
226 | } | ||
227 | } | ||
228 | |||
229 | function unicastTo (options: { | ||
230 | data: any | ||
231 | byActor: MActorId | ||
232 | toActorUrl: string | ||
233 | contextType: ContextType | ||
234 | }) { | ||
235 | const { data, byActor, toActorUrl, contextType } = options | ||
236 | |||
237 | logger.debug('Creating unicast job.', { uri: toActorUrl }) | ||
238 | |||
239 | const payload = { | ||
240 | uri: toActorUrl, | ||
241 | signatureActorId: byActor.id, | ||
242 | body: data, | ||
243 | contextType | ||
244 | } | ||
245 | |||
246 | JobQueue.Instance.createJobAsync({ type: 'activitypub-http-unicast', payload }) | ||
247 | } | ||
248 | |||
249 | // --------------------------------------------------------------------------- | ||
250 | |||
251 | export { | ||
252 | broadcastToFollowers, | ||
253 | unicastTo, | ||
254 | forwardActivity, | ||
255 | broadcastToActors, | ||
256 | sendVideoActivityToOrigin, | ||
257 | forwardVideoRelatedActivity, | ||
258 | sendVideoRelatedActivity | ||
259 | } | ||
260 | |||
261 | // --------------------------------------------------------------------------- | ||
262 | |||
263 | async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) { | ||
264 | const toActorFollowerIds = toFollowersOf.map(a => a.id) | ||
265 | |||
266 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) | ||
267 | const sharedInboxesException = await buildSharedInboxesException(actorsException) | ||
268 | |||
269 | return result.data.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false) | ||
270 | } | ||
271 | |||
272 | async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) { | ||
273 | const serverActor = await getServerActor() | ||
274 | const targetUrls = toActors | ||
275 | .filter(a => a.id !== serverActor.id) // Don't send to ourselves | ||
276 | .map(a => a.getSharedInbox()) | ||
277 | |||
278 | const toActorSharedInboxesSet = new Set(targetUrls) | ||
279 | |||
280 | const sharedInboxesException = await buildSharedInboxesException(actorsException) | ||
281 | return Array.from(toActorSharedInboxesSet) | ||
282 | .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false) | ||
283 | } | ||
284 | |||
285 | async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) { | ||
286 | const serverActor = await getServerActor() | ||
287 | |||
288 | return actorsException | ||
289 | .map(f => f.getSharedInbox()) | ||
290 | .concat([ serverActor.sharedInboxUrl ]) | ||
291 | } | ||