]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/activitypub/send/shared/send-utils.ts
Bumped to version v5.2.1
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / send / shared / send-utils.ts
CommitLineData
54141398 1import { Transaction } from 'sequelize'
9db437c8 2import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache'
7d9ba5c0 3import { getServerActor } from '@server/models/application/application'
a219c910 4import { Activity, ActivityAudience, ActivitypubHttpBroadcastPayload } from '@shared/models'
7d9ba5c0 5import { ContextType } from '@shared/models/activitypub/context'
57e4e1c1
C
6import { afterCommitIfTransaction } from '../../../../helpers/database-utils'
7import { logger } from '../../../../helpers/logger'
8import { ActorModel } from '../../../../models/actor/actor'
9import { ActorFollowModel } from '../../../../models/actor/actor-follow'
10import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../../types/models'
11import { JobQueue } from '../../../job-queue'
12import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAudience } from './audience-utils'
9588d4f4 13
a2377d15 14async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
a1587156 15 byActor: MActorLight
2c8776fc 16 video: MVideoImmutable | MVideoAccountLight
a219c910 17 contextType: ContextType
f27b7a75 18 parallelizable?: boolean
f51c02c7 19 transaction?: Transaction
a2377d15 20}) {
f27b7a75 21 const { byActor, video, transaction, contextType, parallelizable } = options
5224c394 22
a2377d15 23 // Send to origin
5224c394 24 if (video.isOwned() === false) {
57e4e1c1 25 return sendVideoActivityToOrigin(activityBuilder, options)
a2377d15
C
26 }
27
3396e653
C
28 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
29
a2377d15
C
30 // Send to followers
31 const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
32 const activity = activityBuilder(audience)
33
5224c394 34 const actorsException = [ byActor ]
2284f202 35
a219c910
C
36 return broadcastToFollowers({
37 data: activity,
38 byActor,
39 toFollowersOf: actorsInvolvedInVideo,
40 transaction,
41 actorsException,
f27b7a75 42 parallelizable,
a219c910
C
43 contextType
44 })
a2377d15
C
45}
46
57e4e1c1
C
47async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: {
48 byActor: MActorLight
49 video: MVideoImmutable | MVideoAccountLight
a219c910
C
50 contextType: ContextType
51
57e4e1c1
C
52 actorsInvolvedInVideo?: MActorLight[]
53 transaction?: Transaction
57e4e1c1
C
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, () => {
a219c910
C
66 return unicastTo({
67 data: activity,
68 byActor,
69 toActorUrl: accountActor.getSharedInbox(),
70 contextType
71 })
57e4e1c1
C
72 })
73}
74
75// ---------------------------------------------------------------------------
76
9588d4f4
C
77async function forwardVideoRelatedActivity (
78 activity: Activity,
79 t: Transaction,
bdd428a6 80 followersException: MActorWithInboxes[],
943e5193 81 video: MVideoId
9588d4f4
C
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}
63c93323
C
89
90async function forwardActivity (
91 activity: Activity,
92 t: Transaction,
47581df0 93 followersException: MActorWithInboxes[] = [],
93ef8a9d 94 additionalFollowerUrls: string[] = []
63c93323 95) {
8e0fd45e
C
96 logger.info('Forwarding activity %s.', activity.id)
97
63c93323
C
98 const to = activity.to || []
99 const cc = activity.cc || []
100
93ef8a9d 101 const followersUrls = additionalFollowerUrls
63c93323
C
102 for (const dest of to.concat(cc)) {
103 if (dest.endsWith('/followers')) {
104 followersUrls.push(dest)
105 }
106 }
107
50d6de9c
C
108 const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
109 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
63c93323
C
110
111 if (uris.length === 0) {
50d6de9c 112 logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
df1966c9 113 return undefined
63c93323
C
114 }
115
116 logger.debug('Creating forwarding job.', { uris })
117
a219c910 118 const payload: ActivitypubHttpBroadcastPayload = {
63c93323 119 uris,
a219c910
C
120 body: activity,
121 contextType: null
63c93323 122 }
bd911b54 123 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJobAsync({ type: 'activitypub-http-broadcast', payload }))
63c93323 124}
54141398 125
57e4e1c1
C
126// ---------------------------------------------------------------------------
127
a219c910
C
128async function broadcastToFollowers (options: {
129 data: any
130 byActor: MActorId
131 toFollowersOf: MActorId[]
132 transaction: Transaction
133 contextType: ContextType
134
f27b7a75 135 parallelizable?: boolean
a219c910
C
136 actorsException?: MActorWithInboxes[]
137}) {
f27b7a75 138 const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [], parallelizable } = options
2284f202 139
a219c910
C
140 const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction)
141
142 return afterCommitIfTransaction(transaction, () => {
143 return broadcastTo({
144 uris,
145 data,
146 byActor,
f27b7a75 147 parallelizable,
a219c910
C
148 contextType
149 })
150 })
93ef8a9d
C
151}
152
a219c910
C
153async 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
93ef8a9d 163 const uris = await computeUris(toActors, actorsException)
a219c910
C
164
165 return afterCommitIfTransaction(transaction, () => {
166 return broadcastTo({
167 uris,
168 data,
169 byActor,
170 contextType
171 })
172 })
93ef8a9d
C
173}
174
a219c910
C
175function broadcastTo (options: {
176 uris: string[]
177 data: any
178 byActor: MActorId
179 contextType: ContextType
f27b7a75 180 parallelizable?: boolean // default to false
a219c910 181}) {
f27b7a75 182 const { uris, data, byActor, contextType, parallelizable } = options
a219c910 183
93ef8a9d 184 if (uris.length === 0) return undefined
54141398 185
9db437c8
C
186 const broadcastUris: string[] = []
187 const unicastUris: string[] = []
40ff5707 188
9db437c8
C
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
bd911b54 208 JobQueue.Instance.createJobAsync({
f27b7a75
C
209 type: parallelizable
210 ? 'activitypub-http-broadcast-parallel'
211 : 'activitypub-http-broadcast',
212
213 payload
214 })
54141398
C
215 }
216
9db437c8
C
217 for (const unicastUri of unicastUris) {
218 const payload = {
219 uri: unicastUri,
220 signatureActorId: byActor.id,
221 body: data,
222 contextType
223 }
224
bd911b54 225 JobQueue.Instance.createJobAsync({ type: 'activitypub-http-unicast', payload })
9db437c8 226 }
54141398
C
227}
228
a219c910
C
229function unicastTo (options: {
230 data: any
231 byActor: MActorId
232 toActorUrl: string
233 contextType: ContextType
234}) {
235 const { data, byActor, toActorUrl, contextType } = options
236
50d6de9c 237 logger.debug('Creating unicast job.', { uri: toActorUrl })
63c93323 238
94a5ff8a
C
239 const payload = {
240 uri: toActorUrl,
50d6de9c 241 signatureActorId: byActor.id,
598edb8a
C
242 body: data,
243 contextType
54141398
C
244 }
245
bd911b54 246 JobQueue.Instance.createJobAsync({ type: 'activitypub-http-unicast', payload })
54141398
C
247}
248
e251f170 249// ---------------------------------------------------------------------------
54141398 250
e251f170
C
251export {
252 broadcastToFollowers,
253 unicastTo,
254 forwardActivity,
9588d4f4 255 broadcastToActors,
57e4e1c1 256 sendVideoActivityToOrigin,
a2377d15
C
257 forwardVideoRelatedActivity,
258 sendVideoRelatedActivity
54141398
C
259}
260
e251f170 261// ---------------------------------------------------------------------------
e12a0092 262
47581df0 263async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) {
c48e82b5 264 const toActorFollowerIds = toFollowersOf.map(a => a.id)
63c93323 265
50d6de9c 266 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
06a05d5f
C
267 const sharedInboxesException = await buildSharedInboxesException(actorsException)
268
bdd428a6 269 return result.data.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
93ef8a9d
C
270}
271
47581df0 272async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) {
06a05d5f
C
273 const serverActor = await getServerActor()
274 const targetUrls = toActors
275 .filter(a => a.id !== serverActor.id) // Don't send to ourselves
47581df0 276 .map(a => a.getSharedInbox())
06a05d5f
C
277
278 const toActorSharedInboxesSet = new Set(targetUrls)
93ef8a9d 279
06a05d5f 280 const sharedInboxesException = await buildSharedInboxesException(actorsException)
93ef8a9d 281 return Array.from(toActorSharedInboxesSet)
bdd428a6 282 .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
54141398 283}
06a05d5f 284
47581df0 285async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {
06a05d5f
C
286 const serverActor = await getServerActor()
287
288 return actorsException
47581df0 289 .map(f => f.getSharedInbox())
06a05d5f
C
290 .concat([ serverActor.sharedInboxUrl ])
291}