Ver Fonte

Refractor activities sending

Chocobozzz há 5 anos atrás
pai
commit
a2377d15ee

+ 2 - 2
server/controllers/api/videos/index.ts

@@ -393,9 +393,9 @@ async function viewVideo (req: express.Request, res: express.Response) {
     Redis.Instance.setIPVideoView(ip, videoInstance.uuid)
   ])
 
-  const serverAccount = await getServerActor()
+  const serverActor = await getServerActor()
 
-  await sendCreateView(serverAccount, videoInstance, undefined)
+  await sendCreateView(serverActor, videoInstance, undefined)
 
   return res.status(204).end()
 }

+ 5 - 5
server/lib/activitypub/audience.ts

@@ -6,7 +6,7 @@ import { VideoModel } from '../../models/video/video'
 import { VideoCommentModel } from '../../models/video/video-comment'
 import { VideoShareModel } from '../../models/video/video-share'
 
-function getVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]) {
+function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience {
   return {
     to: [ video.VideoChannel.Account.Actor.url ],
     cc: actorsInvolvedInVideo.map(a => a.followersUrl)
@@ -18,7 +18,7 @@ function getVideoCommentAudience (
   threadParentComments: VideoCommentModel[],
   actorsInvolvedInVideo: ActorModel[],
   isOrigin = false
-) {
+): ActivityAudience {
   const to = [ ACTIVITY_PUB.PUBLIC ]
   const cc: string[] = []
 
@@ -41,7 +41,7 @@ function getVideoCommentAudience (
   }
 }
 
-function getObjectFollowersAudience (actorsInvolvedInObject: ActorModel[]) {
+function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience {
   return {
     to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
     cc: []
@@ -83,9 +83,9 @@ function audiencify<T> (object: T, audience: ActivityAudience) {
 export {
   buildAudience,
   getAudience,
-  getVideoAudience,
+  getRemoteVideoAudience,
   getActorsInvolvedInVideo,
-  getObjectFollowersAudience,
+  getAudienceFromFollowersOf,
   audiencify,
   getVideoCommentAudience
 }

+ 2 - 0
server/lib/activitypub/process/process-delete.ts

@@ -41,6 +41,8 @@ async function processDeleteActivity (activity: ActivityDelete) {
   {
     const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(objectUrl)
     if (videoInstance) {
+      if (videoInstance.isOwned()) throw new Error(`Remote instance cannot delete owned video ${videoInstance.url}.`)
+
       return retryTransactionWrapper(processDeleteVideo, actor, videoInstance)
     }
   }

+ 2 - 2
server/lib/activitypub/send/send-announce.ts

@@ -4,14 +4,14 @@ import { ActorModel } from '../../../models/activitypub/actor'
 import { VideoModel } from '../../../models/video/video'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { broadcastToFollowers } from './utils'
-import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience'
+import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience'
 import { logger } from '../../../helpers/logger'
 
 async function buildAnnounceWithVideoAudience (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
   const announcedObject = video.url
 
   const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
-  const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
+  const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
 
   const activity = buildAnnounceActivity(videoShare.url, byActor, announcedObject, audience)
 

+ 43 - 52
server/lib/activitypub/send/send-create.ts

@@ -1,21 +1,13 @@
 import { Transaction } from 'sequelize'
 import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub'
 import { VideoPrivacy } from '../../../../shared/models/videos'
-import { getServerActor } from '../../../helpers/utils'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { VideoModel } from '../../../models/video/video'
 import { VideoAbuseModel } from '../../../models/video/video-abuse'
 import { VideoCommentModel } from '../../../models/video/video-comment'
 import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
-import { broadcastToActors, broadcastToFollowers, unicastTo } from './utils'
-import {
-  audiencify,
-  getActorsInvolvedInVideo,
-  getAudience,
-  getObjectFollowersAudience,
-  getVideoAudience,
-  getVideoCommentAudience
-} from '../audience'
+import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
+import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
 import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
 
@@ -40,6 +32,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
 
   logger.info('Creating job to send video abuse %s.', url)
 
+  // Custom audience, we only send the abuse to the origin instance
   const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] }
   const createActivity = buildCreateActivity(url, byActor, videoAbuse.toActivityPubObject(), audience)
 
@@ -49,15 +42,15 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel,
 async function sendCreateCacheFile (byActor: ActorModel, fileRedundancy: VideoRedundancyModel) {
   logger.info('Creating job to send file cache of %s.', fileRedundancy.url)
 
-  const redundancyObject = fileRedundancy.toActivityPubObject()
-
   const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(fileRedundancy.VideoFile.Video.id)
-  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, undefined)
-
-  const audience = getVideoAudience(video, actorsInvolvedInVideo)
-  const createActivity = buildCreateActivity(fileRedundancy.url, byActor, redundancyObject, audience)
+  const redundancyObject = fileRedundancy.toActivityPubObject()
 
-  return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
+  return sendVideoRelatedCreateActivity({
+    byActor,
+    video,
+    url: fileRedundancy.url,
+    object: redundancyObject
+  })
 }
 
 async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) {
@@ -70,6 +63,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio
   const commentObject = comment.toActivityPubObject(threadParentComments)
 
   const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
+  // Add the actor that commented too
   actorsInvolvedInComment.push(byActor)
 
   const parentsCommentActors = threadParentComments.map(c => c.Account.Actor)
@@ -78,7 +72,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio
   if (isOrigin) {
     audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment, isOrigin)
   } else {
-    audience = getObjectFollowersAudience(actorsInvolvedInComment.concat(parentsCommentActors))
+    audience = getAudienceFromFollowersOf(actorsInvolvedInComment.concat(parentsCommentActors))
   }
 
   const createActivity = buildCreateActivity(comment.url, byActor, commentObject, audience)
@@ -103,24 +97,14 @@ async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transa
   const url = getVideoViewActivityPubUrl(byActor, video)
   const viewActivity = buildViewActivity(byActor, video)
 
-  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
-
-  // Send to origin
-  if (video.isOwned() === false) {
-    const audience = getVideoAudience(video, actorsInvolvedInVideo)
-    const createActivity = buildCreateActivity(url, byActor, viewActivity, audience)
-
-    return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
-  }
-
-  // Send to followers
-  const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
-  const createActivity = buildCreateActivity(url, byActor, viewActivity, audience)
-
-  // Use the server actor to send the view
-  const serverActor = await getServerActor()
-  const actorsException = [ byActor ]
-  return broadcastToFollowers(createActivity, serverActor, actorsInvolvedInVideo, t, actorsException)
+  return sendVideoRelatedCreateActivity({
+    // Use the server actor to send the view
+    byActor,
+    video,
+    url,
+    object: viewActivity,
+    transaction: t
+  })
 }
 
 async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -129,22 +113,13 @@ async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Tra
   const url = getVideoDislikeActivityPubUrl(byActor, video)
   const dislikeActivity = buildDislikeActivity(byActor, video)
 
-  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
-
-  // Send to origin
-  if (video.isOwned() === false) {
-    const audience = getVideoAudience(video, actorsInvolvedInVideo)
-    const createActivity = buildCreateActivity(url, byActor, dislikeActivity, audience)
-
-    return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
-  }
-
-  // Send to followers
-  const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
-  const createActivity = buildCreateActivity(url, byActor, dislikeActivity, audience)
-
-  const actorsException = [ byActor ]
-  return broadcastToFollowers(createActivity, byActor, actorsInvolvedInVideo, t, actorsException)
+  return sendVideoRelatedCreateActivity({
+    byActor,
+    video,
+    url,
+    object: dislikeActivity,
+    transaction: t
+  })
 }
 
 function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate {
@@ -189,3 +164,19 @@ export {
   sendCreateVideoComment,
   sendCreateCacheFile
 }
+
+// ---------------------------------------------------------------------------
+
+async function sendVideoRelatedCreateActivity (options: {
+  byActor: ActorModel,
+  video: VideoModel,
+  url: string,
+  object: any,
+  transaction?: Transaction
+}) {
+  const activityBuilder = (audience: ActivityAudience) => {
+    return buildCreateActivity(options.url, options.byActor, options.object, audience)
+  }
+
+  return sendVideoRelatedActivity(activityBuilder, options)
+}

+ 7 - 6
server/lib/activitypub/send/send-delete.ts

@@ -5,21 +5,22 @@ import { VideoModel } from '../../../models/video/video'
 import { VideoCommentModel } from '../../../models/video/video-comment'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { getDeleteActivityPubUrl } from '../url'
-import { broadcastToActors, broadcastToFollowers, unicastTo } from './utils'
+import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
 import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
 
-async function sendDeleteVideo (video: VideoModel, t: Transaction) {
+async function sendDeleteVideo (video: VideoModel, transaction: Transaction) {
   logger.info('Creating job to broadcast delete of video %s.', video.url)
 
-  const url = getDeleteActivityPubUrl(video.url)
   const byActor = video.VideoChannel.Account.Actor
 
-  const activity = buildDeleteActivity(url, video.url, byActor)
+  const activityBuilder = (audience: ActivityAudience) => {
+    const url = getDeleteActivityPubUrl(video.url)
 
-  const actorsInvolved = await getActorsInvolvedInVideo(video, t)
+    return buildDeleteActivity(url, video.url, byActor, audience)
+  }
 
-  return broadcastToFollowers(activity, byActor, actorsInvolved, t)
+  return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction })
 }
 
 async function sendDeleteActor (byActor: ActorModel, t: Transaction) {

+ 6 - 17
server/lib/activitypub/send/send-like.ts

@@ -3,31 +3,20 @@ import { ActivityAudience, ActivityLike } from '../../../../shared/models/activi
 import { ActorModel } from '../../../models/activitypub/actor'
 import { VideoModel } from '../../../models/video/video'
 import { getVideoLikeActivityPubUrl } from '../url'
-import { broadcastToFollowers, unicastTo } from './utils'
-import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience'
+import { sendVideoRelatedActivity } from './utils'
+import { audiencify, getAudience } from '../audience'
 import { logger } from '../../../helpers/logger'
 
 async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
   logger.info('Creating job to like %s.', video.url)
 
-  const url = getVideoLikeActivityPubUrl(byActor, video)
+  const activityBuilder = (audience: ActivityAudience) => {
+    const url = getVideoLikeActivityPubUrl(byActor, video)
 
-  const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
-
-  // Send to origin
-  if (video.isOwned() === false) {
-    const audience = getVideoAudience(video, accountsInvolvedInVideo)
-    const data = buildLikeActivity(url, byActor, video, audience)
-
-    return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
+    return buildLikeActivity(url, byActor, video, audience)
   }
 
-  // Send to followers
-  const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
-  const activity = buildLikeActivity(url, byActor, video, audience)
-
-  const followersException = [ byActor ]
-  return broadcastToFollowers(activity, byActor, accountsInvolvedInVideo, t, followersException)
+  return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t })
 }
 
 function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike {

+ 33 - 52
server/lib/activitypub/send/send-undo.ts

@@ -11,8 +11,8 @@ import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { VideoModel } from '../../../models/video/video'
 import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
-import { broadcastToFollowers, unicastTo } from './utils'
-import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getVideoAudience } from '../audience'
+import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
+import { audiencify, getAudience } from '../audience'
 import { buildCreateActivity, buildDislikeActivity } from './send-create'
 import { buildFollowActivity } from './send-follow'
 import { buildLikeActivity } from './send-like'
@@ -39,79 +39,44 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) {
   return unicastTo(undoActivity, me, following.inboxUrl)
 }
 
-async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
-  logger.info('Creating job to undo a like of video %s.', video.url)
+async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
+  logger.info('Creating job to undo announce %s.', videoShare.url)
 
-  const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
-  const undoUrl = getUndoActivityPubUrl(likeUrl)
+  const undoUrl = getUndoActivityPubUrl(videoShare.url)
 
-  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
-  const likeActivity = buildLikeActivity(likeUrl, byActor, video)
+  const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
+  const undoActivity = undoActivityData(undoUrl, byActor, announceActivity)
 
-  // Send to origin
-  if (video.isOwned() === false) {
-    const audience = getVideoAudience(video, actorsInvolvedInVideo)
-    const undoActivity = undoActivityData(undoUrl, byActor, likeActivity, audience)
+  const followersException = [ byActor ]
+  return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
+}
 
-    return unicastTo(undoActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
-  }
+async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) {
+  logger.info('Creating job to undo a like of video %s.', video.url)
 
-  const audience = getObjectFollowersAudience(actorsInvolvedInVideo)
-  const undoActivity = undoActivityData(undoUrl, byActor, likeActivity, audience)
+  const likeUrl = getVideoLikeActivityPubUrl(byActor, video)
+  const likeActivity = buildLikeActivity(likeUrl, byActor, video)
 
-  const followersException = [ byActor ]
-  return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
+  return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
 }
 
 async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) {
   logger.info('Creating job to undo a dislike of video %s.', video.url)
 
   const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video)
