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