aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-24 13:41:10 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:53 +0100
commit63c93323ecdeaa4b6183d75dd3f13469e1ef3ebd (patch)
tree3ee4b351025d4b19f6e880754df44fa7605b683d /server/lib
parentd4f1e94c89336255537b0b82913591f00e716201 (diff)
downloadPeerTube-63c93323ecdeaa4b6183d75dd3f13469e1ef3ebd.tar.gz
PeerTube-63c93323ecdeaa4b6183d75dd3f13469e1ef3ebd.tar.zst
PeerTube-63c93323ecdeaa4b6183d75dd3f13469e1ef3ebd.zip
Correctly forward like/dislikes and undo
Diffstat (limited to 'server/lib')
-rw-r--r--server/lib/activitypub/process/process-add.ts2
-rw-r--r--server/lib/activitypub/process/process-create.ts42
-rw-r--r--server/lib/activitypub/process/process-like.ts25
-rw-r--r--server/lib/activitypub/process/process-undo.ts43
-rw-r--r--server/lib/activitypub/send/misc.ts83
-rw-r--r--server/lib/activitypub/send/send-create.ts26
-rw-r--r--server/lib/activitypub/send/send-like.ts14
-rw-r--r--server/lib/activitypub/send/send-undo.ts38
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts11
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts27
-rw-r--r--server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts10
11 files changed, 209 insertions, 112 deletions
diff --git a/server/lib/activitypub/process/process-add.ts b/server/lib/activitypub/process/process-add.ts
index 433e68eb6..98280b9f0 100644
--- a/server/lib/activitypub/process/process-add.ts
+++ b/server/lib/activitypub/process/process-add.ts
@@ -76,7 +76,7 @@ function addRemoteVideo (account: AccountInstance,
76 if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.') 76 if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
77 77
78 const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t) 78 const videoFromDatabase = await db.Video.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
79 if (videoFromDatabase) throw new Error('Video with this UUID/Url already exists.') 79 if (videoFromDatabase) return videoFromDatabase
80 80
81 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc) 81 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
82 const video = db.Video.build(videoData) 82 const video = db.Video.build(videoData)
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index 147bbd132..1f982598b 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,14 +1,14 @@
1import { ActivityCreate, VideoChannelObject } from '../../../../shared' 1import { ActivityCreate, VideoChannelObject } from '../../../../shared'
2import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object'
2import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object' 3import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects/video-abuse-object'
3import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object' 4import { ViewObject } from '../../../../shared/models/activitypub/objects/view-object'
4import { logger, retryTransactionWrapper } from '../../../helpers' 5import { logger, retryTransactionWrapper } from '../../../helpers'
5import { database as db } from '../../../initializers' 6import { database as db } from '../../../initializers'
6import { AccountInstance } from '../../../models/account/account-interface' 7import { AccountInstance } from '../../../models/account/account-interface'
7import { getOrCreateAccountAndServer } from '../account' 8import { getOrCreateAccountAndServer } from '../account'
8import { sendCreateDislikeToVideoFollowers, sendCreateViewToVideoFollowers } from '../send/send-create' 9import { forwardActivity } from '../send/misc'
9import { getVideoChannelActivityPubUrl } from '../url' 10import { getVideoChannelActivityPubUrl } from '../url'
10import { videoChannelActivityObjectToDBAttributes } from './misc' 11import { videoChannelActivityObjectToDBAttributes } from './misc'
11import { DislikeObject } from '../../../../shared/models/activitypub/objects/dislike-object'
12 12
13async function processCreateActivity (activity: ActivityCreate) { 13async function processCreateActivity (activity: ActivityCreate) {
14 const activityObject = activity.object 14 const activityObject = activity.object
@@ -16,9 +16,9 @@ async function processCreateActivity (activity: ActivityCreate) {
16 const account = await getOrCreateAccountAndServer(activity.actor) 16 const account = await getOrCreateAccountAndServer(activity.actor)
17 17
18 if (activityType === 'View') { 18 if (activityType === 'View') {
19 return processCreateView(activityObject as ViewObject) 19 return processCreateView(account, activity)
20 } else if (activityType === 'Dislike') { 20 } else if (activityType === 'Dislike') {
21 return processCreateDislike(account, activityObject as DislikeObject) 21 return processCreateDislike(account, activity)
22 } else if (activityType === 'VideoChannel') { 22 } else if (activityType === 'VideoChannel') {
23 return processCreateVideoChannel(account, activityObject as VideoChannelObject) 23 return processCreateVideoChannel(account, activityObject as VideoChannelObject)
24 } else if (activityType === 'Flag') { 24 } else if (activityType === 'Flag') {
@@ -37,19 +37,20 @@ export {
37 37
38// --------------------------------------------------------------------------- 38// ---------------------------------------------------------------------------
39 39
40async function processCreateDislike (byAccount: AccountInstance, dislike: DislikeObject) { 40async function processCreateDislike (byAccount: AccountInstance, activity: ActivityCreate) {
41 const options = { 41 const options = {
42 arguments: [ byAccount, dislike ], 42 arguments: [ byAccount, activity ],
43 errorMessage: 'Cannot dislike the video with many retries.' 43 errorMessage: 'Cannot dislike the video with many retries.'
44 } 44 }
45 45
46 return retryTransactionWrapper(createVideoDislike, options) 46 return retryTransactionWrapper(createVideoDislike, options)
47} 47}
48 48
49function createVideoDislike (byAccount: AccountInstance, dislike: DislikeObject) { 49function createVideoDislike (byAccount: AccountInstance, activity: ActivityCreate) {
50 return db.sequelize.transaction(async t => { 50 const dislike = activity.object as DislikeObject
51 const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object)
52 51
52 return db.sequelize.transaction(async t => {
53 const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t)
53 if (!video) throw new Error('Unknown video ' + dislike.object) 54 if (!video) throw new Error('Unknown video ' + dislike.object)
54 55
55 const rate = { 56 const rate = {
@@ -59,15 +60,22 @@ function createVideoDislike (byAccount: AccountInstance, dislike: DislikeObject)
59 } 60 }
60 const [ , created ] = await db.AccountVideoRate.findOrCreate({ 61 const [ , created ] = await db.AccountVideoRate.findOrCreate({
61 where: rate, 62 where: rate,
62 defaults: rate 63 defaults: rate,
64 transaction: t
63 }) 65 })
64 await video.increment('dislikes') 66 await video.increment('dislikes', { transaction: t })
65 67
66 if (video.isOwned() && created === true) await sendCreateDislikeToVideoFollowers(byAccount, video, undefined) 68 if (video.isOwned() && created === true) {
69 // Don't resend the activity to the sender
70 const exceptions = [ byAccount ]
71 await forwardActivity(activity, t, exceptions)
72 }
67 }) 73 })
68} 74}
69 75
70async function processCreateView (view: ViewObject) { 76async function processCreateView (byAccount: AccountInstance, activity: ActivityCreate) {
77 const view = activity.object as ViewObject
78
71 const video = await db.Video.loadByUrlAndPopulateAccount(view.object) 79 const video = await db.Video.loadByUrlAndPopulateAccount(view.object)
72 80
73 if (!video) throw new Error('Unknown video ' + view.object) 81 if (!video) throw new Error('Unknown video ' + view.object)
@@ -77,7 +85,11 @@ async function processCreateView (view: ViewObject) {
77 85
78 await video.increment('views') 86 await video.increment('views')
79 87
80 if (video.isOwned()) await sendCreateViewToVideoFollowers(account, video, undefined) 88 if (video.isOwned()) {
89 // Don't resend the activity to the sender
90 const exceptions = [ byAccount ]
91 await forwardActivity(activity, undefined, exceptions)
92 }
81} 93}
82 94
83function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) { 95function processCreateVideoChannel (account: AccountInstance, videoChannelToCreateData: VideoChannelObject) {
@@ -94,7 +106,7 @@ function addRemoteVideoChannel (account: AccountInstance, videoChannelToCreateDa
94 106
95 return db.sequelize.transaction(async t => { 107 return db.sequelize.transaction(async t => {
96 let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t) 108 let videoChannel = await db.VideoChannel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
97 if (videoChannel) throw new Error('Video channel with this URL/UUID already exists.') 109 if (videoChannel) return videoChannel
98 110
99 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account) 111 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
100 videoChannel = db.VideoChannel.build(videoChannelData) 112 videoChannel = db.VideoChannel.build(videoChannelData)
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index d77b30f24..0347f95be 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -1,14 +1,14 @@
1import { ActivityLike } from '../../../../shared/models/activitypub/activity' 1import { ActivityLike } from '../../../../shared/models/activitypub/activity'
2import { retryTransactionWrapper } from '../../../helpers/database-utils'
2import { database as db } from '../../../initializers' 3import { database as db } from '../../../initializers'
3import { AccountInstance } from '../../../models/account/account-interface' 4import { AccountInstance } from '../../../models/account/account-interface'
4import { getOrCreateAccountAndServer } from '../account' 5import { getOrCreateAccountAndServer } from '../account'
5import { sendLikeToVideoFollowers } from '../send/send-like' 6import { forwardActivity } from '../send/misc'
6import { retryTransactionWrapper } from '../../../helpers/database-utils'
7 7
8async function processLikeActivity (activity: ActivityLike) { 8async function processLikeActivity (activity: ActivityLike) {
9 const account = await getOrCreateAccountAndServer(activity.actor) 9 const account = await getOrCreateAccountAndServer(activity.actor)
10 10
11 return processLikeVideo(account, activity.object) 11 return processLikeVideo(account, activity)
12} 12}
13 13
14// --------------------------------------------------------------------------- 14// ---------------------------------------------------------------------------
@@ -19,16 +19,18 @@ export {
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
22async function processLikeVideo (byAccount: AccountInstance, videoUrl: string) { 22async function processLikeVideo (byAccount: AccountInstance, activity: ActivityLike) {
23 const options = { 23 const options = {
24 arguments: [ byAccount, videoUrl ], 24 arguments: [ byAccount, activity ],
25 errorMessage: 'Cannot like the video with many retries.' 25 errorMessage: 'Cannot like the video with many retries.'
26 } 26 }
27 27
28 return retryTransactionWrapper(createVideoLike, options) 28 return retryTransactionWrapper(createVideoLike, options)
29} 29}
30 30
31function createVideoLike (byAccount: AccountInstance, videoUrl: string) { 31function createVideoLike (byAccount: AccountInstance, activity: ActivityLike) {
32 const videoUrl = activity.object
33
32 return db.sequelize.transaction(async t => { 34 return db.sequelize.transaction(async t => {
33 const video = await db.Video.loadByUrlAndPopulateAccount(videoUrl) 35 const video = await db.Video.loadByUrlAndPopulateAccount(videoUrl)
34 36
@@ -41,10 +43,15 @@ function createVideoLike (byAccount: AccountInstance, videoUrl: string) {
41 } 43 }
42 const [ , created ] = await db.AccountVideoRate.findOrCreate({ 44 const [ , created ] = await db.AccountVideoRate.findOrCreate({
43 where: rate, 45 where: rate,
44 defaults: rate 46 defaults: rate,
47 transaction: t
45 }) 48 })
46 await video.increment('likes') 49 await video.increment('likes', { transaction: t })
47 50
48 if (video.isOwned() && created === true) await sendLikeToVideoFollowers(byAccount, video, undefined) 51 if (video.isOwned() && created === true) {
52 // Don't resend the activity to the sender
53 const exceptions = [ byAccount ]
54 await forwardActivity(activity, t, exceptions)
55 }
49 }) 56 })
50} 57}
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index 9fe066c01..cc221045f 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -3,16 +3,15 @@ import { DislikeObject } from '../../../../shared/models/activitypub/objects/dis
3import { retryTransactionWrapper } from '../../../helpers/database-utils' 3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { database as db } from '../../../initializers' 5import { database as db } from '../../../initializers'
6import { sendUndoDislikeToVideoFollowers } from '../index' 6import { forwardActivity } from '../send/misc'
7import { sendUndoLikeToVideoFollowers } from '../send/send-undo'
8 7
9async function processUndoActivity (activity: ActivityUndo) { 8async function processUndoActivity (activity: ActivityUndo) {
10 const activityToUndo = activity.object 9 const activityToUndo = activity.object
11 10
12 if (activityToUndo.type === 'Like') { 11 if (activityToUndo.type === 'Like') {
13 return processUndoLike(activity.actor, activityToUndo) 12 return processUndoLike(activity.actor, activity)
14 } else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') { 13 } else if (activityToUndo.type === 'Create' && activityToUndo.object.type === 'Dislike') {
15 return processUndoDislike(activity.actor, activityToUndo.object) 14 return processUndoDislike(activity.actor, activity)
16 } else if (activityToUndo.type === 'Follow') { 15 } else if (activityToUndo.type === 'Follow') {
17 return processUndoFollow(activity.actor, activityToUndo) 16 return processUndoFollow(activity.actor, activityToUndo)
18 } 17 }
@@ -30,57 +29,69 @@ export {
30 29
31// --------------------------------------------------------------------------- 30// ---------------------------------------------------------------------------
32 31
33function processUndoLike (actor: string, likeActivity: ActivityLike) { 32function processUndoLike (actor: string, activity: ActivityUndo) {
34 const options = { 33 const options = {
35 arguments: [ actor, likeActivity ], 34 arguments: [ actor, activity ],
36 errorMessage: 'Cannot undo like with many retries.' 35 errorMessage: 'Cannot undo like with many retries.'
37 } 36 }
38 37
39 return retryTransactionWrapper(undoLike, options) 38 return retryTransactionWrapper(undoLike, options)
40} 39}
41 40
42function undoLike (actor: string, likeActivity: ActivityLike) { 41function undoLike (actor: string, activity: ActivityUndo) {
42 const likeActivity = activity.object as ActivityLike
43
43 return db.sequelize.transaction(async t => { 44 return db.sequelize.transaction(async t => {
44 const byAccount = await db.Account.loadByUrl(actor, t) 45 const byAccount = await db.Account.loadByUrl(actor, t)
45 if (!byAccount) throw new Error('Unknown account ' + actor) 46 if (!byAccount) throw new Error('Unknown account ' + actor)
46 47
47 const video = await db.Video.loadByUrlAndPopulateAccount(likeActivity.object) 48 const video = await db.Video.loadByUrlAndPopulateAccount(likeActivity.object, t)
48 if (!video) throw new Error('Unknown video ' + likeActivity.actor) 49 if (!video) throw new Error('Unknown video ' + likeActivity.actor)
49 50
50 const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t) 51 const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t)
51 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`) 52 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`)
52 53
53 await rate.destroy({ transaction: t }) 54 await rate.destroy({ transaction: t })
54 await video.decrement('likes') 55 await video.decrement('likes', { transaction: t })
55 56
56 if (video.isOwned()) await sendUndoLikeToVideoFollowers(byAccount, video, t) 57 if (video.isOwned()) {
58 // Don't resend the activity to the sender
59 const exceptions = [ byAccount ]
60 await forwardActivity(activity, t, exceptions)
61 }
57 }) 62 })
58} 63}
59 64
60function processUndoDislike (actor: string, dislikeCreateActivity: DislikeObject) { 65function processUndoDislike (actor: string, activity: ActivityUndo) {
61 const options = { 66 const options = {
62 arguments: [ actor, dislikeCreateActivity ], 67 arguments: [ actor, activity ],
63 errorMessage: 'Cannot undo dislike with many retries.' 68 errorMessage: 'Cannot undo dislike with many retries.'
64 } 69 }
65 70
66 return retryTransactionWrapper(undoDislike, options) 71 return retryTransactionWrapper(undoDislike, options)
67} 72}
68 73
69function undoDislike (actor: string, dislike: DislikeObject) { 74function undoDislike (actor: string, activity: ActivityUndo) {
75 const dislike = activity.object.object as DislikeObject
76
70 return db.sequelize.transaction(async t => { 77 return db.sequelize.transaction(async t => {
71 const byAccount = await db.Account.loadByUrl(actor, t) 78 const byAccount = await db.Account.loadByUrl(actor, t)
72 if (!byAccount) throw new Error('Unknown account ' + actor) 79 if (!byAccount) throw new Error('Unknown account ' + actor)
73 80
74 const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object) 81 const video = await db.Video.loadByUrlAndPopulateAccount(dislike.object, t)
75 if (!video) throw new Error('Unknown video ' + dislike.actor) 82 if (!video) throw new Error('Unknown video ' + dislike.actor)
76 83
77 const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t) 84 const rate = await db.AccountVideoRate.load(byAccount.id, video.id, t)
78 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`) 85 if (!rate) throw new Error(`Unknown rate by account ${byAccount.id} for video ${video.id}.`)
79 86
80 await rate.destroy({ transaction: t }) 87 await rate.destroy({ transaction: t })
81 await video.decrement('dislikes') 88 await video.decrement('dislikes', { transaction: t })
82 89
83 if (video.isOwned()) await sendUndoDislikeToVideoFollowers(byAccount, video, t) 90 if (video.isOwned()) {
91 // Don't resend the activity to the sender
92 const exceptions = [ byAccount ]
93 await forwardActivity(activity, t, exceptions)
94 }
84 }) 95 })
85} 96}
86 97
diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts
index 41a039b19..fe137464e 100644
--- a/server/lib/activitypub/send/misc.ts
+++ b/server/lib/activitypub/send/misc.ts
@@ -2,8 +2,45 @@ import { Transaction } from 'sequelize'
2import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers/logger'
3import { ACTIVITY_PUB, database as db } from '../../../initializers' 3import { ACTIVITY_PUB, database as db } from '../../../initializers'
4import { AccountInstance } from '../../../models/account/account-interface' 4import { AccountInstance } from '../../../models/account/account-interface'
5import { activitypubHttpJobScheduler } from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler' 5import {
6 activitypubHttpJobScheduler,
7 ActivityPubHttpPayload
8} from '../../jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler'
6import { VideoInstance } from '../../../models/video/video-interface' 9import { VideoInstance } from '../../../models/video/video-interface'
10import { Activity } from '../../../../shared/models/activitypub/activity'
11
12async 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}
7 44
8async function broadcastToFollowers ( 45async function broadcastToFollowers (
9 data: any, 46 data: any,
@@ -12,18 +49,15 @@ async function broadcastToFollowers (
12 t: Transaction, 49 t: Transaction,
13 followersException: AccountInstance[] = [] 50 followersException: AccountInstance[] = []
14) { 51) {
15 const toAccountFollowerIds = toAccountFollowers.map(a => a.id) 52 const uris = await computeFollowerUris(toAccountFollowers, followersException)
16 53 if (uris.length === 0) {
17 const result = await db.AccountFollow.listAcceptedFollowerSharedInboxUrls(toAccountFollowerIds) 54 logger.info('0 followers for %s, no broadcasting.', toAccountFollowers.map(a => a.id).join(', '))
18 if (result.data.length === 0) { 55 return
19 logger.info('Not broadcast because of 0 followers for %s.', toAccountFollowerIds.join(', '))
20 return undefined
21 } 56 }
22 57
23 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) 58 logger.debug('Creating broadcast job.', { uris })
24 const uris = result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1)
25 59
26 const jobPayload = { 60 const jobPayload: ActivityPubHttpPayload = {
27 uris, 61 uris,
28 signatureAccountId: byAccount.id, 62 signatureAccountId: byAccount.id,
29 body: data 63 body: data
@@ -33,7 +67,9 @@ async function broadcastToFollowers (
33} 67}
34 68
35async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) { 69async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: string, t: Transaction) {
36 const jobPayload = { 70 logger.debug('Creating unicast job.', { uri: toAccountUrl })
71
72 const jobPayload: ActivityPubHttpPayload = {
37 uris: [ toAccountUrl ], 73 uris: [ toAccountUrl ],
38 signatureAccountId: byAccount.id, 74 signatureAccountId: byAccount.id,
39 body: data 75 body: data
@@ -42,21 +78,21 @@ async function unicastTo (data: any, byAccount: AccountInstance, toAccountUrl: s
42 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload) 78 return activitypubHttpJobScheduler.createJob(t, 'activitypubHttpUnicastHandler', jobPayload)
43} 79}
44 80
45function getOriginVideoAudience (video: VideoInstance) { 81function getOriginVideoAudience (video: VideoInstance, accountsInvolvedInVideo: AccountInstance[]) {
46 return { 82 return {
47 to: [ video.VideoChannel.Account.url ], 83 to: [ video.VideoChannel.Account.url ],
48 cc: [ video.VideoChannel.Account.url + '/followers' ] 84 cc: accountsInvolvedInVideo.map(a => a.followersUrl)
49 } 85 }
50} 86}
51 87
52function getVideoFollowersAudience (video: VideoInstance) { 88function getVideoFollowersAudience (accountsInvolvedInVideo: AccountInstance[]) {
53 return { 89 return {
54 to: [ video.VideoChannel.Account.url + '/followers' ], 90 to: accountsInvolvedInVideo.map(a => a.followersUrl),
55 cc: [] 91 cc: []
56 } 92 }
57} 93}
58 94
59async function getAccountsToForwardVideoAction (byAccount: AccountInstance, video: VideoInstance) { 95async function getAccountsInvolvedInVideo (video: VideoInstance) {
60 const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id) 96 const accountsToForwardView = await db.VideoShare.loadAccountsByShare(video.id)
61 accountsToForwardView.push(video.VideoChannel.Account) 97 accountsToForwardView.push(video.VideoChannel.Account)
62 98
@@ -81,6 +117,16 @@ async function getAudience (accountSender: AccountInstance, isPublic = true) {
81 return { to, cc } 117 return { to, cc }
82} 118}
83 119
120async 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
84// --------------------------------------------------------------------------- 130// ---------------------------------------------------------------------------
85 131
86export { 132export {
@@ -88,6 +134,7 @@ export {
88 unicastTo, 134 unicastTo,
89 getAudience, 135 getAudience,
90 getOriginVideoAudience, 136 getOriginVideoAudience,
91 getAccountsToForwardVideoAction, 137 getAccountsInvolvedInVideo,
92 getVideoFollowersAudience 138 getVideoFollowersAudience,
139 forwardActivity
93} 140}
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index 6afe67ee6..113d89233 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -1,12 +1,12 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityCreate } from '../../../../shared/models/activitypub/activity' 2import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub/activity'
3import { getServerAccount } from '../../../helpers/utils' 3import { getServerAccount } from '../../../helpers/utils'
4import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models' 4import { AccountInstance, VideoChannelInstance, VideoInstance } from '../../../models'
5import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface' 5import { VideoAbuseInstance } from '../../../models/video/video-abuse-interface'
6import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' 6import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
7import { 7import {
8 broadcastToFollowers, 8 broadcastToFollowers,
9 getAccountsToForwardVideoAction, 9 getAccountsInvolvedInVideo,
10 getAudience, 10 getAudience,
11 getOriginVideoAudience, 11 getOriginVideoAudience,
12 getVideoFollowersAudience, 12 getVideoFollowersAudience,
@@ -35,7 +35,8 @@ async function sendCreateViewToOrigin (byAccount: AccountInstance, video: VideoI
35 const url = getVideoViewActivityPubUrl(byAccount, video) 35 const url = getVideoViewActivityPubUrl(byAccount, video)
36 const viewActivity = createViewActivityData(byAccount, video) 36 const viewActivity = createViewActivityData(byAccount, video)
37 37
38 const audience = getOriginVideoAudience(video) 38 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
39 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
39 const data = await createActivityData(url, byAccount, viewActivity, audience) 40 const data = await createActivityData(url, byAccount, viewActivity, audience)
40 41
41 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 42 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
@@ -45,12 +46,12 @@ async function sendCreateViewToVideoFollowers (byAccount: AccountInstance, video
45 const url = getVideoViewActivityPubUrl(byAccount, video) 46 const url = getVideoViewActivityPubUrl(byAccount, video)
46 const viewActivity = createViewActivityData(byAccount, video) 47 const viewActivity = createViewActivityData(byAccount, video)
47 48
48 const audience = getVideoFollowersAudience(video) 49 const accountsToForwardView = await getAccountsInvolvedInVideo(video)
50 const audience = getVideoFollowersAudience(accountsToForwardView)
49 const data = await createActivityData(url, byAccount, viewActivity, audience) 51 const data = await createActivityData(url, byAccount, viewActivity, audience)
50 52
53 // Use the server account to send the view, because it could be an unregistered account
51 const serverAccount = await getServerAccount() 54 const serverAccount = await getServerAccount()
52 const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
53
54 const followersException = [ byAccount ] 55 const followersException = [ byAccount ]
55 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 56 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException)
56} 57}
@@ -59,7 +60,8 @@ async function sendCreateDislikeToOrigin (byAccount: AccountInstance, video: Vid
59 const url = getVideoDislikeActivityPubUrl(byAccount, video) 60 const url = getVideoDislikeActivityPubUrl(byAccount, video)
60 const dislikeActivity = createDislikeActivityData(byAccount, video) 61 const dislikeActivity = createDislikeActivityData(byAccount, video)
61 62
62 const audience = getOriginVideoAudience(video) 63 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
64 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
63 const data = await createActivityData(url, byAccount, dislikeActivity, audience) 65 const data = await createActivityData(url, byAccount, dislikeActivity, audience)
64 66
65 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 67 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
@@ -69,17 +71,15 @@ async function sendCreateDislikeToVideoFollowers (byAccount: AccountInstance, vi
69 const url = getVideoDislikeActivityPubUrl(byAccount, video) 71 const url = getVideoDislikeActivityPubUrl(byAccount, video)
70 const dislikeActivity = createDislikeActivityData(byAccount, video) 72 const dislikeActivity = createDislikeActivityData(byAccount, video)
71 73
72 const audience = getVideoFollowersAudience(video) 74 const accountsToForwardView = await getAccountsInvolvedInVideo(video)
75 const audience = getVideoFollowersAudience(accountsToForwardView)
73 const data = await createActivityData(url, byAccount, dislikeActivity, audience) 76 const data = await createActivityData(url, byAccount, dislikeActivity, audience)
74 77
75 const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
76 const serverAccount = await getServerAccount()
77
78 const followersException = [ byAccount ] 78 const followersException = [ byAccount ]
79 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 79 return broadcastToFollowers(data, byAccount, accountsToForwardView, t, followersException)
80} 80}
81 81
82async function createActivityData (url: string, byAccount: AccountInstance, object: any, audience?: { to: string[], cc: string[] }) { 82async function createActivityData (url: string, byAccount: AccountInstance, object: any, audience?: ActivityAudience) {
83 if (!audience) { 83 if (!audience) {
84 audience = await getAudience(byAccount) 84 audience = await getAudience(byAccount)
85 } 85 }
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index 70a7d886f..8ca775bf3 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -1,11 +1,10 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityLike } from '../../../../shared/models/activitypub/activity' 2import { ActivityLike } from '../../../../shared/models/activitypub/activity'
3import { getServerAccount } from '../../../helpers/utils'
4import { AccountInstance, VideoInstance } from '../../../models' 3import { AccountInstance, VideoInstance } from '../../../models'
5import { getVideoLikeActivityPubUrl } from '../url' 4import { getVideoLikeActivityPubUrl } from '../url'
6import { 5import {
7 broadcastToFollowers, 6 broadcastToFollowers,
8 getAccountsToForwardVideoAction, 7 getAccountsInvolvedInVideo,
9 getAudience, 8 getAudience,
10 getOriginVideoAudience, 9 getOriginVideoAudience,
11 getVideoFollowersAudience, 10 getVideoFollowersAudience,
@@ -15,7 +14,8 @@ import {
15async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 14async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
16 const url = getVideoLikeActivityPubUrl(byAccount, video) 15 const url = getVideoLikeActivityPubUrl(byAccount, video)
17 16
18 const audience = getOriginVideoAudience(video) 17 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
18 const audience = getOriginVideoAudience(video, accountsInvolvedInVideo)
19 const data = await likeActivityData(url, byAccount, video, audience) 19 const data = await likeActivityData(url, byAccount, video, audience)
20 20
21 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t) 21 return unicastTo(data, byAccount, video.VideoChannel.Account.sharedInboxUrl, t)
@@ -24,14 +24,14 @@ async function sendLikeToOrigin (byAccount: AccountInstance, video: VideoInstanc
24async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 24async function sendLikeToVideoFollowers (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
25 const url = getVideoLikeActivityPubUrl(byAccount, video) 25 const url = getVideoLikeActivityPubUrl(byAccount, video)
26 26
27 const audience = getVideoFollowersAudience(video) 27 const accountsInvolvedInVideo = await getAccountsInvolvedInVideo(video)
28 const audience = getVideoFollowersAudience(accountsInvolvedInVideo)
28 const data = await likeActivityData(url, byAccount, video, audience) 29 const data = await likeActivityData(url, byAccount, video, audience)
29 30
30 const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video) 31 const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
31 const serverAccount = await getServerAccount()
32 32
33 const followersException = [ byAccount ] 33 const followersException = [ byAccount ]
34 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 34 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
35} 35}
36 36
37async function likeActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, audience?: { to: string[], cc: string[] }) { 37async function likeActivityData (url: string, byAccount: AccountInstance, video: VideoInstance, audience?: { to: string[], cc: string[] }) {
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 8f46a051e..79fc113f0 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -1,11 +1,16 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActivityCreate, ActivityFollow, ActivityLike, ActivityUndo } from '../../../../shared/models/activitypub/activity' 2import {
3import { getServerAccount } from '../../../helpers/utils' 3 ActivityAudience,
4 ActivityCreate,
5 ActivityFollow,
6 ActivityLike,
7 ActivityUndo
8} from '../../../../shared/models/activitypub/activity'
4import { AccountInstance } from '../../../models' 9import { AccountInstance } from '../../../models'
5import { AccountFollowInstance } from '../../../models/account/account-follow-interface' 10import { AccountFollowInstance } from '../../../models/account/account-follow-interface'
6import { VideoInstance } from '../../../models/video/video-interface' 11import { VideoInstance } from '../../../models/video/video-interface'
7import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' 12import { getAccountFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url'
8import { broadcastToFollowers, getAccountsToForwardVideoAction, unicastTo } from './misc' 13import { broadcastToFollowers, getAccountsInvolvedInVideo, getAudience, getVideoFollowersAudience, unicastTo } from './misc'
9import { createActivityData, createDislikeActivityData } from './send-create' 14import { createActivityData, createDislikeActivityData } from './send-create'
10import { followActivityData } from './send-follow' 15import { followActivityData } from './send-follow'
11import { likeActivityData } from './send-like' 16import { likeActivityData } from './send-like'
@@ -37,14 +42,13 @@ async function sendUndoLikeToVideoFollowers (byAccount: AccountInstance, video:
37 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video) 42 const likeUrl = getVideoLikeActivityPubUrl(byAccount, video)
38 const undoUrl = getUndoActivityPubUrl(likeUrl) 43 const undoUrl = getUndoActivityPubUrl(likeUrl)
39 44
45 const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
46 const audience = getVideoFollowersAudience(toAccountsFollowers)
40 const object = await likeActivityData(likeUrl, byAccount, video) 47 const object = await likeActivityData(likeUrl, byAccount, video)
41 const data = await undoActivityData(undoUrl, byAccount, object) 48 const data = await undoActivityData(undoUrl, byAccount, object, audience)
42
43 const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video)
44 const serverAccount = await getServerAccount()
45 49
46 const followersException = [ byAccount ] 50 const followersException = [ byAccount ]
47 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 51 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
48} 52}
49 53
50async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) { 54async function sendUndoDislikeToOrigin (byAccount: AccountInstance, video: VideoInstance, t: Transaction) {
@@ -68,11 +72,10 @@ async function sendUndoDislikeToVideoFollowers (byAccount: AccountInstance, vide
68 72
69 const data = await undoActivityData(undoUrl, byAccount, object) 73 const data = await undoActivityData(undoUrl, byAccount, object)
70 74
71 const accountsToForwardView = await getAccountsToForwardVideoAction(byAccount, video) 75 const toAccountsFollowers = await getAccountsInvolvedInVideo(video)
72 const serverAccount = await getServerAccount()
73 76
74 const followersException = [ byAccount ] 77 const followersException = [ byAccount ]
75 return broadcastToFollowers(data, serverAccount, accountsToForwardView, t, followersException) 78 return broadcastToFollowers(data, byAccount, toAccountsFollowers, t, followersException)
76} 79}
77 80
78// --------------------------------------------------------------------------- 81// ---------------------------------------------------------------------------
@@ -87,11 +90,22 @@ export {
87 90
88// --------------------------------------------------------------------------- 91// ---------------------------------------------------------------------------
89 92
90async function undoActivityData (url: string, byAccount: AccountInstance, object: ActivityFollow | ActivityLike | ActivityCreate) { 93async function undoActivityData (
94 url: string,
95 byAccount: AccountInstance,
96 object: ActivityFollow | ActivityLike | ActivityCreate,
97 audience?: ActivityAudience
98) {
99 if (!audience) {
100 audience = await getAudience(byAccount)
101 }
102
91 const activity: ActivityUndo = { 103 const activity: ActivityUndo = {
92 type: 'Undo', 104 type: 'Undo',
93 id: url, 105 id: url,
94 actor: byAccount.url, 106 actor: byAccount.url,
107 to: audience.to,
108 cc: audience.cc,
95 object 109 object
96 } 110 }
97 111
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
index 5b4c65b81..49d4bf5c6 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-broadcast-handler.ts
@@ -1,21 +1,16 @@
1import { logger } from '../../../helpers' 1import { logger } from '../../../helpers'
2import { buildSignedActivity } from '../../../helpers/activitypub'
3import { doRequest } from '../../../helpers/requests' 2import { doRequest } from '../../../helpers/requests'
4import { database as db } from '../../../initializers' 3import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
5import { ActivityPubHttpPayload, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
6 4
7async function process (payload: ActivityPubHttpPayload, jobId: number) { 5async function process (payload: ActivityPubHttpPayload, jobId: number) {
8 logger.info('Processing ActivityPub broadcast in job %d.', jobId) 6 logger.info('Processing ActivityPub broadcast in job %d.', jobId)
9 7
10 const accountSignature = await db.Account.load(payload.signatureAccountId) 8 const body = await computeBody(payload)
11 if (!accountSignature) throw new Error('Unknown signature account id.')
12
13 const signedBody = await buildSignedActivity(accountSignature, payload.body)
14 9
15 const options = { 10 const options = {
16 method: 'POST', 11 method: 'POST',
17 uri: '', 12 uri: '',
18 json: signedBody 13 json: body
19 } 14 }
20 15
21 for (const uri of payload.uris) { 16 for (const uri of payload.uris) {
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
index ccf109935..f1fe774cc 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-job-scheduler.ts
@@ -1,11 +1,13 @@
1import { JobScheduler, JobHandler } from '../job-scheduler' 1import { JobCategory } from '../../../../shared'
2import { buildSignedActivity } from '../../../helpers/activitypub'
3import { logger } from '../../../helpers/logger'
4import { ACTIVITY_PUB } from '../../../initializers/constants'
5import { database as db } from '../../../initializers/database'
6import { JobHandler, JobScheduler } from '../job-scheduler'
2 7
3import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler' 8import * as activitypubHttpBroadcastHandler from './activitypub-http-broadcast-handler'
4import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
5import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler' 9import * as activitypubHttpFetcherHandler from './activitypub-http-fetcher-handler'
6import { JobCategory } from '../../../../shared' 10import * as activitypubHttpUnicastHandler from './activitypub-http-unicast-handler'
7import { ACTIVITY_PUB } from '../../../initializers/constants'
8import { logger } from '../../../helpers/logger'
9 11
10type ActivityPubHttpPayload = { 12type ActivityPubHttpPayload = {
11 uris: string[] 13 uris: string[]
@@ -40,8 +42,21 @@ function maybeRetryRequestLater (err: Error, payload: ActivityPubHttpPayload, ur
40 } 42 }
41} 43}
42 44
45async function computeBody (payload: ActivityPubHttpPayload) {
46 let body = payload.body
47
48 if (payload.signatureAccountId) {
49 const accountSignature = await db.Account.load(payload.signatureAccountId)
50 if (!accountSignature) throw new Error('Unknown signature account id.')
51 body = await buildSignedActivity(accountSignature, payload.body)
52 }
53
54 return body
55}
56
43export { 57export {
44 ActivityPubHttpPayload, 58 ActivityPubHttpPayload,
45 activitypubHttpJobScheduler, 59 activitypubHttpJobScheduler,
46 maybeRetryRequestLater 60 maybeRetryRequestLater,
61 computeBody
47} 62}
diff --git a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
index f7f3dabbd..4c95197c4 100644
--- a/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
+++ b/server/lib/jobs/activitypub-http-job-scheduler/activitypub-http-unicast-handler.ts
@@ -1,21 +1,17 @@
1import { logger } from '../../../helpers' 1import { logger } from '../../../helpers'
2import { doRequest } from '../../../helpers/requests' 2import { doRequest } from '../../../helpers/requests'
3import { ActivityPubHttpPayload, maybeRetryRequestLater } from './activitypub-http-job-scheduler' 3import { ActivityPubHttpPayload, computeBody, maybeRetryRequestLater } from './activitypub-http-job-scheduler'
4import { database as db } from '../../../initializers/database'
5import { buildSignedActivity } from '../../../helpers/activitypub'
6 4
7async function process (payload: ActivityPubHttpPayload, jobId: number) { 5async function process (payload: ActivityPubHttpPayload, jobId: number) {
8 logger.info('Processing ActivityPub unicast in job %d.', jobId) 6 logger.info('Processing ActivityPub unicast in job %d.', jobId)
9 7
10 const accountSignature = await db.Account.load(payload.signatureAccountId) 8 const body = await computeBody(payload)
11 if (!accountSignature) throw new Error('Unknown signature account id.')
12 9
13 const signedBody = await buildSignedActivity(accountSignature, payload.body)
14 const uri = payload.uris[0] 10 const uri = payload.uris[0]
15 const options = { 11 const options = {
16 method: 'POST', 12 method: 'POST',
17 uri, 13 uri,
18 json: signedBody 14 json: body
19 } 15 }
20 16
21 try { 17 try {