-  const undoUrl = getUndoActivityPubUrl(dislikeUrl)
-
-  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
   const dislikeActivity = buildDislikeActivity(byActor, video)
   const createDislikeActivity = buildCreateActivity(dislikeUrl, byActor, dislikeActivity)
 
-  if (video.isOwned() === false) {
-    const audience = getVideoAudience(video, actorsInvolvedInVideo)
-    const undoActivity = undoActivityData(undoUrl, byActor, createDislikeActivity, audience)
-
-    return unicastTo(undoActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
-  }
-
-  const undoActivity = undoActivityData(undoUrl, byActor, createDislikeActivity)
-
-  const followersException = [ byActor ]
-  return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
-}
-
-async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) {
-  logger.info('Creating job to undo announce %s.', videoShare.url)
-
-  const undoUrl = getUndoActivityPubUrl(videoShare.url)
-
-  const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t)
-  const undoActivity = undoActivityData(undoUrl, byActor, announceActivity)
-
-  const followersException = [ byActor ]
-  return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException)
+  return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: createDislikeActivity, transaction: t })
 }
 
 async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) {
   logger.info('Creating job to undo cache file %s.', redundancyModel.url)
 
-  const undoUrl = getUndoActivityPubUrl(redundancyModel.url)
-
   const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.VideoFile.Video.id)
