aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/activitypub
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2022-03-23 16:14:33 +0100
committerChocobozzz <me@florianbigard.com>2022-03-24 09:40:46 +0100
commita219c9100b3ce8774d454497d46be87465bf664e (patch)
treecaa869e47919a9e23cc86dcece1100e239683b8c /server/lib/activitypub
parent7e98a7df7d04e19ba67163a86c7b876d78d76839 (diff)
downloadPeerTube-a219c9100b3ce8774d454497d46be87465bf664e.tar.gz
PeerTube-a219c9100b3ce8774d454497d46be87465bf664e.tar.zst
PeerTube-a219c9100b3ce8774d454497d46be87465bf664e.zip
Refactor AP context builder
Diffstat (limited to 'server/lib/activitypub')
-rw-r--r--server/lib/activitypub/activity.ts12
-rw-r--r--server/lib/activitypub/audience.ts2
-rw-r--r--server/lib/activitypub/context.ts275
-rw-r--r--server/lib/activitypub/send/http.ts68
-rw-r--r--server/lib/activitypub/send/index.ts2
-rw-r--r--server/lib/activitypub/send/send-accept.ts7
-rw-r--r--server/lib/activitypub/send/send-announce.ts14
-rw-r--r--server/lib/activitypub/send/send-create.ts67
-rw-r--r--server/lib/activitypub/send/send-delete.ts67
-rw-r--r--server/lib/activitypub/send/send-dislike.ts4
-rw-r--r--server/lib/activitypub/send/send-flag.ts14
-rw-r--r--server/lib/activitypub/send/send-follow.ts4
-rw-r--r--server/lib/activitypub/send/send-like.ts4
-rw-r--r--server/lib/activitypub/send/send-reject.ts2
-rw-r--r--server/lib/activitypub/send/send-undo.ts50
-rw-r--r--server/lib/activitypub/send/send-update.ts44
-rw-r--r--server/lib/activitypub/send/shared/send-utils.ts104
17 files changed, 507 insertions, 233 deletions
diff --git a/server/lib/activitypub/activity.ts b/server/lib/activitypub/activity.ts
index 215b50b69..cccb7b1c1 100644
--- a/server/lib/activitypub/activity.ts
+++ b/server/lib/activitypub/activity.ts
@@ -1,14 +1,3 @@
1import { signJsonLDObject } from '@server/helpers/peertube-crypto'
2import { MActor } from '@server/types/models'
3import { ContextType } from '@shared/models'
4import { activityPubContextify } from './context'
5
6function buildSignedActivity <T> (byActor: MActor, data: T, contextType?: ContextType) {
7 const activity = activityPubContextify(data, contextType)
8
9 return signJsonLDObject(byActor, activity)
10}
11
12function getAPId (object: string | { id: string }) { 1function getAPId (object: string | { id: string }) {
13 if (typeof object === 'string') return object 2 if (typeof object === 'string') return object
14 3
@@ -16,6 +5,5 @@ function getAPId (object: string | { id: string }) {
16} 5}
17 6
18export { 7export {
19 buildSignedActivity,
20 getAPId 8 getAPId
21} 9}
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts
index 2bd5bb066..6f5491387 100644
--- a/server/lib/activitypub/audience.ts
+++ b/server/lib/activitypub/audience.ts
@@ -22,7 +22,7 @@ function buildAudience (followerUrls: string[], isPublic = true) {
22} 22}
23 23
24function audiencify<T> (object: T, audience: ActivityAudience) { 24function audiencify<T> (object: T, audience: ActivityAudience) {
25 return Object.assign(object, audience) 25 return { ...audience, ...object }
26} 26}
27 27
28// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/context.ts b/server/lib/activitypub/context.ts
index 71f08da80..3bc40e2aa 100644
--- a/server/lib/activitypub/context.ts
+++ b/server/lib/activitypub/context.ts
@@ -1,137 +1,168 @@
1import { ContextType } from '@shared/models' 1import { ContextType } from '@shared/models'
2 2
3function getContextData (type: ContextType) { 3function activityPubContextify <T> (data: T, type: ContextType) {
4 const context: any[] = [ 4 return { ...getContextData(type), ...data }
5 'https://www.w3.org/ns/activitystreams', 5}
6 'https://w3id.org/security/v1', 6
7 { 7// ---------------------------------------------------------------------------
8 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' 8
9export {
10 getContextData,
11 activityPubContextify
12}
13
14// ---------------------------------------------------------------------------
15
16type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string }) }
17
18const contextStore = {
19 Video: buildContext({
20 Hashtag: 'as:Hashtag',
21 uuid: 'sc:identifier',
22 category: 'sc:category',
23 licence: 'sc:license',
24 subtitleLanguage: 'sc:subtitleLanguage',
25 sensitive: 'as:sensitive',
26 language: 'sc:inLanguage',
27
28 // TODO: remove in a few versions, introduced in 4.2
29 icons: 'as:icon',
30
31 isLiveBroadcast: 'sc:isLiveBroadcast',
32 liveSaveReplay: {
33 '@type': 'sc:Boolean',
34 '@id': 'pt:liveSaveReplay'
35 },
36 permanentLive: {
37 '@type': 'sc:Boolean',
38 '@id': 'pt:permanentLive'
39 },
40 latencyMode: {
41 '@type': 'sc:Number',
42 '@id': 'pt:latencyMode'
43 },
44
45 Infohash: 'pt:Infohash',
46
47 originallyPublishedAt: 'sc:datePublished',
48 views: {
49 '@type': 'sc:Number',
50 '@id': 'pt:views'
51 },
52 state: {
53 '@type': 'sc:Number',
54 '@id': 'pt:state'
55 },
56 size: {
57 '@type': 'sc:Number',
58 '@id': 'pt:size'
59 },
60 fps: {
61 '@type': 'sc:Number',
62 '@id': 'pt:fps'
63 },
64 commentsEnabled: {
65 '@type': 'sc:Boolean',
66 '@id': 'pt:commentsEnabled'
67 },
68 downloadEnabled: {
69 '@type': 'sc:Boolean',
70 '@id': 'pt:downloadEnabled'
71 },
72 waitTranscoding: {
73 '@type': 'sc:Boolean',
74 '@id': 'pt:waitTranscoding'
75 },
76 support: {
77 '@type': 'sc:Text',
78 '@id': 'pt:support'
79 },
80 likes: {
81 '@id': 'as:likes',
82 '@type': '@id'
83 },
84 dislikes: {
85 '@id': 'as:dislikes',
86 '@type': '@id'
87 },
88 shares: {
89 '@id': 'as:shares',
90 '@type': '@id'
91 },
92 comments: {
93 '@id': 'as:comments',
94 '@type': '@id'
9 } 95 }
10 ] 96 }),
11 97
12 if (type !== 'View' && type !== 'Announce') { 98 Playlist: buildContext({
13 const additional = { 99 Playlist: 'pt:Playlist',
14 pt: 'https://joinpeertube.org/ns#', 100 PlaylistElement: 'pt:PlaylistElement',
15 sc: 'http://schema.org#' 101 position: {
102 '@type': 'sc:Number',
103 '@id': 'pt:position'
104 },
105 startTimestamp: {
106 '@type': 'sc:Number',
107 '@id': 'pt:startTimestamp'
108 },
109 stopTimestamp: {
110 '@type': 'sc:Number',
111 '@id': 'pt:stopTimestamp'
16 } 112 }
113 }),
114
115 CacheFile: buildContext({
116 expires: 'sc:expires',
117 CacheFile: 'pt:CacheFile'
118 }),
17 119
18 if (type === 'CacheFile') { 120 Flag: buildContext({
19 Object.assign(additional, { 121 Hashtag: 'as:Hashtag'
20 expires: 'sc:expires', 122 }),
21 CacheFile: 'pt:CacheFile' 123
22 }) 124 Actor: buildContext({
23 } else { 125 playlists: {
24 Object.assign(additional, { 126 '@id': 'pt:playlists',
25 Hashtag: 'as:Hashtag', 127 '@type': '@id'
26 uuid: 'sc:identifier',
27 category: 'sc:category',
28 licence: 'sc:license',
29 subtitleLanguage: 'sc:subtitleLanguage',
30 sensitive: 'as:sensitive',
31 language: 'sc:inLanguage',
32
33 // TODO: remove in a few versions, introduced in 4.2
34 icons: 'as:icon',
35
36 isLiveBroadcast: 'sc:isLiveBroadcast',
37 liveSaveReplay: {
38 '@type': 'sc:Boolean',
39 '@id': 'pt:liveSaveReplay'
40 },
41 permanentLive: {
42 '@type': 'sc:Boolean',
43 '@id': 'pt:permanentLive'
44 },
45 latencyMode: {
46 '@type': 'sc:Number',
47 '@id': 'pt:latencyMode'
48 },
49
50 Infohash: 'pt:Infohash',
51 Playlist: 'pt:Playlist',
52 PlaylistElement: 'pt:PlaylistElement',
53
54 originallyPublishedAt: 'sc:datePublished',
55 views: {
56 '@type': 'sc:Number',
57 '@id': 'pt:views'
58 },
59 state: {
60 '@type': 'sc:Number',
61 '@id': 'pt:state'
62 },
63 size: {
64 '@type': 'sc:Number',
65 '@id': 'pt:size'
66 },
67 fps: {
68 '@type': 'sc:Number',
69 '@id': 'pt:fps'
70 },
71 startTimestamp: {
72 '@type': 'sc:Number',
73 '@id': 'pt:startTimestamp'
74 },
75 stopTimestamp: {
76 '@type': 'sc:Number',
77 '@id': 'pt:stopTimestamp'
78 },
79 position: {
80 '@type': 'sc:Number',
81 '@id': 'pt:position'
82 },
83 commentsEnabled: {
84 '@type': 'sc:Boolean',
85 '@id': 'pt:commentsEnabled'
86 },
87 downloadEnabled: {
88 '@type': 'sc:Boolean',
89 '@id': 'pt:downloadEnabled'
90 },
91 waitTranscoding: {
92 '@type': 'sc:Boolean',
93 '@id': 'pt:waitTranscoding'
94 },
95 support: {
96 '@type': 'sc:Text',
97 '@id': 'pt:support'
98 },
99 likes: {
100 '@id': 'as:likes',
101 '@type': '@id'
102 },
103 dislikes: {
104 '@id': 'as:dislikes',
105 '@type': '@id'
106 },
107 playlists: {
108 '@id': 'pt:playlists',
109 '@type': '@id'
110 },
111 shares: {
112 '@id': 'as:shares',
113 '@type': '@id'
114 },
115 comments: {
116 '@id': 'as:comments',
117 '@type': '@id'
118 }
119 })
120 } 128 }
129 }),
121 130
122 context.push(additional) 131 Follow: buildContext(),
123 } 132 Reject: buildContext(),
133 Accept: buildContext(),
134 View: buildContext(),
135 Announce: buildContext(),
136 Comment: buildContext(),
137 Delete: buildContext(),
138 Rate: buildContext()
139}
124 140
141function getContextData (type: ContextType) {
125 return { 142 return {
126 '@context': context 143 '@context': contextStore[type]
127 } 144 }
128} 145}
129 146
130function activityPubContextify <T> (data: T, type: ContextType = 'All') { 147function buildContext (contextValue?: ContextValue) {
131 return Object.assign({}, data, getContextData(type)) 148 const baseContext = [
132} 149 'https://www.w3.org/ns/activitystreams',
150 'https://w3id.org/security/v1',
151 {
152 RsaSignature2017: 'https://w3id.org/security#RsaSignature2017'
153 }
154 ]
133 155
134export { 156 if (!contextValue) return baseContext
135 getContextData, 157
136 activityPubContextify 158 return [
159 ...baseContext,
160
161 {
162 pt: 'https://joinpeertube.org/ns#',
163 sc: 'http://schema.org#',
164
165 ...contextValue
166 }
167 ]
137} 168}
diff --git a/server/lib/activitypub/send/http.ts b/server/lib/activitypub/send/http.ts
new file mode 100644
index 000000000..d8d0b8542
--- /dev/null
+++ b/server/lib/activitypub/send/http.ts
@@ -0,0 +1,68 @@
1import { buildDigest, signJsonLDObject } from '@server/helpers/peertube-crypto'
2import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants'
3import { ActorModel } from '@server/models/actor/actor'
4import { getServerActor } from '@server/models/application/application'
5import { MActor } from '@server/types/models'
6import { ContextType } from '@shared/models/activitypub/context'
7import { activityPubContextify } from '../context'
8
9type Payload <T> = { body: T, contextType: ContextType, signatureActorId?: number }
10
11async function computeBody <T> (
12 payload: Payload<T>
13): Promise<T | T & { type: 'RsaSignature2017', creator: string, created: string }> {
14 let body = payload.body
15
16 if (payload.signatureActorId) {
17 const actorSignature = await ActorModel.load(payload.signatureActorId)
18 if (!actorSignature) throw new Error('Unknown signature actor id.')
19
20 body = await signAndContextify(actorSignature, payload.body, payload.contextType)
21 }
22
23 return body
24}
25
26async function buildSignedRequestOptions (payload: Payload<any>) {
27 let actor: MActor | null
28
29 if (payload.signatureActorId) {
30 actor = await ActorModel.load(payload.signatureActorId)
31 if (!actor) throw new Error('Unknown signature actor id.')
32 } else {
33 // We need to sign the request, so use the server
34 actor = await getServerActor()
35 }
36
37 const keyId = actor.url
38 return {
39 algorithm: HTTP_SIGNATURE.ALGORITHM,
40 authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME,
41 keyId,
42 key: actor.privateKey,
43 headers: HTTP_SIGNATURE.HEADERS_TO_SIGN
44 }
45}
46
47function buildGlobalHeaders (body: any) {
48 return {
49 'digest': buildDigest(body),
50 'content-type': 'application/activity+json',
51 'accept': ACTIVITY_PUB.ACCEPT_HEADER
52 }
53}
54
55function signAndContextify <T> (byActor: MActor, data: T, contextType: ContextType | null) {
56 const activity = contextType
57 ? activityPubContextify(data, contextType)
58 : data
59
60 return signJsonLDObject(byActor, activity)
61}
62
63export {
64 buildGlobalHeaders,
65 computeBody,
66 buildSignedRequestOptions,
67 signAndContextify
68}
diff --git a/server/lib/activitypub/send/index.ts b/server/lib/activitypub/send/index.ts
index 028936810..852ea2e74 100644
--- a/server/lib/activitypub/send/index.ts
+++ b/server/lib/activitypub/send/index.ts
@@ -1,4 +1,4 @@
1export * from './send-accept' 1export * from './http'
2export * from './send-accept' 2export * from './send-accept'
3export * from './send-announce' 3export * from './send-announce'
4export * from './send-create' 4export * from './send-create'
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts
index 939f06d9e..4c9bcbb0b 100644
--- a/server/lib/activitypub/send/send-accept.ts
+++ b/server/lib/activitypub/send/send-accept.ts
@@ -21,7 +21,12 @@ function sendAccept (actorFollow: MActorFollowActors) {
21 const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow) 21 const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow)
22 const data = buildAcceptActivity(url, me, followData) 22 const data = buildAcceptActivity(url, me, followData)
23 23
24 return unicastTo(data, me, follower.inboxUrl) 24 return unicastTo({
25 data,
26 byActor: me,
27 toActorUrl: follower.inboxUrl,
28 contextType: 'Accept'
29 })
25} 30}
26 31
27// --------------------------------------------------------------------------- 32// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts
index 7897beb75..6c078b047 100644
--- a/server/lib/activitypub/send/send-announce.ts
+++ b/server/lib/activitypub/send/send-announce.ts
@@ -23,13 +23,19 @@ async function buildAnnounceWithVideoAudience (
23 return { activity, actorsInvolvedInVideo } 23 return { activity, actorsInvolvedInVideo }
24} 24}
25 25
26async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { 26async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) {
27 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) 27 const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction)
28 28
29 logger.info('Creating job to send announce %s.', videoShare.url) 29 logger.info('Creating job to send announce %s.', videoShare.url)
30 30
31 const followersException = [ byActor ] 31 return broadcastToFollowers({
32 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce') 32 data: activity,
33 byActor,
34 toFollowersOf: actorsInvolvedInVideo,
35 transaction,
36 actorsException: [ byActor ],
37 contextType: 'Announce'
38 })
33} 39}
34 40
35function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { 41function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce {
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts
index f6d897220..5d8763495 100644
--- a/server/lib/activitypub/send/send-create.ts
+++ b/server/lib/activitypub/send/send-create.ts
@@ -25,7 +25,7 @@ import {
25 25
26const lTags = loggerTagsFactory('ap', 'create') 26const lTags = loggerTagsFactory('ap', 'create')
27 27
28async function sendCreateVideo (video: MVideoAP, t: Transaction) { 28async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
29 if (!video.hasPrivacyForFederation()) return undefined 29 if (!video.hasPrivacyForFederation()) return undefined
30 30
31 logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid)) 31 logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
@@ -36,7 +36,13 @@ async function sendCreateVideo (video: MVideoAP, t: Transaction) {
36 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) 36 const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC)
37 const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience) 37 const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience)
38 38
39 return broadcastToFollowers(createActivity, byActor, [ byActor ], t) 39 return broadcastToFollowers({
40 data: createActivity,
41 byActor,
42 toFollowersOf: [ byActor ],
43 transaction,
44 contextType: 'Video'
45 })
40} 46}
41 47
42async function sendCreateCacheFile ( 48async function sendCreateCacheFile (
@@ -55,7 +61,7 @@ async function sendCreateCacheFile (
55 }) 61 })
56} 62}
57 63
58async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) { 64async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
59 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 65 if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
60 66
61 logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid)) 67 logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
@@ -63,7 +69,7 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac
63 const byActor = playlist.OwnerAccount.Actor 69 const byActor = playlist.OwnerAccount.Actor
64 const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) 70 const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
65 71
66 const object = await playlist.toActivityPubObject(null, t) 72 const object = await playlist.toActivityPubObject(null, transaction)
67 const createActivity = buildCreateActivity(playlist.url, byActor, object, audience) 73 const createActivity = buildCreateActivity(playlist.url, byActor, object, audience)
68 74
69 const serverActor = await getServerActor() 75 const serverActor = await getServerActor()
@@ -71,19 +77,25 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac
71 77
72 if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor) 78 if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor)
73 79
74 return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) 80 return broadcastToFollowers({
81 data: createActivity,
82 byActor,
83 toFollowersOf,
84 transaction,
85 contextType: 'Playlist'
86 })
75} 87}
76 88
77async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) { 89async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
78 logger.info('Creating job to send comment %s.', comment.url) 90 logger.info('Creating job to send comment %s.', comment.url)
79 91
80 const isOrigin = comment.Video.isOwned() 92 const isOrigin = comment.Video.isOwned()
81 93
82 const byActor = comment.Account.Actor 94 const byActor = comment.Account.Actor
83 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) 95 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction)
84 const commentObject = comment.toActivityPubObject(threadParentComments) 96 const commentObject = comment.toActivityPubObject(threadParentComments)
85 97
86 const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t) 98 const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction)
87 // Add the actor that commented too 99 // Add the actor that commented too
88 actorsInvolvedInComment.push(byActor) 100 actorsInvolvedInComment.push(byActor)
89 101
@@ -101,16 +113,45 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transacti
101 113
102 // This was a reply, send it to the parent actors 114 // This was a reply, send it to the parent actors
103 const actorsException = [ byActor ] 115 const actorsException = [ byActor ]
104 await broadcastToActors(createActivity, byActor, parentsCommentActors, t, actorsException) 116 await broadcastToActors({
117 data: createActivity,
118 byActor,
119 toActors: parentsCommentActors,
120 transaction,
121 actorsException,
122 contextType: 'Comment'
123 })
105 124
106 // Broadcast to our followers 125 // Broadcast to our followers
107 await broadcastToFollowers(createActivity, byActor, [ byActor ], t) 126 await broadcastToFollowers({
127 data: createActivity,
128 byActor,
129 toFollowersOf: [ byActor ],
130 transaction,
131 contextType: 'Comment'
132 })
108 133
109 // Send to actors involved in the comment 134 // Send to actors involved in the comment
110 if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException) 135 if (isOrigin) {
136 return broadcastToFollowers({
137 data: createActivity,
138 byActor,
139 toFollowersOf: actorsInvolvedInComment,
140 transaction,
141 actorsException,
142 contextType: 'Comment'
143 })
144 }
111 145
112 // Send to origin 146 // Send to origin
113 t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox())) 147 return transaction.afterCommit(() => {
148 return unicastTo({
149 data: createActivity,
150 byActor,
151 toActorUrl: comment.Video.VideoChannel.Account.Actor.getSharedInbox(),
152 contextType: 'Comment'
153 })
154 })
114} 155}
115 156
116function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { 157function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate {
@@ -144,8 +185,8 @@ async function sendVideoRelatedCreateActivity (options: {
144 video: MVideoAccountLight 185 video: MVideoAccountLight
145 url: string 186 url: string
146 object: any 187 object: any
188 contextType: ContextType
147 transaction?: Transaction 189 transaction?: Transaction
148 contextType?: ContextType
149}) { 190}) {
150 const activityBuilder = (audience: ActivityAudience) => { 191 const activityBuilder = (audience: ActivityAudience) => {
151 return buildCreateActivity(options.url, options.byActor, options.object, audience) 192 return buildCreateActivity(options.url, options.byActor, options.object, audience)
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts
index 39216cdeb..0d85d9001 100644
--- a/server/lib/activitypub/send/send-delete.ts
+++ b/server/lib/activitypub/send/send-delete.ts
@@ -23,16 +23,16 @@ async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transact
23 return buildDeleteActivity(url, video.url, byActor, audience) 23 return buildDeleteActivity(url, video.url, byActor, audience)
24 } 24 }
25 25
26 return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction }) 26 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'Delete', transaction })
27} 27}
28 28
29async function sendDeleteActor (byActor: ActorModel, t: Transaction) { 29async function sendDeleteActor (byActor: ActorModel, transaction: Transaction) {
30 logger.info('Creating job to broadcast delete of actor %s.', byActor.url) 30 logger.info('Creating job to broadcast delete of actor %s.', byActor.url)
31 31
32 const url = getDeleteActivityPubUrl(byActor.url) 32 const url = getDeleteActivityPubUrl(byActor.url)
33 const activity = buildDeleteActivity(url, byActor.url, byActor) 33 const activity = buildDeleteActivity(url, byActor.url, byActor)
34 34
35 const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) 35 const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction)
36 36
37 // In case the actor did not have any videos 37 // In case the actor did not have any videos
38 const serverActor = await getServerActor() 38 const serverActor = await getServerActor()
@@ -40,10 +40,16 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) {
40 40
41 actorsInvolved.push(byActor) 41 actorsInvolved.push(byActor)
42 42
43 return broadcastToFollowers(activity, byActor, actorsInvolved, t) 43 return broadcastToFollowers({
44 data: activity,
45 byActor,
46 toFollowersOf: actorsInvolved,
47 contextType: 'Delete',
48 transaction
49 })
44} 50}
45 51
46async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Transaction) { 52async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, transaction: Transaction) {
47 logger.info('Creating job to send delete of comment %s.', videoComment.url) 53 logger.info('Creating job to send delete of comment %s.', videoComment.url)
48 54
49 const isVideoOrigin = videoComment.Video.isOwned() 55 const isVideoOrigin = videoComment.Video.isOwned()
@@ -53,10 +59,10 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran
53 ? videoComment.Account.Actor 59 ? videoComment.Account.Actor
54 : videoComment.Video.VideoChannel.Account.Actor 60 : videoComment.Video.VideoChannel.Account.Actor
55 61
56 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t) 62 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, transaction)
57 const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted()) 63 const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted())
58 64
59 const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t) 65 const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, transaction)
60 actorsInvolvedInComment.push(byActor) // Add the actor that commented the video 66 actorsInvolvedInComment.push(byActor) // Add the actor that commented the video
61 67
62 const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin) 68 const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin)
@@ -64,19 +70,48 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran
64 70
65 // This was a reply, send it to the parent actors 71 // This was a reply, send it to the parent actors
66 const actorsException = [ byActor ] 72 const actorsException = [ byActor ]
67 await broadcastToActors(activity, byActor, threadParentCommentsFiltered.map(c => c.Account.Actor), t, actorsException) 73 await broadcastToActors({
74 data: activity,
75 byActor,
76 toActors: threadParentCommentsFiltered.map(c => c.Account.Actor),
77 transaction,
78 contextType: 'Delete',
79 actorsException
80 })
68 81
69 // Broadcast to our followers 82 // Broadcast to our followers
70 await broadcastToFollowers(activity, byActor, [ byActor ], t) 83 await broadcastToFollowers({
84 data: activity,
85 byActor,
86 toFollowersOf: [ byActor ],
87 contextType: 'Delete',
88 transaction
89 })
71 90
72 // Send to actors involved in the comment 91 // Send to actors involved in the comment
73 if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException) 92 if (isVideoOrigin) {
93 return broadcastToFollowers({
94 data: activity,
95 byActor,
96 toFollowersOf: actorsInvolvedInComment,
97 transaction,
98 contextType: 'Delete',
99 actorsException
100 })
101 }
74 102
75 // Send to origin 103 // Send to origin
76 t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.getSharedInbox())) 104 return transaction.afterCommit(() => {
105 return unicastTo({
106 data: activity,
107 byActor,
108 toActorUrl: videoComment.Video.VideoChannel.Account.Actor.getSharedInbox(),
109 contextType: 'Delete'
110 })
111 })
77} 112}
78 113
79async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) { 114async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, transaction: Transaction) {
80 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) 115 logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url)
81 116
82 const byActor = videoPlaylist.OwnerAccount.Actor 117 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -89,7 +124,13 @@ async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary
89 124
90 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) 125 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor)
91 126
92 return broadcastToFollowers(activity, byActor, toFollowersOf, t) 127 return broadcastToFollowers({
128 data: activity,
129 byActor,
130 toFollowersOf,
131 contextType: 'Delete',
132 transaction
133 })
93} 134}
94 135
95// --------------------------------------------------------------------------- 136// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts
index ecb11e9bf..959e74823 100644
--- a/server/lib/activitypub/send/send-dislike.ts
+++ b/server/lib/activitypub/send/send-dislike.ts
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
6import { getVideoDislikeActivityPubUrlByLocalActor } from '../url' 6import { getVideoDislikeActivityPubUrlByLocalActor } from '../url'
7import { sendVideoActivityToOrigin } from './shared/send-utils' 7import { sendVideoActivityToOrigin } from './shared/send-utils'
8 8
9function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendDislike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) {
10 logger.info('Creating job to dislike %s.', video.url) 10 logger.info('Creating job to dislike %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -15,7 +15,7 @@ function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction
15 return buildDislikeActivity(url, byActor, video, audience) 15 return buildDislikeActivity(url, byActor, video, audience)
16 } 16 }
17 17
18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' })
19} 19}
20 20
21function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike { 21function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike {
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts
index 6df4e7eb8..138eb5adc 100644
--- a/server/lib/activitypub/send/send-flag.ts
+++ b/server/lib/activitypub/send/send-flag.ts
@@ -17,16 +17,20 @@ function sendAbuse (byActor: MActor, abuse: MAbuseAP, flaggedAccount: MAccountLi
17 const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } 17 const audience = { to: [ flaggedAccount.Actor.url ], cc: [] }
18 const flagActivity = buildFlagActivity(url, byActor, abuse, audience) 18 const flagActivity = buildFlagActivity(url, byActor, abuse, audience)
19 19
20 t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox())) 20 return t.afterCommit(() => {
21 return unicastTo({
22 data: flagActivity,
23 byActor,
24 toActorUrl: flaggedAccount.Actor.getSharedInbox(),
25 contextType: 'Flag'
26 })
27 })
21} 28}
22 29
23function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { 30function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag {
24 if (!audience) audience = getAudience(byActor) 31 if (!audience) audience = getAudience(byActor)
25 32
26 const activity = Object.assign( 33 const activity = { id: url, actor: byActor.url, ...abuse.toActivityPubObject() }
27 { id: url, actor: byActor.url },
28 abuse.toActivityPubObject()
29 )
30 34
31 return audiencify(activity, audience) 35 return audiencify(activity, audience)
32} 36}
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts
index aeeb50a2a..57501dadb 100644
--- a/server/lib/activitypub/send/send-follow.ts
+++ b/server/lib/activitypub/send/send-follow.ts
@@ -15,7 +15,9 @@ function sendFollow (actorFollow: MActorFollowActors, t: Transaction) {
15 15
16 const data = buildFollowActivity(actorFollow.url, me, following) 16 const data = buildFollowActivity(actorFollow.url, me, following)
17 17
18 t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) 18 return t.afterCommit(() => {
19 return unicastTo({ data, byActor: me, toActorUrl: following.inboxUrl, contextType: 'Follow' })
20 })
19} 21}
20 22
21function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow { 23function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow {
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts
index a5fe95e0a..46c9fdec9 100644
--- a/server/lib/activitypub/send/send-like.ts
+++ b/server/lib/activitypub/send/send-like.ts
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience'
6import { getVideoLikeActivityPubUrlByLocalActor } from '../url' 6import { getVideoLikeActivityPubUrlByLocalActor } from '../url'
7import { sendVideoActivityToOrigin } from './shared/send-utils' 7import { sendVideoActivityToOrigin } from './shared/send-utils'
8 8
9function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 9function sendLike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) {
10 logger.info('Creating job to like %s.', video.url) 10 logger.info('Creating job to like %s.', video.url)
11 11
12 const activityBuilder = (audience: ActivityAudience) => { 12 const activityBuilder = (audience: ActivityAudience) => {
@@ -15,7 +15,7 @@ function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
15 return buildLikeActivity(url, byActor, video, audience) 15 return buildLikeActivity(url, byActor, video, audience)
16 } 16 }
17 17
18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t }) 18 return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' })
19} 19}
20 20
21function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike { 21function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike {
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts
index 01b8f743b..83d8dfba7 100644
--- a/server/lib/activitypub/send/send-reject.ts
+++ b/server/lib/activitypub/send/send-reject.ts
@@ -18,7 +18,7 @@ function sendReject (followUrl: string, follower: MActor, following: MActor) {
18 const url = getLocalActorFollowRejectActivityPubUrl(follower, following) 18 const url = getLocalActorFollowRejectActivityPubUrl(follower, following)
19 const data = buildRejectActivity(url, following, followData) 19 const data = buildRejectActivity(url, following, followData)
20 20
21 return unicastTo(data, following, follower.inboxUrl) 21 return unicastTo({ data, byActor: following, toActorUrl: follower.inboxUrl, contextType: 'Reject' })
22} 22}
23 23
24// --------------------------------------------------------------------------- 24// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts
index 948ca0d7a..36d7ef991 100644
--- a/server/lib/activitypub/send/send-undo.ts
+++ b/server/lib/activitypub/send/send-undo.ts
@@ -6,7 +6,8 @@ import {
6 ActivityDislike, 6 ActivityDislike,
7 ActivityFollow, 7 ActivityFollow,
8 ActivityLike, 8 ActivityLike,
9 ActivityUndo 9 ActivityUndo,
10 ContextType
10} from '@shared/models' 11} from '@shared/models'
11import { logger } from '../../../helpers/logger' 12import { logger } from '../../../helpers/logger'
12import { VideoModel } from '../../../models/video/video' 13import { VideoModel } from '../../../models/video/video'
@@ -43,24 +44,37 @@ function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) {
43 const followActivity = buildFollowActivity(actorFollow.url, me, following) 44 const followActivity = buildFollowActivity(actorFollow.url, me, following)
44 const undoActivity = undoActivityData(undoUrl, me, followActivity) 45 const undoActivity = undoActivityData(undoUrl, me, followActivity)
45 46
46 t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) 47 return t.afterCommit(() => {
48 return unicastTo({
49 data: undoActivity,
50 byActor: me,
51 toActorUrl: following.inboxUrl,
52 contextType: 'Follow'
53 })
54 })
47} 55}
48 56
49// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
50 58
51async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { 59async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) {
52 logger.info('Creating job to undo announce %s.', videoShare.url) 60 logger.info('Creating job to undo announce %s.', videoShare.url)
53 61
54 const undoUrl = getUndoActivityPubUrl(videoShare.url) 62 const undoUrl = getUndoActivityPubUrl(videoShare.url)
55 63
56 const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) 64 const { activity: announce, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction)
57 const undoActivity = undoActivityData(undoUrl, byActor, announceActivity) 65 const undoActivity = undoActivityData(undoUrl, byActor, announce)
58 66
59 const followersException = [ byActor ] 67 return broadcastToFollowers({
60 return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) 68 data: undoActivity,
69 byActor,
70 toFollowersOf: actorsInvolvedInVideo,
71 transaction,
72 actorsException: [ byActor ],
73 contextType: 'Announce'
74 })
61} 75}
62 76
63async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) { 77async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, transaction: Transaction) {
64 logger.info('Creating job to undo cache file %s.', redundancyModel.url) 78 logger.info('Creating job to undo cache file %s.', redundancyModel.url)
65 79
66 const associatedVideo = redundancyModel.getVideo() 80 const associatedVideo = redundancyModel.getVideo()
@@ -72,7 +86,14 @@ async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedund
72 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(associatedVideo.id) 86 const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(associatedVideo.id)
73 const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject()) 87 const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject())
74 88
75 return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t }) 89 return sendUndoVideoRelatedActivity({
90 byActor,
91 video,
92 url: redundancyModel.url,
93 activity: createActivity,
94 contextType: 'CacheFile',
95 transaction
96 })
76} 97}
77 98
78// --------------------------------------------------------------------------- 99// ---------------------------------------------------------------------------
@@ -83,7 +104,7 @@ async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Tran
83 const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video) 104 const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video)
84 const likeActivity = buildLikeActivity(likeUrl, byActor, video) 105 const likeActivity = buildLikeActivity(likeUrl, byActor, video)
85 106
86 return sendUndoVideoToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) 107 return sendUndoVideoRateToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t })
87} 108}
88 109
89async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { 110async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) {
@@ -92,7 +113,7 @@ async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: T
92 const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video) 113 const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video)
93 const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video) 114 const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video)
94 115
95 return sendUndoVideoToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) 116 return sendUndoVideoRateToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t })
96} 117}
97 118
98// --------------------------------------------------------------------------- 119// ---------------------------------------------------------------------------
@@ -131,6 +152,7 @@ async function sendUndoVideoRelatedActivity (options: {
131 video: MVideoAccountLight 152 video: MVideoAccountLight
132 url: string 153 url: string
133 activity: ActivityFollow | ActivityCreate | ActivityAnnounce 154 activity: ActivityFollow | ActivityCreate | ActivityAnnounce
155 contextType: ContextType
134 transaction: Transaction 156 transaction: Transaction
135}) { 157}) {
136 const activityBuilder = (audience: ActivityAudience) => { 158 const activityBuilder = (audience: ActivityAudience) => {
@@ -142,7 +164,7 @@ async function sendUndoVideoRelatedActivity (options: {
142 return sendVideoRelatedActivity(activityBuilder, options) 164 return sendVideoRelatedActivity(activityBuilder, options)
143} 165}
144 166
145async function sendUndoVideoToOriginActivity (options: { 167async function sendUndoVideoRateToOriginActivity (options: {
146 byActor: MActor 168 byActor: MActor
147 video: MVideoAccountLight 169 video: MVideoAccountLight
148 url: string 170 url: string
@@ -155,5 +177,5 @@ async function sendUndoVideoToOriginActivity (options: {
155 return undoActivityData(undoUrl, options.byActor, options.activity, audience) 177 return undoActivityData(undoUrl, options.byActor, options.activity, audience)
156 } 178 }
157 179
158 return sendVideoActivityToOrigin(activityBuilder, options) 180 return sendVideoActivityToOrigin(activityBuilder, { ...options, contextType: 'Rate' })
159} 181}
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts
index 7c9e72cbc..3577ece02 100644
--- a/server/lib/activitypub/send/send-update.ts
+++ b/server/lib/activitypub/send/send-update.ts
@@ -20,20 +20,20 @@ import { getUpdateActivityPubUrl } from '../url'
20import { getActorsInvolvedInVideo } from './shared' 20import { getActorsInvolvedInVideo } from './shared'
21import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils' 21import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils'
22 22
23async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { 23async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, transaction: Transaction, overriddenByActor?: MActor) {
24 const video = videoArg as MVideoAP 24 const video = videoArg as MVideoAP
25 25
26 if (!video.hasPrivacyForFederation()) return undefined 26 if (!video.hasPrivacyForFederation()) return undefined
27 27
28 logger.info('Creating job to update video %s.', video.url) 28 logger.info('Creating job to update video %s.', video.url)
29 29
30 const byActor = overrodeByActor || video.VideoChannel.Account.Actor 30 const byActor = overriddenByActor || video.VideoChannel.Account.Actor
31 31
32 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) 32 const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString())
33 33
34 // Needed to build the AP object 34 // Needed to build the AP object
35 if (!video.VideoCaptions) { 35 if (!video.VideoCaptions) {
36 video.VideoCaptions = await video.$get('VideoCaptions', { transaction: t }) 36 video.VideoCaptions = await video.$get('VideoCaptions', { transaction })
37 } 37 }
38 38
39 const videoObject = video.toActivityPubObject() 39 const videoObject = video.toActivityPubObject()
@@ -41,13 +41,19 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction
41 41
42 const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience) 42 const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience)
43 43
44 const actorsInvolved = await getActorsInvolvedInVideo(video, t) 44 const actorsInvolved = await getActorsInvolvedInVideo(video, transaction)
45 if (overrodeByActor) actorsInvolved.push(overrodeByActor) 45 if (overriddenByActor) actorsInvolved.push(overriddenByActor)
46 46
47 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 47 return broadcastToFollowers({
48 data: updateActivity,
49 byActor,
50 toFollowersOf: actorsInvolved,
51 contextType: 'Video',
52 transaction
53 })
48} 54}
49 55
50async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) { 56async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
51 const byActor = accountOrChannel.Actor 57 const byActor = accountOrChannel.Actor
52 58
53 logger.info('Creating job to update actor %s.', byActor.url) 59 logger.info('Creating job to update actor %s.', byActor.url)
@@ -60,15 +66,21 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa
60 let actorsInvolved: MActor[] 66 let actorsInvolved: MActor[]
61 if (accountOrChannel instanceof AccountModel) { 67 if (accountOrChannel instanceof AccountModel) {
62 // Actors that shared my videos are involved too 68 // Actors that shared my videos are involved too
63 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) 69 actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction)
64 } else { 70 } else {
65 // Actors that shared videos of my channel are involved too 71 // Actors that shared videos of my channel are involved too
66 actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t) 72 actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, transaction)
67 } 73 }
68 74
69 actorsInvolved.push(byActor) 75 actorsInvolved.push(byActor)
70 76
71 return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) 77 return broadcastToFollowers({
78 data: updateActivity,
79 byActor,
80 toFollowersOf: actorsInvolved,
81 transaction,
82 contextType: 'Actor'
83 })
72} 84}
73 85
74async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { 86async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
@@ -92,7 +104,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
92 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) 104 return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
93} 105}
94 106
95async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { 107async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
96 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 108 if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
97 109
98 const byActor = videoPlaylist.OwnerAccount.Actor 110 const byActor = videoPlaylist.OwnerAccount.Actor
@@ -101,7 +113,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr
101 113
102 const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString()) 114 const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString())
103 115
104 const object = await videoPlaylist.toActivityPubObject(null, t) 116 const object = await videoPlaylist.toActivityPubObject(null, transaction)
105 const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC) 117 const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC)
106 118
107 const updateActivity = buildUpdateActivity(url, byActor, object, audience) 119 const updateActivity = buildUpdateActivity(url, byActor, object, audience)
@@ -111,7 +123,13 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr
111 123
112 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) 124 if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor)
113 125
114 return broadcastToFollowers(updateActivity, byActor, toFollowersOf, t) 126 return broadcastToFollowers({
127 data: updateActivity,
128 byActor,
129 toFollowersOf,
130 transaction,
131 contextType: 'Playlist'
132 })
115} 133}
116 134
117// --------------------------------------------------------------------------- 135// ---------------------------------------------------------------------------
diff --git a/server/lib/activitypub/send/shared/send-utils.ts b/server/lib/activitypub/send/shared/send-utils.ts
index 9e8f12fa8..dbcde91ee 100644
--- a/server/lib/activitypub/send/shared/send-utils.ts
+++ b/server/lib/activitypub/send/shared/send-utils.ts
@@ -1,7 +1,7 @@
1import { Transaction } from 'sequelize' 1import { Transaction } from 'sequelize'
2import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache' 2import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache'
3import { getServerActor } from '@server/models/application/application' 3import { getServerActor } from '@server/models/application/application'
4import { Activity, ActivityAudience } from '@shared/models' 4import { Activity, ActivityAudience, ActivitypubHttpBroadcastPayload } from '@shared/models'
5import { ContextType } from '@shared/models/activitypub/context' 5import { ContextType } from '@shared/models/activitypub/context'
6import { afterCommitIfTransaction } from '../../../../helpers/database-utils' 6import { afterCommitIfTransaction } from '../../../../helpers/database-utils'
7import { logger } from '../../../../helpers/logger' 7import { logger } from '../../../../helpers/logger'
@@ -14,8 +14,8 @@ import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAud
14async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { 14async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: {
15 byActor: MActorLight 15 byActor: MActorLight
16 video: MVideoImmutable | MVideoAccountLight 16 video: MVideoImmutable | MVideoAccountLight
17 contextType: ContextType
17 transaction?: Transaction 18 transaction?: Transaction
18 contextType?: ContextType
19}) { 19}) {
20 const { byActor, video, transaction, contextType } = options 20 const { byActor, video, transaction, contextType } = options
21 21
@@ -32,15 +32,23 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud
32 32
33 const actorsException = [ byActor ] 33 const actorsException = [ byActor ]
34 34
35 return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType) 35 return broadcastToFollowers({
36 data: activity,
37 byActor,
38 toFollowersOf: actorsInvolvedInVideo,
39 transaction,
40 actorsException,
41 contextType
42 })
36} 43}
37 44
38async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: { 45async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: {
39 byActor: MActorLight 46 byActor: MActorLight
40 video: MVideoImmutable | MVideoAccountLight 47 video: MVideoImmutable | MVideoAccountLight
48 contextType: ContextType
49
41 actorsInvolvedInVideo?: MActorLight[] 50 actorsInvolvedInVideo?: MActorLight[]
42 transaction?: Transaction 51 transaction?: Transaction
43 contextType?: ContextType
44}) { 52}) {
45 const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options 53 const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options
46 54
@@ -53,7 +61,12 @@ async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAu
53 const activity = activityBuilder(audience) 61 const activity = activityBuilder(audience)
54 62
55 return afterCommitIfTransaction(transaction, () => { 63 return afterCommitIfTransaction(transaction, () => {
56 return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType) 64 return unicastTo({
65 data: activity,
66 byActor,
67 toActorUrl: accountActor.getSharedInbox(),
68 contextType
69 })
57 }) 70 })
58} 71}
59 72
@@ -100,41 +113,69 @@ async function forwardActivity (
100 113
101 logger.debug('Creating forwarding job.', { uris }) 114 logger.debug('Creating forwarding job.', { uris })
102 115
103 const payload = { 116 const payload: ActivitypubHttpBroadcastPayload = {
104 uris, 117 uris,
105 body: activity 118 body: activity,
119 contextType: null
106 } 120 }
107 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })) 121 return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }))
108} 122}
109 123
110// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
111 125
112async function broadcastToFollowers ( 126async function broadcastToFollowers (options: {
113 data: any, 127 data: any
114 byActor: MActorId, 128 byActor: MActorId
115 toFollowersOf: MActorId[], 129 toFollowersOf: MActorId[]
116 t: Transaction, 130 transaction: Transaction
117 actorsException: MActorWithInboxes[] = [], 131 contextType: ContextType
118 contextType?: ContextType 132
119) { 133 actorsException?: MActorWithInboxes[]
120 const uris = await computeFollowerUris(toFollowersOf, actorsException, t) 134}) {
135 const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [] } = options
121 136
122 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) 137 const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction)
138
139 return afterCommitIfTransaction(transaction, () => {
140 return broadcastTo({
141 uris,
142 data,
143 byActor,
144 contextType
145 })
146 })
123} 147}
124 148
125async function broadcastToActors ( 149async function broadcastToActors (options: {
126 data: any, 150 data: any
127 byActor: MActorId, 151 byActor: MActorId
128 toActors: MActor[], 152 toActors: MActor[]
129 t?: Transaction, 153 transaction: Transaction
130 actorsException: MActorWithInboxes[] = [], 154 contextType: ContextType
131 contextType?: ContextType 155 actorsException?: MActorWithInboxes[]
132) { 156}) {
157 const { data, byActor, toActors, transaction, contextType, actorsException = [] } = options
158
133 const uris = await computeUris(toActors, actorsException) 159 const uris = await computeUris(toActors, actorsException)
134 return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) 160
161 return afterCommitIfTransaction(transaction, () => {
162 return broadcastTo({
163 uris,
164 data,
165 byActor,
166 contextType
167 })
168 })
135} 169}
136 170
137function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) { 171function broadcastTo (options: {
172 uris: string[]
173 data: any
174 byActor: MActorId
175 contextType: ContextType
176}) {
177 const { uris, data, byActor, contextType } = options
178
138 if (uris.length === 0) return undefined 179 if (uris.length === 0) return undefined
139 180
140 const broadcastUris: string[] = [] 181 const broadcastUris: string[] = []
@@ -174,7 +215,14 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?
174 } 215 }
175} 216}
176 217
177function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) { 218function unicastTo (options: {
219 data: any
220 byActor: MActorId
221 toActorUrl: string
222 contextType: ContextType
223}) {
224 const { data, byActor, toActorUrl, contextType } = options
225
178 logger.debug('Creating unicast job.', { uri: toActorUrl }) 226 logger.debug('Creating unicast job.', { uri: toActorUrl })
179 227
180 const payload = { 228 const payload = {