]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/send/utils.ts
Fix hooks definition
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / send / utils.ts
1 import { Transaction } from 'sequelize'
2 import { getServerActor } from '@server/models/application/application'
3 import { ContextType } from '@shared/models/activitypub/context'
4 import { Activity, ActivityAudience } from '../../../../shared/models/activitypub'
5 import { afterCommitIfTransaction } from '../../../helpers/database-utils'
6 import { logger } from '../../../helpers/logger'
7 import { ActorModel } from '../../../models/actor/actor'
8 import { ActorFollowModel } from '../../../models/actor/actor-follow'
9 import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../types/models'
10 import { JobQueue } from '../../job-queue'
11 import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
12
13 async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
14 byActor: MActorLight
15 video: MVideoImmutable | MVideoAccountLight
16 transaction?: Transaction
17 contextType?: ContextType
18 }) {
19 const { byActor, video, transaction, contextType } = options
20
21 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
22
23 // Send to origin
24 if (video.isOwned() === false) {
25 let accountActor: MActorLight = (video as MVideoAccountLight).VideoChannel?.Account?.Actor
26
27 if (!accountActor) accountActor = await ActorModel.loadAccountActorByVideoId(video.id, transaction)
28
29 const audience = getRemoteVideoAudience(accountActor, actorsInvolvedInVideo)
30 const activity = activityBuilder(audience)
31
32 return afterCommitIfTransaction(transaction, () => {
33 return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType)
34 })
35 }
36
37 // Send to followers
38 const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
39 const activity = activityBuilder(audience)
40
41 const actorsException = [ byActor ]
42
43 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType)
44 }
45
46 async function forwardVideoRelatedActivity (
47 activity: Activity,
48 t: Transaction,
49 followersException: MActorWithInboxes[],
50 video: MVideoId
51 ) {
52 // Mastodon does not add our announces in audience, so we forward to them manually
53 const additionalActors = await getActorsInvolvedInVideo(video, t)
54 const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)
55
56 return forwardActivity(activity, t, followersException, additionalFollowerUrls)
57 }
58
59 async function forwardActivity (
60 activity: Activity,
61 t: Transaction,
62 followersException: MActorWithInboxes[] = [],
63 additionalFollowerUrls: string[] = []
64 ) {
65 logger.info('Forwarding activity %s.', activity.id)
66
67 const to = activity.to || []
68 const cc = activity.cc || []
69
70 const followersUrls = additionalFollowerUrls
71 for (const dest of to.concat(cc)) {
72 if (dest.endsWith('/followers')) {
73 followersUrls.push(dest)
74 }
75 }
76
77 const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
78 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
79
80 if (uris.length === 0) {
81 logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
82 return undefined
83 }
84
85 logger.debug('Creating forwarding job.', { uris })
86
87 const payload = {
88 uris,
89 body: activity
90 }
91 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
92 }
93
94 async function broadcastToFollowers (
95 data: any,
96 byActor: MActorId,
97 toFollowersOf: MActorId[],
98 t: Transaction,
99 actorsException: MActorWithInboxes[] = [],
100 contextType?: ContextType
101 ) {
102 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
103
104 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
105 }
106
107 async function broadcastToActors (
108 data: any,
109 byActor: MActorId,
110 toActors: MActor[],
111 t?: Transaction,
112 actorsException: MActorWithInboxes[] = [],
113 contextType?: ContextType
114 ) {
115 const uris = await computeUris(toActors, actorsException)
116 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
117 }
118
119 function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) {
120 if (uris.length === 0) return undefined
121
122 logger.debug('Creating broadcast job.', { uris })
123
124 const payload = {
125 uris,
126 signatureActorId: byActor.id,
127 body: data,
128 contextType
129 }
130
131 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
132 }
133
134 function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) {
135 logger.debug('Creating unicast job.', { uri: toActorUrl })
136
137 const payload = {
138 uri: toActorUrl,
139 signatureActorId: byActor.id,
140 body: data,
141 contextType
142 }
143
144 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
145 }
146
147 // ---------------------------------------------------------------------------
148
149 export {
150 broadcastToFollowers,
151 unicastTo,
152 forwardActivity,
153 broadcastToActors,
154 forwardVideoRelatedActivity,
155 sendVideoRelatedActivity
156 }
157
158 // ---------------------------------------------------------------------------
159
160 async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) {
161 const toActorFollowerIds = toFollowersOf.map(a => a.id)
162
163 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
164 const sharedInboxesException = await buildSharedInboxesException(actorsException)
165
166 return result.data.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
167 }
168
169 async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) {
170 const serverActor = await getServerActor()
171 const targetUrls = toActors
172 .filter(a => a.id !== serverActor.id) // Don't send to ourselves
173 .map(a => a.getSharedInbox())
174
175 const toActorSharedInboxesSet = new Set(targetUrls)
176
177 const sharedInboxesException = await buildSharedInboxesException(actorsException)
178 return Array.from(toActorSharedInboxesSet)
179 .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
180 }
181
182 async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {
183 const serverActor = await getServerActor()
184
185 return actorsException
186 .map(f => f.getSharedInbox())
187 .concat([ serverActor.sharedInboxUrl ])
188 }