-  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t)
-
-  const audience = getVideoAudience(video, actorsInvolvedInVideo)
   const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject())
 
-  const undoActivity = undoActivityData(undoUrl, byActor, createActivity, audience)
-
-  return unicastTo(undoActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
+  return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t })
 }
 
 // ---------------------------------------------------------------------------
@@ -144,3 +109,19 @@ function undoActivityData (
     audience
   )
 }
+
+async function sendUndoVideoRelatedActivity (options: {
+  byActor: ActorModel,
+  video: VideoModel,
+  url: string,
+  activity: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce,
+  transaction: Transaction
+}) {
+  const activityBuilder = (audience: ActivityAudience) => {
+    const undoUrl = getUndoActivityPubUrl(options.url)
+
+    return undoActivityData(undoUrl, options.byActor, options.activity, audience)
+  }
+
+  return sendVideoRelatedActivity(activityBuilder, options)
+}

+ 8 - 8
server/lib/activitypub/send/send-update.ts

@@ -7,8 +7,8 @@ import { VideoModel } from '../../../models/video/video'
 import { VideoChannelModel } from '../../../models/video/video-channel'
 import { VideoShareModel } from '../../../models/video/video-share'
 import { getUpdateActivityPubUrl } from '../url'
-import { broadcastToFollowers, unicastTo } from './utils'
-import { audiencify, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience } from '../audience'
+import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils'
+import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience'
 import { logger } from '../../../helpers/logger'
 import { VideoCaptionModel } from '../../../models/video/video-caption'
 import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
