utils.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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 { VideoModel } from '../../../models/video/video'
  8. import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience'
  9. import { getServerActor } from '../../../helpers/utils'
  10. async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
  11. byActor: ActorModel,
  12. video: VideoModel,
  13. transaction?: Transaction
  14. }) {
  15. const actorsInvolvedInVideo = await getActorsInvolvedInVideo(options.video, options.transaction)
  16. // Send to origin
  17. if (options.video.isOwned() === false) {
  18. const audience = getRemoteVideoAudience(options.video, actorsInvolvedInVideo)
  19. const activity = activityBuilder(audience)
  20. return unicastTo(activity, options.byActor, options.video.VideoChannel.Account.Actor.sharedInboxUrl)
  21. }
  22. // Send to followers
  23. const audience = getAudienceFromFollowersOf(actorsInvolvedInVideo)
  24. const activity = activityBuilder(audience)
  25. const actorsException = [ options.byActor ]
  26. return broadcastToFollowers(activity, options.byActor, actorsInvolvedInVideo, options.transaction, actorsException)
  27. }
  28. async function forwardVideoRelatedActivity (
  29. activity: Activity,
  30. t: Transaction,
  31. followersException: ActorModel[] = [],
  32. video: VideoModel
  33. ) {
  34. // Mastodon does not add our announces in audience, so we forward to them manually
  35. const additionalActors = await getActorsInvolvedInVideo(video, t)
  36. const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)
  37. return forwardActivity(activity, t, followersException, additionalFollowerUrls)
  38. }
  39. async function forwardActivity (
  40. activity: Activity,
  41. t: Transaction,
  42. followersException: ActorModel[] = [],
  43. additionalFollowerUrls: string[] = []
  44. ) {
  45. logger.info('Forwarding activity %s.', activity.id)
  46. const to = activity.to || []
  47. const cc = activity.cc || []
  48. const followersUrls = additionalFollowerUrls
  49. for (const dest of to.concat(cc)) {
  50. if (dest.endsWith('/followers')) {
  51. followersUrls.push(dest)
  52. }
  53. }
  54. const toActorFollowers = await ActorModel.listByFollowersUrls(followersUrls, t)
  55. const uris = await computeFollowerUris(toActorFollowers, followersException, t)
  56. if (uris.length === 0) {
  57. logger.info('0 followers for %s, no forwarding.', toActorFollowers.map(a => a.id).join(', '))
  58. return undefined
  59. }
  60. logger.debug('Creating forwarding job.', { uris })
  61. const payload = {
  62. uris,
  63. body: activity
  64. }
  65. return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
  66. }
  67. async function broadcastToFollowers (
  68. data: any,
  69. byActor: ActorModel,
  70. toFollowersOf: ActorModel[],
  71. t: Transaction,
  72. actorsException: ActorModel[] = []
  73. ) {
  74. const uris = await computeFollowerUris(toFollowersOf, actorsException, t)
  75. return broadcastTo(uris, data, byActor)
  76. }
  77. async function broadcastToActors (
  78. data: any,
  79. byActor: ActorModel,
  80. toActors: ActorModel[],
  81. actorsException: ActorModel[] = []
  82. ) {
  83. const uris = await computeUris(toActors, actorsException)
  84. return broadcastTo(uris, data, byActor)
  85. }
  86. async function broadcastTo (uris: string[], data: any, byActor: ActorModel) {
  87. if (uris.length === 0) return undefined
  88. logger.debug('Creating broadcast job.', { uris })
  89. const payload = {
  90. uris,
  91. signatureActorId: byActor.id,
  92. body: data
  93. }
  94. return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })
  95. }
  96. async function unicastTo (data: any, byActor: ActorModel, toActorUrl: string) {
  97. logger.debug('Creating unicast job.', { uri: toActorUrl })
  98. const payload = {
  99. uri: toActorUrl,
  100. signatureActorId: byActor.id,
  101. body: data
  102. }
  103. return JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload })
  104. }
  105. // ---------------------------------------------------------------------------
  106. export {
  107. broadcastToFollowers,
  108. unicastTo,
  109. forwardActivity,
  110. broadcastToActors,
  111. forwardVideoRelatedActivity,
  112. sendVideoRelatedActivity
  113. }
  114. // ---------------------------------------------------------------------------
  115. async function computeFollowerUris (toFollowersOf: ActorModel[], actorsException: ActorModel[], t: Transaction) {
  116. const toActorFollowerIds = toFollowersOf.map(a => a.id)
  117. const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
  118. const sharedInboxesException = await buildSharedInboxesException(actorsException)
  119. return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
  120. }
  121. async function computeUris (toActors: ActorModel[], actorsException: ActorModel[] = []) {
  122. const serverActor = await getServerActor()
  123. const targetUrls = toActors
  124. .filter(a => a.id !== serverActor.id) // Don't send to ourselves
  125. .map(a => a.sharedInboxUrl || a.inboxUrl)
  126. const toActorSharedInboxesSet = new Set(targetUrls)
  127. const sharedInboxesException = await buildSharedInboxesException(actorsException)
  128. return Array.from(toActorSharedInboxesSet)
  129. .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
  130. }
  131. async function buildSharedInboxesException (actorsException: ActorModel[]) {
  132. const serverActor = await getServerActor()
  133. return actorsException
  134. .map(f => f.sharedInboxUrl || f.inboxUrl)
  135. .concat([ serverActor.sharedInboxUrl ])
  136. }