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 { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models'
12 async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
14 video: MVideoAccountLight,
15 transaction?: Transaction
17 const { byActor, video, transaction } = options
19 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction)
22 if (video.isOwned() === false) {
23 const audience = getRemoteVideoAudience(video, actorsInvolvedInVideo)
24 const activity = activityBuilder(audience)
26 return afterCommitIfTransaction(transaction, () => {
27 return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
32 const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
33 const activity = activityBuilder(audience)
35 const actorsException = [ byActor ]
37 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException)
40 async function forwardVideoRelatedActivity (
43 followersException: MActorFollowerException[] = [],
46 // Mastodon does not add our announces in audience, so we forward to them manually
47 const additionalActors = await getActorsInvolvedInVideo(video, t)
48 const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)
50 return forwardActivity(activity, t, followersException, additionalFollowerUrls)
53 async function forwardActivity (
56 followersException: MActorFollowerException[] = [],
57 additionalFollowerUrls: string[] = []
59 logger.info('Forwarding activity %s.', activity.id)
61 const to = activity.to || []
62 const cc = activity.cc || []
64 const followersUrls = additionalFollowerUrls
65 for (const dest of to.concat(cc)) {
66 if (dest.endsWith('/followers')) {
67 followersUrls.push(dest)
71 const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
72 const uris = await computeFollowerUris(toActorFollowers, followersException, t)
74 if (uris.length === 0) {
75 logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
79 logger.debug('Creating forwarding job.', { uris })
85 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
88 async function broadcastToFollowers (
91 toFollowersOf: MActorId[],
93 actorsException: MActorFollowerException[] = []
95 const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
97 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
100 async function broadcastToActors (
105 actorsException: MActorFollowerException[] = []
107 const uris = await computeUris(toActors, actorsException)
108 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor))
111 function broadcastTo (uris: string[], data: any, byActor: MActorId) {
112 if (uris.length === 0) return undefined
114 logger.debug('Creating broadcast job.', { uris })
118 signatureActorId: byActor.id,
122 return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
125 function unicastTo (data: any, byActor: MActorId, toActorUrl: string) {
126 logger.debug('Creating unicast job.', { uri: toActorUrl })
130 signatureActorId: byActor.id,
134 JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
137 // ---------------------------------------------------------------------------
140 broadcastToFollowers,
144 forwardVideoRelatedActivity,
145 sendVideoRelatedActivity
148 // ---------------------------------------------------------------------------
150 async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) {
151 const toActorFollowerIds = toFollowersOf.map(a => a.id)
153 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
154 const sharedInboxesException = await buildSharedInboxesException(actorsException)
156 return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
159 async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) {
160 const serverActor = await getServerActor()
161 const targetUrls = toActors
162 .filter(a => a.id !== serverActor.id) // Don't send to ourselves
163 .map(a => a.sharedInboxUrl || a.inboxUrl)
165 const toActorSharedInboxesSet = new Set(targetUrls)
167 const sharedInboxesException = await buildSharedInboxesException(actorsException)
168 return Array.from(toActorSharedInboxesSet)
169 .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
172 async function buildSharedInboxesException (actorsException: MActorFollowerException[]) {
173 const serverActor = await getServerActor()
175 return actorsException
176 .map(f => f.sharedInboxUrl || f.inboxUrl)
177 .concat([ serverActor.sharedInboxUrl ])