@@ -61,16 +61,16 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod
 async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) {
   logger.info('Creating job to update cache file %s.', redundancyModel.url)
 
-  const url = getUpdateActivityPubUrl(redundancyModel.url, redundancyModel.updatedAt.toISOString())
   const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.VideoFile.Video.id)
 
-  const redundancyObject = redundancyModel.toActivityPubObject()
+  const activityBuilder = (audience: ActivityAudience) => {
+    const redundancyObject = redundancyModel.toActivityPubObject()
+    const url = getUpdateActivityPubUrl(redundancyModel.url, redundancyModel.updatedAt.toISOString())
 
-  const accountsInvolvedInVideo = await getActorsInvolvedInVideo(video, undefined)
-  const audience = getObjectFollowersAudience(accountsInvolvedInVideo)
+    return buildUpdateActivity(url, byActor, redundancyObject, audience)
+  }
 
-  const updateActivity = buildUpdateActivity(url, byActor, redundancyObject, audience)
-  return unicastTo(updateActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)
+  return sendVideoRelatedActivity(activityBuilder, { byActor, video })
 }
 
 // ---------------------------------------------------------------------------

+ 27 - 3
server/lib/activitypub/send/utils.ts

@@ -1,13 +1,36 @@
 import { Transaction } from 'sequelize'
