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