]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/lib/activitypub/send/shared/send-utils.ts
Don't store remote rates of remote videos
[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'
57e4e1c1 4import { Activity, ActivityAudience } 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
f51c02c7 17 transaction?: Transaction
598edb8a 18 contextType?: ContextType
a2377d15 19}) {
598edb8a 20 const { byActor, video, transaction, contextType } = options
5224c394
C
21
22 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
a2377d15
C
23
24 // Send to origin
5224c394 25 if (video.isOwned() === false) {
57e4e1c1 26 return sendVideoActivityToOrigin(activityBuilder, options)
a2377d15
C
27 }
28
29 // Send to followers
30 const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
31 const activity = activityBuilder(audience)
32
5224c394 33 const actorsException = [ byActor ]
2284f202 34
598edb8a 35 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType)
a2377d15
C
36}
37
57e4e1c1
C
38async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: {
39 byActor: MActorLight
40 video: MVideoImmutable | MVideoAccountLight
41 actorsInvolvedInVideo?: MActorLight[]
42 transaction?: Transaction
43 contextType?: ContextType
44}) {
45 const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options
46
47 if (video.isOwned()) throw new Error('Cannot send activity to owned video origin ' + video.url)
48
49 let accountActor: MActorLight = (video as MVideoAccountLight).VideoChannel?.Account?.Actor
50 if (!accountActor) accountActor = await ActorModel.loadAccountActorByVideoId(video.id, transaction)
51
52 const audience = getOriginVideoAudience(accountActor, actorsInvolvedInVideo)
53 const activity = activityBuilder(audience)
54
55 return afterCommitIfTransaction(transaction, () => {
56 return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType)
57 })
58}
59
60// ---------------------------------------------------------------------------
61
9588d4f4
C
62async function forwardVideoRelatedActivity (
63 activity: Activity,
64 t: Transaction,
bdd428a6 65 followersException: MActorWithInboxes[],
943e5193 66 video: MVideoId
9588d4f4
C
67) {
68 // Mastodon does not add our announces in audience, so we forward to them manually
69 const additionalActors = await getActorsInvolvedInVideo(video, t)
70 const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)
71
72 return forwardActivity(activity, t, followersException, additionalFollowerUrls)
73}
63c93323
C
74
75async function forwardActivity (
76 activity: Activity,
77 t: Transaction,
47581df0 78 followersException: MActorWithInboxes[] = [],
93ef8a9d 79 additionalFollowerUrls: string[] = []
63c93323 80) {
8e0fd45e
C
81 logger.info('Forwarding activity %s.', activity.id)
82
63c93323
C
83 const to = activity.to || []
84 const cc = activity.cc || []
85
93ef8a9d 86 const followersUrls = additionalFollowerUrls
63c93323
C
87 for (const dest of to.concat(cc)) {
88 if (dest.endsWith('/followers')) {
89 followersUrls.push(dest)
90 }
91 }
92
50d6de9c
C
93 const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
94 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
63c93323
C
95
96 if (uris.length === 0) {
50d6de9c 97 logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
df1966c9 98 return undefined
63c93323
C
99 }
100
101 logger.debug('Creating forwarding job.', { uris })
102
94a5ff8a 103 const payload = {
63c93323
C
104 uris,
105 body: activity
106 }
2284f202 107 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
63c93323 108}
54141398 109
57e4e1c1
C
110// ---------------------------------------------------------------------------
111
40ff5707
C
112async function broadcastToFollowers (
113 data: any,
453e83ea
C
114 byActor: MActorId,
115 toFollowersOf: MActorId[],
40ff5707 116 t: Transaction,
598edb8a
C
117 actorsException: MActorWithInboxes[] = [],
118 contextType?: ContextType
40ff5707 119) {
c48e82b5 120 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
2284f202 121
598edb8a 122 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
93ef8a9d
C
123}
124
125async function broadcastToActors (
126 data: any,
453e83ea
C
127 byActor: MActorId,
128 toActors: MActor[],
2284f202 129 t?: Transaction,
598edb8a
C
130 actorsException: MActorWithInboxes[] = [],
131 contextType?: ContextType
93ef8a9d
C
132) {
133 const uris = await computeUris(toActors, actorsException)
598edb8a 134 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
93ef8a9d
C
135}
136
598edb8a 137function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) {
93ef8a9d 138 if (uris.length === 0) return undefined
54141398 139
9db437c8
C
140 const broadcastUris: string[] = []
141 const unicastUris: string[] = []
40ff5707 142
9db437c8
C
143 // Bad URIs could be slow to respond, prefer to process them in a dedicated queue
144 for (const uri of uris) {
145 if (ActorFollowHealthCache.Instance.isBadInbox(uri)) {
146 unicastUris.push(uri)
147 } else {
148 broadcastUris.push(uri)
149 }
150 }
151
152 logger.debug('Creating broadcast job.', { broadcastUris, unicastUris })
153
154 if (broadcastUris.length !== 0) {
155 const payload = {
156 uris: broadcastUris,
157 signatureActorId: byActor.id,
158 body: data,
159 contextType
160 }
161
162 JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
54141398
C
163 }
164
9db437c8
C
165 for (const unicastUri of unicastUris) {
166 const payload = {
167 uri: unicastUri,
168 signatureActorId: byActor.id,
169 body: data,
170 contextType
171 }
172
173 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
174 }
54141398
C
175}
176
598edb8a 177function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) {
50d6de9c 178 logger.debug('Creating unicast job.', { uri: toActorUrl })
63c93323 179
94a5ff8a
C
180 const payload = {
181 uri: toActorUrl,
50d6de9c 182 signatureActorId: byActor.id,
598edb8a
C
183 body: data,
184 contextType
54141398
C
185 }
186
2284f202 187 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
54141398
C
188}
189
e251f170 190// ---------------------------------------------------------------------------
54141398 191
e251f170
C
192export {
193 broadcastToFollowers,
194 unicastTo,
195 forwardActivity,
9588d4f4 196 broadcastToActors,
57e4e1c1 197 sendVideoActivityToOrigin,
a2377d15
C
198 forwardVideoRelatedActivity,
199 sendVideoRelatedActivity
54141398
C
200}
201
e251f170 202// ---------------------------------------------------------------------------
e12a0092 203
47581df0 204async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) {
c48e82b5 205 const toActorFollowerIds = toFollowersOf.map(a => a.id)
63c93323 206
50d6de9c 207 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
06a05d5f
C
208 const sharedInboxesException = await buildSharedInboxesException(actorsException)
209
bdd428a6 210 return result.data.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
93ef8a9d
C
211}
212
47581df0 213async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) {
06a05d5f
C
214 const serverActor = await getServerActor()
215 const targetUrls = toActors
216 .filter(a => a.id !== serverActor.id) // Don't send to ourselves
47581df0 217 .map(a => a.getSharedInbox())
06a05d5f
C
218
219 const toActorSharedInboxesSet = new Set(targetUrls)
93ef8a9d 220
06a05d5f 221 const sharedInboxesException = await buildSharedInboxesException(actorsException)
93ef8a9d 222 return Array.from(toActorSharedInboxesSet)
bdd428a6 223 .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
54141398 224}
06a05d5f 225
47581df0 226async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {
06a05d5f
C
227 const serverActor = await getServerActor()
228
229 return actorsException
47581df0 230 .map(f => f.getSharedInbox())
06a05d5f
C
231 .concat([ serverActor.sharedInboxUrl ])
232}