From a219c9100b3ce8774d454497d46be87465bf664e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 23 Mar 2022 16:14:33 +0100 Subject: Refactor AP context builder --- server/lib/activitypub/activity.ts | 12 - server/lib/activitypub/audience.ts | 2 +- server/lib/activitypub/context.ts | 275 +++++++++++++---------- server/lib/activitypub/send/http.ts | 68 ++++++ server/lib/activitypub/send/index.ts | 2 +- server/lib/activitypub/send/send-accept.ts | 7 +- server/lib/activitypub/send/send-announce.ts | 14 +- server/lib/activitypub/send/send-create.ts | 67 ++++-- server/lib/activitypub/send/send-delete.ts | 67 ++++-- server/lib/activitypub/send/send-dislike.ts | 4 +- server/lib/activitypub/send/send-flag.ts | 14 +- server/lib/activitypub/send/send-follow.ts | 4 +- server/lib/activitypub/send/send-like.ts | 4 +- server/lib/activitypub/send/send-reject.ts | 2 +- server/lib/activitypub/send/send-undo.ts | 50 +++-- server/lib/activitypub/send/send-update.ts | 44 ++-- server/lib/activitypub/send/shared/send-utils.ts | 104 ++++++--- 17 files changed, 507 insertions(+), 233 deletions(-) create mode 100644 server/lib/activitypub/send/http.ts (limited to 'server/lib/activitypub') 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 @@ -import { signJsonLDObject } from '@server/helpers/peertube-crypto' -import { MActor } from '@server/types/models' -import { ContextType } from '@shared/models' -import { activityPubContextify } from './context' - -function buildSignedActivity (byActor: MActor, data: T, contextType?: ContextType) { - const activity = activityPubContextify(data, contextType) - - return signJsonLDObject(byActor, activity) -} - function getAPId (object: string | { id: string }) { if (typeof object === 'string') return object @@ -16,6 +5,5 @@ function getAPId (object: string | { id: string }) { } export { - buildSignedActivity, getAPId } 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) { } function audiencify (object: T, audience: ActivityAudience) { - return Object.assign(object, audience) + return { ...audience, ...object } } // --------------------------------------------------------------------------- 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 @@ import { ContextType } from '@shared/models' -function getContextData (type: ContextType) { - const context: any[] = [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { - RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' +function activityPubContextify (data: T, type: ContextType) { + return { ...getContextData(type), ...data } +} + +// --------------------------------------------------------------------------- + +export { + getContextData, + activityPubContextify +} + +// --------------------------------------------------------------------------- + +type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string }) } + +const contextStore = { + Video: buildContext({ + Hashtag: 'as:Hashtag', + uuid: 'sc:identifier', + category: 'sc:category', + licence: 'sc:license', + subtitleLanguage: 'sc:subtitleLanguage', + sensitive: 'as:sensitive', + language: 'sc:inLanguage', + + // TODO: remove in a few versions, introduced in 4.2 + icons: 'as:icon', + + isLiveBroadcast: 'sc:isLiveBroadcast', + liveSaveReplay: { + '@type': 'sc:Boolean', + '@id': 'pt:liveSaveReplay' + }, + permanentLive: { + '@type': 'sc:Boolean', + '@id': 'pt:permanentLive' + }, + latencyMode: { + '@type': 'sc:Number', + '@id': 'pt:latencyMode' + }, + + Infohash: 'pt:Infohash', + + originallyPublishedAt: 'sc:datePublished', + views: { + '@type': 'sc:Number', + '@id': 'pt:views' + }, + state: { + '@type': 'sc:Number', + '@id': 'pt:state' + }, + size: { + '@type': 'sc:Number', + '@id': 'pt:size' + }, + fps: { + '@type': 'sc:Number', + '@id': 'pt:fps' + }, + commentsEnabled: { + '@type': 'sc:Boolean', + '@id': 'pt:commentsEnabled' + }, + downloadEnabled: { + '@type': 'sc:Boolean', + '@id': 'pt:downloadEnabled' + }, + waitTranscoding: { + '@type': 'sc:Boolean', + '@id': 'pt:waitTranscoding' + }, + support: { + '@type': 'sc:Text', + '@id': 'pt:support' + }, + likes: { + '@id': 'as:likes', + '@type': '@id' + }, + dislikes: { + '@id': 'as:dislikes', + '@type': '@id' + }, + shares: { + '@id': 'as:shares', + '@type': '@id' + }, + comments: { + '@id': 'as:comments', + '@type': '@id' } - ] + }), - if (type !== 'View' && type !== 'Announce') { - const additional = { - pt: 'https://joinpeertube.org/ns#', - sc: 'http://schema.org#' + Playlist: buildContext({ + Playlist: 'pt:Playlist', + PlaylistElement: 'pt:PlaylistElement', + position: { + '@type': 'sc:Number', + '@id': 'pt:position' + }, + startTimestamp: { + '@type': 'sc:Number', + '@id': 'pt:startTimestamp' + }, + stopTimestamp: { + '@type': 'sc:Number', + '@id': 'pt:stopTimestamp' } + }), + + CacheFile: buildContext({ + expires: 'sc:expires', + CacheFile: 'pt:CacheFile' + }), - if (type === 'CacheFile') { - Object.assign(additional, { - expires: 'sc:expires', - CacheFile: 'pt:CacheFile' - }) - } else { - Object.assign(additional, { - Hashtag: 'as:Hashtag', - uuid: 'sc:identifier', - category: 'sc:category', - licence: 'sc:license', - subtitleLanguage: 'sc:subtitleLanguage', - sensitive: 'as:sensitive', - language: 'sc:inLanguage', - - // TODO: remove in a few versions, introduced in 4.2 - icons: 'as:icon', - - isLiveBroadcast: 'sc:isLiveBroadcast', - liveSaveReplay: { - '@type': 'sc:Boolean', - '@id': 'pt:liveSaveReplay' - }, - permanentLive: { - '@type': 'sc:Boolean', - '@id': 'pt:permanentLive' - }, - latencyMode: { - '@type': 'sc:Number', - '@id': 'pt:latencyMode' - }, - - Infohash: 'pt:Infohash', - Playlist: 'pt:Playlist', - PlaylistElement: 'pt:PlaylistElement', - - originallyPublishedAt: 'sc:datePublished', - views: { - '@type': 'sc:Number', - '@id': 'pt:views' - }, - state: { - '@type': 'sc:Number', - '@id': 'pt:state' - }, - size: { - '@type': 'sc:Number', - '@id': 'pt:size' - }, - fps: { - '@type': 'sc:Number', - '@id': 'pt:fps' - }, - startTimestamp: { - '@type': 'sc:Number', - '@id': 'pt:startTimestamp' - }, - stopTimestamp: { - '@type': 'sc:Number', - '@id': 'pt:stopTimestamp' - }, - position: { - '@type': 'sc:Number', - '@id': 'pt:position' - }, - commentsEnabled: { - '@type': 'sc:Boolean', - '@id': 'pt:commentsEnabled' - }, - downloadEnabled: { - '@type': 'sc:Boolean', - '@id': 'pt:downloadEnabled' - }, - waitTranscoding: { - '@type': 'sc:Boolean', - '@id': 'pt:waitTranscoding' - }, - support: { - '@type': 'sc:Text', - '@id': 'pt:support' - }, - likes: { - '@id': 'as:likes', - '@type': '@id' - }, - dislikes: { - '@id': 'as:dislikes', - '@type': '@id' - }, - playlists: { - '@id': 'pt:playlists', - '@type': '@id' - }, - shares: { - '@id': 'as:shares', - '@type': '@id' - }, - comments: { - '@id': 'as:comments', - '@type': '@id' - } - }) + Flag: buildContext({ + Hashtag: 'as:Hashtag' + }), + + Actor: buildContext({ + playlists: { + '@id': 'pt:playlists', + '@type': '@id' } + }), - context.push(additional) - } + Follow: buildContext(), + Reject: buildContext(), + Accept: buildContext(), + View: buildContext(), + Announce: buildContext(), + Comment: buildContext(), + Delete: buildContext(), + Rate: buildContext() +} +function getContextData (type: ContextType) { return { - '@context': context + '@context': contextStore[type] } } -function activityPubContextify (data: T, type: ContextType = 'All') { - return Object.assign({}, data, getContextData(type)) -} +function buildContext (contextValue?: ContextValue) { + const baseContext = [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + { + RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' + } + ] -export { - getContextData, - activityPubContextify + if (!contextValue) return baseContext + + return [ + ...baseContext, + + { + pt: 'https://joinpeertube.org/ns#', + sc: 'http://schema.org#', + + ...contextValue + } + ] } 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 @@ +import { buildDigest, signJsonLDObject } from '@server/helpers/peertube-crypto' +import { ACTIVITY_PUB, HTTP_SIGNATURE } from '@server/initializers/constants' +import { ActorModel } from '@server/models/actor/actor' +import { getServerActor } from '@server/models/application/application' +import { MActor } from '@server/types/models' +import { ContextType } from '@shared/models/activitypub/context' +import { activityPubContextify } from '../context' + +type Payload = { body: T, contextType: ContextType, signatureActorId?: number } + +async function computeBody ( + payload: Payload +): Promise { + let body = payload.body + + if (payload.signatureActorId) { + const actorSignature = await ActorModel.load(payload.signatureActorId) + if (!actorSignature) throw new Error('Unknown signature actor id.') + + body = await signAndContextify(actorSignature, payload.body, payload.contextType) + } + + return body +} + +async function buildSignedRequestOptions (payload: Payload) { + let actor: MActor | null + + if (payload.signatureActorId) { + actor = await ActorModel.load(payload.signatureActorId) + if (!actor) throw new Error('Unknown signature actor id.') + } else { + // We need to sign the request, so use the server + actor = await getServerActor() + } + + const keyId = actor.url + return { + algorithm: HTTP_SIGNATURE.ALGORITHM, + authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, + keyId, + key: actor.privateKey, + headers: HTTP_SIGNATURE.HEADERS_TO_SIGN + } +} + +function buildGlobalHeaders (body: any) { + return { + 'digest': buildDigest(body), + 'content-type': 'application/activity+json', + 'accept': ACTIVITY_PUB.ACCEPT_HEADER + } +} + +function signAndContextify (byActor: MActor, data: T, contextType: ContextType | null) { + const activity = contextType + ? activityPubContextify(data, contextType) + : data + + return signJsonLDObject(byActor, activity) +} + +export { + buildGlobalHeaders, + computeBody, + buildSignedRequestOptions, + signAndContextify +} 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 @@ -export * from './send-accept' +export * from './http' export * from './send-accept' export * from './send-announce' export * 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) { const url = getLocalActorFollowAcceptActivityPubUrl(actorFollow) const data = buildAcceptActivity(url, me, followData) - return unicastTo(data, me, follower.inboxUrl) + return unicastTo({ + data, + byActor: me, + toActorUrl: follower.inboxUrl, + contextType: 'Accept' + }) } // --------------------------------------------------------------------------- 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 ( return { activity, actorsInvolvedInVideo } } -async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { - const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) +async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) { + const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction) logger.info('Creating job to send announce %s.', videoShare.url) - const followersException = [ byActor ] - return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce') + return broadcastToFollowers({ + data: activity, + byActor, + toFollowersOf: actorsInvolvedInVideo, + transaction, + actorsException: [ byActor ], + contextType: 'Announce' + }) } function 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 { const lTags = loggerTagsFactory('ap', 'create') -async function sendCreateVideo (video: MVideoAP, t: Transaction) { +async function sendCreateVideo (video: MVideoAP, transaction: Transaction) { if (!video.hasPrivacyForFederation()) return undefined 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) { const audience = getAudience(byActor, video.privacy === VideoPrivacy.PUBLIC) const createActivity = buildCreateActivity(video.url, byActor, videoObject, audience) - return broadcastToFollowers(createActivity, byActor, [ byActor ], t) + return broadcastToFollowers({ + data: createActivity, + byActor, + toFollowersOf: [ byActor ], + transaction, + contextType: 'Video' + }) } async function sendCreateCacheFile ( @@ -55,7 +61,7 @@ async function sendCreateCacheFile ( }) } -async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) { +async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) { if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined 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 const byActor = playlist.OwnerAccount.Actor const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) - const object = await playlist.toActivityPubObject(null, t) + const object = await playlist.toActivityPubObject(null, transaction) const createActivity = buildCreateActivity(playlist.url, byActor, object, audience) const serverActor = await getServerActor() @@ -71,19 +77,25 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transac if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor) - return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) + return broadcastToFollowers({ + data: createActivity, + byActor, + toFollowersOf, + transaction, + contextType: 'Playlist' + }) } -async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) { +async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) { logger.info('Creating job to send comment %s.', comment.url) const isOrigin = comment.Video.isOwned() const byActor = comment.Account.Actor - const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) + const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction) const commentObject = comment.toActivityPubObject(threadParentComments) - const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t) + const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction) // Add the actor that commented too actorsInvolvedInComment.push(byActor) @@ -101,16 +113,45 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transacti // This was a reply, send it to the parent actors const actorsException = [ byActor ] - await broadcastToActors(createActivity, byActor, parentsCommentActors, t, actorsException) + await broadcastToActors({ + data: createActivity, + byActor, + toActors: parentsCommentActors, + transaction, + actorsException, + contextType: 'Comment' + }) // Broadcast to our followers - await broadcastToFollowers(createActivity, byActor, [ byActor ], t) + await broadcastToFollowers({ + data: createActivity, + byActor, + toFollowersOf: [ byActor ], + transaction, + contextType: 'Comment' + }) // Send to actors involved in the comment - if (isOrigin) return broadcastToFollowers(createActivity, byActor, actorsInvolvedInComment, t, actorsException) + if (isOrigin) { + return broadcastToFollowers({ + data: createActivity, + byActor, + toFollowersOf: actorsInvolvedInComment, + transaction, + actorsException, + contextType: 'Comment' + }) + } // Send to origin - t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.getSharedInbox())) + return transaction.afterCommit(() => { + return unicastTo({ + data: createActivity, + byActor, + toActorUrl: comment.Video.VideoChannel.Account.Actor.getSharedInbox(), + contextType: 'Comment' + }) + }) } function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { @@ -144,8 +185,8 @@ async function sendVideoRelatedCreateActivity (options: { video: MVideoAccountLight url: string object: any + contextType: ContextType transaction?: Transaction - contextType?: ContextType }) { const activityBuilder = (audience: ActivityAudience) => { 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 return buildDeleteActivity(url, video.url, byActor, audience) } - return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction }) + return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'Delete', transaction }) } -async function sendDeleteActor (byActor: ActorModel, t: Transaction) { +async function sendDeleteActor (byActor: ActorModel, transaction: Transaction) { logger.info('Creating job to broadcast delete of actor %s.', byActor.url) const url = getDeleteActivityPubUrl(byActor.url) const activity = buildDeleteActivity(url, byActor.url, byActor) - const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) + const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction) // In case the actor did not have any videos const serverActor = await getServerActor() @@ -40,10 +40,16 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) { actorsInvolved.push(byActor) - return broadcastToFollowers(activity, byActor, actorsInvolved, t) + return broadcastToFollowers({ + data: activity, + byActor, + toFollowersOf: actorsInvolved, + contextType: 'Delete', + transaction + }) } -async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Transaction) { +async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, transaction: Transaction) { logger.info('Creating job to send delete of comment %s.', videoComment.url) const isVideoOrigin = videoComment.Video.isOwned() @@ -53,10 +59,10 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran ? videoComment.Account.Actor : videoComment.Video.VideoChannel.Account.Actor - const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t) + const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, transaction) const threadParentCommentsFiltered = threadParentComments.filter(c => !c.isDeleted()) - const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t) + const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, transaction) actorsInvolvedInComment.push(byActor) // Add the actor that commented the video const audience = getVideoCommentAudience(videoComment, threadParentCommentsFiltered, actorsInvolvedInComment, isVideoOrigin) @@ -64,19 +70,48 @@ async function sendDeleteVideoComment (videoComment: MCommentOwnerVideo, t: Tran // This was a reply, send it to the parent actors const actorsException = [ byActor ] - await broadcastToActors(activity, byActor, threadParentCommentsFiltered.map(c => c.Account.Actor), t, actorsException) + await broadcastToActors({ + data: activity, + byActor, + toActors: threadParentCommentsFiltered.map(c => c.Account.Actor), + transaction, + contextType: 'Delete', + actorsException + }) // Broadcast to our followers - await broadcastToFollowers(activity, byActor, [ byActor ], t) + await broadcastToFollowers({ + data: activity, + byActor, + toFollowersOf: [ byActor ], + contextType: 'Delete', + transaction + }) // Send to actors involved in the comment - if (isVideoOrigin) return broadcastToFollowers(activity, byActor, actorsInvolvedInComment, t, actorsException) + if (isVideoOrigin) { + return broadcastToFollowers({ + data: activity, + byActor, + toFollowersOf: actorsInvolvedInComment, + transaction, + contextType: 'Delete', + actorsException + }) + } // Send to origin - t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.getSharedInbox())) + return transaction.afterCommit(() => { + return unicastTo({ + data: activity, + byActor, + toActorUrl: videoComment.Video.VideoChannel.Account.Actor.getSharedInbox(), + contextType: 'Delete' + }) + }) } -async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) { +async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, transaction: Transaction) { logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) const byActor = videoPlaylist.OwnerAccount.Actor @@ -89,7 +124,13 @@ async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) - return broadcastToFollowers(activity, byActor, toFollowersOf, t) + return broadcastToFollowers({ + data: activity, + byActor, + toFollowersOf, + contextType: 'Delete', + transaction + }) } // --------------------------------------------------------------------------- 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' import { getVideoDislikeActivityPubUrlByLocalActor } from '../url' import { sendVideoActivityToOrigin } from './shared/send-utils' -function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { +function sendDislike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) { logger.info('Creating job to dislike %s.', video.url) const activityBuilder = (audience: ActivityAudience) => { @@ -15,7 +15,7 @@ function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction return buildDislikeActivity(url, byActor, video, audience) } - return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t }) + return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' }) } function 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 const audience = { to: [ flaggedAccount.Actor.url ], cc: [] } const flagActivity = buildFlagActivity(url, byActor, abuse, audience) - t.afterCommit(() => unicastTo(flagActivity, byActor, flaggedAccount.Actor.getSharedInbox())) + return t.afterCommit(() => { + return unicastTo({ + data: flagActivity, + byActor, + toActorUrl: flaggedAccount.Actor.getSharedInbox(), + contextType: 'Flag' + }) + }) } function buildFlagActivity (url: string, byActor: MActor, abuse: MAbuseAP, audience: ActivityAudience): ActivityFlag { if (!audience) audience = getAudience(byActor) - const activity = Object.assign( - { id: url, actor: byActor.url }, - abuse.toActivityPubObject() - ) + const activity = { id: url, actor: byActor.url, ...abuse.toActivityPubObject() } return audiencify(activity, audience) } 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) { const data = buildFollowActivity(actorFollow.url, me, following) - t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) + return t.afterCommit(() => { + return unicastTo({ data, byActor: me, toActorUrl: following.inboxUrl, contextType: 'Follow' }) + }) } function 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' import { getVideoLikeActivityPubUrlByLocalActor } from '../url' import { sendVideoActivityToOrigin } from './shared/send-utils' -function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { +function sendLike (byActor: MActor, video: MVideoAccountLight, transaction: Transaction) { logger.info('Creating job to like %s.', video.url) const activityBuilder = (audience: ActivityAudience) => { @@ -15,7 +15,7 @@ function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { return buildLikeActivity(url, byActor, video, audience) } - return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction: t }) + return sendVideoActivityToOrigin(activityBuilder, { byActor, video, transaction, contextType: 'Rate' }) } function 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) { const url = getLocalActorFollowRejectActivityPubUrl(follower, following) const data = buildRejectActivity(url, following, followData) - return unicastTo(data, following, follower.inboxUrl) + return unicastTo({ data, byActor: following, toActorUrl: follower.inboxUrl, contextType: 'Reject' }) } // --------------------------------------------------------------------------- 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 { ActivityDislike, ActivityFollow, ActivityLike, - ActivityUndo + ActivityUndo, + ContextType } from '@shared/models' import { logger } from '../../../helpers/logger' import { VideoModel } from '../../../models/video/video' @@ -43,24 +44,37 @@ function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { const followActivity = buildFollowActivity(actorFollow.url, me, following) const undoActivity = undoActivityData(undoUrl, me, followActivity) - t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) + return t.afterCommit(() => { + return unicastTo({ + data: undoActivity, + byActor: me, + toActorUrl: following.inboxUrl, + contextType: 'Follow' + }) + }) } // --------------------------------------------------------------------------- -async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { +async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, transaction: Transaction) { logger.info('Creating job to undo announce %s.', videoShare.url) const undoUrl = getUndoActivityPubUrl(videoShare.url) - const { activity: announceActivity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) - const undoActivity = undoActivityData(undoUrl, byActor, announceActivity) - - const followersException = [ byActor ] - return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) + const { activity: announce, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, transaction) + const undoActivity = undoActivityData(undoUrl, byActor, announce) + + return broadcastToFollowers({ + data: undoActivity, + byActor, + toFollowersOf: actorsInvolvedInVideo, + transaction, + actorsException: [ byActor ], + contextType: 'Announce' + }) } -async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) { +async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, transaction: Transaction) { logger.info('Creating job to undo cache file %s.', redundancyModel.url) const associatedVideo = redundancyModel.getVideo() @@ -72,7 +86,14 @@ async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedund const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(associatedVideo.id) const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject()) - return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t }) + return sendUndoVideoRelatedActivity({ + byActor, + video, + url: redundancyModel.url, + activity: createActivity, + contextType: 'CacheFile', + transaction + }) } // --------------------------------------------------------------------------- @@ -83,7 +104,7 @@ async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Tran const likeUrl = getVideoLikeActivityPubUrlByLocalActor(byActor, video) const likeActivity = buildLikeActivity(likeUrl, byActor, video) - return sendUndoVideoToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) + return sendUndoVideoRateToOriginActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) } async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { @@ -92,7 +113,7 @@ async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: T const dislikeUrl = getVideoDislikeActivityPubUrlByLocalActor(byActor, video) const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video) - return sendUndoVideoToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) + return sendUndoVideoRateToOriginActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) } // --------------------------------------------------------------------------- @@ -131,6 +152,7 @@ async function sendUndoVideoRelatedActivity (options: { video: MVideoAccountLight url: string activity: ActivityFollow | ActivityCreate | ActivityAnnounce + contextType: ContextType transaction: Transaction }) { const activityBuilder = (audience: ActivityAudience) => { @@ -142,7 +164,7 @@ async function sendUndoVideoRelatedActivity (options: { return sendVideoRelatedActivity(activityBuilder, options) } -async function sendUndoVideoToOriginActivity (options: { +async function sendUndoVideoRateToOriginActivity (options: { byActor: MActor video: MVideoAccountLight url: string @@ -155,5 +177,5 @@ async function sendUndoVideoToOriginActivity (options: { return undoActivityData(undoUrl, options.byActor, options.activity, audience) } - return sendVideoActivityToOrigin(activityBuilder, options) + return sendVideoActivityToOrigin(activityBuilder, { ...options, contextType: 'Rate' }) } 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' import { getActorsInvolvedInVideo } from './shared' import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils' -async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { +async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, transaction: Transaction, overriddenByActor?: MActor) { const video = videoArg as MVideoAP if (!video.hasPrivacyForFederation()) return undefined logger.info('Creating job to update video %s.', video.url) - const byActor = overrodeByActor || video.VideoChannel.Account.Actor + const byActor = overriddenByActor || video.VideoChannel.Account.Actor const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) // Needed to build the AP object if (!video.VideoCaptions) { - video.VideoCaptions = await video.$get('VideoCaptions', { transaction: t }) + video.VideoCaptions = await video.$get('VideoCaptions', { transaction }) } const videoObject = video.toActivityPubObject() @@ -41,13 +41,19 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction const updateActivity = buildUpdateActivity(url, byActor, videoObject, audience) - const actorsInvolved = await getActorsInvolvedInVideo(video, t) - if (overrodeByActor) actorsInvolved.push(overrodeByActor) + const actorsInvolved = await getActorsInvolvedInVideo(video, transaction) + if (overriddenByActor) actorsInvolved.push(overriddenByActor) - return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) + return broadcastToFollowers({ + data: updateActivity, + byActor, + toFollowersOf: actorsInvolved, + contextType: 'Video', + transaction + }) } -async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) { +async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) { const byActor = accountOrChannel.Actor logger.info('Creating job to update actor %s.', byActor.url) @@ -60,15 +66,21 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa let actorsInvolved: MActor[] if (accountOrChannel instanceof AccountModel) { // Actors that shared my videos are involved too - actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) + actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, transaction) } else { // Actors that shared videos of my channel are involved too - actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t) + actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, transaction) } actorsInvolved.push(byActor) - return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) + return broadcastToFollowers({ + data: updateActivity, + byActor, + toFollowersOf: actorsInvolved, + transaction, + contextType: 'Actor' + }) } async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { @@ -92,7 +104,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) } -async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { +async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) { if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined const byActor = videoPlaylist.OwnerAccount.Actor @@ -101,7 +113,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString()) - const object = await videoPlaylist.toActivityPubObject(null, t) + const object = await videoPlaylist.toActivityPubObject(null, transaction) const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC) const updateActivity = buildUpdateActivity(url, byActor, object, audience) @@ -111,7 +123,13 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Tr if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) - return broadcastToFollowers(updateActivity, byActor, toFollowersOf, t) + return broadcastToFollowers({ + data: updateActivity, + byActor, + toFollowersOf, + transaction, + contextType: 'Playlist' + }) } // --------------------------------------------------------------------------- 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 @@ import { Transaction } from 'sequelize' import { ActorFollowHealthCache } from '@server/lib/actor-follow-health-cache' import { getServerActor } from '@server/models/application/application' -import { Activity, ActivityAudience } from '@shared/models' +import { Activity, ActivityAudience, ActivitypubHttpBroadcastPayload } from '@shared/models' import { ContextType } from '@shared/models/activitypub/context' import { afterCommitIfTransaction } from '../../../../helpers/database-utils' import { logger } from '../../../../helpers/logger' @@ -14,8 +14,8 @@ import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getOriginVideoAud async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { byActor: MActorLight video: MVideoImmutable | MVideoAccountLight + contextType: ContextType transaction?: Transaction - contextType?: ContextType }) { const { byActor, video, transaction, contextType } = options @@ -32,15 +32,23 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud const actorsException = [ byActor ] - return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType) + return broadcastToFollowers({ + data: activity, + byActor, + toFollowersOf: actorsInvolvedInVideo, + transaction, + actorsException, + contextType + }) } async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAudience) => Activity, options: { byActor: MActorLight video: MVideoImmutable | MVideoAccountLight + contextType: ContextType + actorsInvolvedInVideo?: MActorLight[] transaction?: Transaction - contextType?: ContextType }) { const { byActor, video, actorsInvolvedInVideo, transaction, contextType } = options @@ -53,7 +61,12 @@ async function sendVideoActivityToOrigin (activityBuilder: (audience: ActivityAu const activity = activityBuilder(audience) return afterCommitIfTransaction(transaction, () => { - return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType) + return unicastTo({ + data: activity, + byActor, + toActorUrl: accountActor.getSharedInbox(), + contextType + }) }) } @@ -100,41 +113,69 @@ async function forwardActivity ( logger.debug('Creating forwarding job.', { uris }) - const payload = { + const payload: ActivitypubHttpBroadcastPayload = { uris, - body: activity + body: activity, + contextType: null } return afterCommitIfTransaction(t, () => JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload })) } // --------------------------------------------------------------------------- -async function broadcastToFollowers ( - data: any, - byActor: MActorId, - toFollowersOf: MActorId[], - t: Transaction, - actorsException: MActorWithInboxes[] = [], - contextType?: ContextType -) { - const uris = await computeFollowerUris(toFollowersOf, actorsException, t) +async function broadcastToFollowers (options: { + data: any + byActor: MActorId + toFollowersOf: MActorId[] + transaction: Transaction + contextType: ContextType + + actorsException?: MActorWithInboxes[] +}) { + const { data, byActor, toFollowersOf, transaction, contextType, actorsException = [] } = options - return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) + const uris = await computeFollowerUris(toFollowersOf, actorsException, transaction) + + return afterCommitIfTransaction(transaction, () => { + return broadcastTo({ + uris, + data, + byActor, + contextType + }) + }) } -async function broadcastToActors ( - data: any, - byActor: MActorId, - toActors: MActor[], - t?: Transaction, - actorsException: MActorWithInboxes[] = [], - contextType?: ContextType -) { +async function broadcastToActors (options: { + data: any + byActor: MActorId + toActors: MActor[] + transaction: Transaction + contextType: ContextType + actorsException?: MActorWithInboxes[] +}) { + const { data, byActor, toActors, transaction, contextType, actorsException = [] } = options + const uris = await computeUris(toActors, actorsException) - return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) + + return afterCommitIfTransaction(transaction, () => { + return broadcastTo({ + uris, + data, + byActor, + contextType + }) + }) } -function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) { +function broadcastTo (options: { + uris: string[] + data: any + byActor: MActorId + contextType: ContextType +}) { + const { uris, data, byActor, contextType } = options + if (uris.length === 0) return undefined const broadcastUris: string[] = [] @@ -174,7 +215,14 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType? } } -function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) { +function unicastTo (options: { + data: any + byActor: MActorId + toActorUrl: string + contextType: ContextType +}) { + const { data, byActor, toActorUrl, contextType } = options + logger.debug('Creating unicast job.', { uri: toActorUrl }) const payload = { -- cgit v1.2.3