-import { Activity } from '../../../../shared/models/activitypub'
+import { Activity, ActivityAudience } from '../../../../shared/models/activitypub'
 import { logger } from '../../../helpers/logger'
 import { ActorModel } from '../../../models/activitypub/actor'
 import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
 import { JobQueue } from '../../job-queue'
 import { VideoModel } from '../../../models/video/video'
-import { getActorsInvolvedInVideo } from '../audience'
+import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
 import { getServerActor } from '../../../helpers/utils'
 
+async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
+  byActor: ActorModel,
+  video: VideoModel,
+  transaction?: Transaction
+}) {
+  const actorsInvolvedInVideo = await getActorsInvolvedInVideo(options.video, options.transaction)
+
+  // Send to origin
+  if (options.video.isOwned() === false) {
+    const audience = getRemoteVideoAudience(options.video, actorsInvolvedInVideo)
+    const activity = activityBuilder(audience)
+
+    return unicastTo(activity, options.byActor, options.video.VideoChannel.Account.Actor.sharedInboxUrl)
+  }
+
+  // Send to followers
+  const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
+  const activity = activityBuilder(audience)
+
+  const actorsException = [ options.byActor ]
+  return broadcastToFollowers(activity, options.byActor, actorsInvolvedInVideo, options.transaction, actorsException)
+}
+
 async function forwardVideoRelatedActivity (
   activity: Activity,
   t: Transaction,
@@ -110,7 +133,8 @@ export {
   unicastTo,
   forwardActivity,
   broadcastToActors,
-  forwardVideoRelatedActivity
+  forwardVideoRelatedActivity,
+  sendVideoRelatedActivity
 }
 
 // ---------------------------------------------------------------------------

+ 0 - 3
server/tests/api/server/stats.ts

@@ -65,7 +65,6 @@ describe('Test stats (excluding redundancy)', function () {
     expect(data.totalVideos).to.equal(1)
     expect(data.totalInstanceFollowers).to.equal(2)
     expect(data.totalInstanceFollowing).to.equal(1)
-    expect(data.videosRedundancy).to.have.lengthOf(0)
   })
 
   it('Should have the correct stats on instance 2', async function () {
@@ -80,7 +79,6 @@ describe('Test stats (excluding redundancy)', function () {
     expect(data.totalVideos).to.equal(1)
     expect(data.totalInstanceFollowers).to.equal(1)
     expect(data.totalInstanceFollowing).to.equal(1)
-    expect(data.videosRedundancy).to.have.lengthOf(0)
   })
 
   it('Should have the correct stats on instance 3', async function () {
@@ -95,7 +93,6 @@ describe('Test stats (excluding redundancy)', function () {
     expect(data.totalVideos).to.equal(1)
     expect(data.totalInstanceFollowing).to.equal(1)
     expect(data.totalInstanceFollowers).to.equal(0)
-    expect(data.videosRedundancy).to.have.lengthOf(0)
   })
 
   after(async function () {