Move models to typescript-sequelize
[oweals/peertube.git] / server / lib / activitypub / send / misc.ts
1 import { Transaction } from 'sequelize'
2 import { Activity } from '../../../../shared/models/activitypub'
3 import { logger } from '../../../helpers'
4 import { ACTIVITY_PUB } from '../../../initializers'
5 import { AccountModel } from '../../../models/account/account'
6 import { AccountFollowModel } from '../../../models/account/account-follow'
7 import { VideoModel } from '../../../models/video/video'
8 import { VideoChannelModel } from '../../../models/video/video-channel'
9 import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
10 import { VideoShareModel } from '../../../models/video/video-share'
11 import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/activitypub-http-job-scheduler'
12
13 async function forwardActivity (
14   activity: Activity,
15   t: Transaction,
16   followersException: AccountModel[] = []
17 ) {
18   const to = activity.to || []
19   const cc = activity.cc || []
20
21   const followersUrls: string[] = []
22   for (const dest of to.concat(cc)) {
23     if (dest.endsWith('/followers')) {
24       followersUrls.push(dest)
25     }
26   }
27
28   const toAccountFollowers = await AccountModel.listByFollowersUrls(followersUrls, t)
29   const uris = await computeFollowerUris(toAccountFollowers, followersException, t)
30
31   if (uris.length === 0) {
32     logger.info('0 followers for %s, no forwarding.', toAccountFollowers.map(a => a.id).join(', '))
33     return undefined
34   }
35
36   logger.debug('Creating forwarding job.', { uris })
37
38   const jobPayload: ActivityPubHttpPayload = {
39     uris,
40     body: activity
41   }
42
43   return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
44 }
45
46 async function broadcastToFollowers (
47   data: any,
48   byAccount: AccountModel,
49   toAccountFollowers: AccountModel[],
50   t: Transaction,
51   followersException: AccountModel[] = []
52 ) {
53   const uris = await computeFollowerUris(toAccountFollowers, followersException, t)
54   if (uris.length === 0) {
55     logger.info('0 followers for %s, no broadcasting.', toAccountFollowers.map(a => a.id).join(', '))
56     return undefined
57   }
58
59   logger.debug('Creating broadcast job.', { uris })
60
61   const jobPayload: ActivityPubHttpPayload = {
62     uris,
63     signatureAccountId: byAccount.id,
64     body: data
65   }
66
67   return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpBroadcastHandler', jobPayload)
68 }
69
70 async function unicastTo (data: any, byAccount: AccountModel, toAccountUrl: string, t: Transaction) {
71   logger.debug('Creating unicast job.', { uri: toAccountUrl })
72
73   const jobPayload: ActivityPubHttpPayload = {
74     uris: [ toAccountUrl ],
75     signatureAccountId: byAccount.id,
76     body: data
77   }
78
79   return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
80 }
81
82 function getOriginVideoAudience (video: VideoModel, accountsInvolvedInVideo: AccountModel[]) {
83   return {
84     to: [ video.VideoChannel.Account.url ],
85     cc: accountsInvolvedInVideo.map(a => a.followersUrl)
86   }
87 }
88
89 function getOriginVideoChannelAudience (videoChannel: VideoChannelModel, accountsInvolved: AccountModel[]) {
90   return {
91     to: [ videoChannel.Account.url ],
92     cc: accountsInvolved.map(a => a.followersUrl)
93   }
94 }
95
96 function getObjectFollowersAudience (accountsInvolvedInObject: AccountModel[]) {
97   return {
98     to: accountsInvolvedInObject.map(a => a.followersUrl),
99     cc: []
100   }
101 }
102
103 async function getAccountsInvolvedInVideo (video: VideoModel, t: Transaction) {
104   const accountsToForwardView = await VideoShareModel.loadAccountsByShare(video.id, t)
105   accountsToForwardView.push(video.VideoChannel.Account)
106
107   return accountsToForwardView
108 }
109
110 async function getAccountsInvolvedInVideoChannel (videoChannel: VideoChannelModel, t: Transaction) {
111   const accountsToForwardView = await VideoChannelShareModel.loadAccountsByShare(videoChannel.id, t)
112   accountsToForwardView.push(videoChannel.Account)
113
114   return accountsToForwardView
115 }
116
117 async function getAudience (accountSender: AccountModel, t: Transaction, isPublic = true) {
118   const followerInboxUrls = await accountSender.getFollowerSharedInboxUrls(t)
119
120   // Thanks Mastodon: https://github.com/tootsuite/mastodon/blob/master/app/lib/activitypub/tag_manager.rb#L47
121   let to = []
122   let cc = []
123
124   if (isPublic) {
125     to = [ ACTIVITY_PUB.PUBLIC ]
126     cc = followerInboxUrls
127   } else { // Unlisted
128     to = followerInboxUrls
129     cc = [ ACTIVITY_PUB.PUBLIC ]
130   }
131
132   return { to, cc }
133 }
134
135 async function computeFollowerUris (toAccountFollower: AccountModel[], followersException: AccountModel[], t: Transaction) {
136   const toAccountFollowerIds = toAccountFollower.map(a => a.id)
137
138   const result = await AccountFollowModel.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds, t)
139   const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl)
140   return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
141 }
142
143 // ---------------------------------------------------------------------------
144
145 export {
146   broadcastToFollowers,
147   getOriginVideoChannelAudience,
148   unicastTo,
149   getAudience,
150   getOriginVideoAudience,
151   getAccountsInvolvedInVideo,
152   getAccountsInvolvedInVideoChannel,
153   getObjectFollowersAudience,
154   forwardActivity
155 }