utils.ts 6.3 KB

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