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 { afterCommitIfTransaction } from '../../../helpers/database-utils'
9 import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../types/models'
10 import { getServerActor } from '@server/models/application/application'
11 import { ContextType } from '@shared/models/activitypub/context'
13 async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
15 video: MVideoImmutable | MVideoAccountLight
16 transaction?: Transaction
17 contextType?: ContextType
19 const { byActor, video, transaction, contextType } = options
21 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
24 if (video.isOwned() === false) {
25 const accountActor = (video as MVideoAccountLight).VideoChannel?.Account?.Actor || await ActorModel.loadAccountActorByVideoId(video.id)
27 const audience = getRemoteVideoAudience(accountActor, actorsInvolvedInVideo)
28 const activity = activityBuilder(audience)
30 return afterCommitIfTransaction(transaction, () => {
31 return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType)
36 const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
37 const activity = activityBuilder(audience)
39 const actorsException = [ byActor ]
41 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType)
44 async function forwardVideoRelatedActivity (
47 followersException: MActorWithInboxes[],
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)
54 return forwardActivity(activity, t, followersException, additionalFollowerUrls)
57 async function forwardActivity (
60 followersException: MActorWithInboxes[] = [],
61 additionalFollowerUrls: string[] = []
63 logger.info('Forwarding activity %s.', activity.id)
65 const to = activity.to || []
66 const cc = activity.cc || []
68 const followersUrls = additionalFollowerUrls
69 for (const dest of to.concat(cc)) {
70 if (dest.endsWith('/followers')) {
71 followersUrls.push(dest)
75 const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
76 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
78 if (uris.length === 0) {
79 logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
83 logger.debug('Creating forwarding job.', { uris })
89 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
92 async function broadcastToFollowers (
95 toFollowersOf: MActorId[],
97 actorsException: MActorWithInboxes[] = [],
98 contextType?: ContextType
100 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
102 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
105 async function broadcastToActors (
110 actorsException: MActorWithInboxes[] = [],
111 contextType?: ContextType
113 const uris = await computeUris(toActors, actorsException)
114 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType))
117 function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) {
118 if (uris.length === 0) return undefined
120 logger.debug('Creating broadcast job.', { uris })
124 signatureActorId: byActor.id,
129 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
132 function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) {
133 logger.debug('Creating unicast job.', { uri: toActorUrl })
137 signatureActorId: byActor.id,
142 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
145 // ---------------------------------------------------------------------------
148 broadcastToFollowers,
152 forwardVideoRelatedActivity,
153 sendVideoRelatedActivity
156 // ---------------------------------------------------------------------------
158 async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorWithInboxes[], t: Transaction) {
159 const toActorFollowerIds = toFollowersOf.map(a => a.id)
161 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
162 const sharedInboxesException = await buildSharedInboxesException(actorsException)
164 return result.data.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
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())
173 const toActorSharedInboxesSet = new Set(targetUrls)
175 const sharedInboxesException = await buildSharedInboxesException(actorsException)
176 return Array.from(toActorSharedInboxesSet)
177 .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false)
180 async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) {
181 const serverActor = await getServerActor()
183 return actorsException
184 .map(f => f.getSharedInbox())
185 .concat([ serverActor.sharedInboxUrl ])