aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/lib/activitypub/process
diff options
context:
space:
mode:
Diffstat (limited to 'server/lib/activitypub/process')
-rw-r--r--server/lib/activitypub/process/index.ts1
-rw-r--r--server/lib/activitypub/process/process-accept.ts32
-rw-r--r--server/lib/activitypub/process/process-announce.ts75
-rw-r--r--server/lib/activitypub/process/process-create.ts170
-rw-r--r--server/lib/activitypub/process/process-delete.ts153
-rw-r--r--server/lib/activitypub/process/process-dislike.ts58
-rw-r--r--server/lib/activitypub/process/process-flag.ts103
-rw-r--r--server/lib/activitypub/process/process-follow.ts156
-rw-r--r--server/lib/activitypub/process/process-like.ts60
-rw-r--r--server/lib/activitypub/process/process-reject.ts33
-rw-r--r--server/lib/activitypub/process/process-undo.ts183
-rw-r--r--server/lib/activitypub/process/process-update.ts119
-rw-r--r--server/lib/activitypub/process/process-view.ts42
-rw-r--r--server/lib/activitypub/process/process.ts92
14 files changed, 0 insertions, 1277 deletions
diff --git a/server/lib/activitypub/process/index.ts b/server/lib/activitypub/process/index.ts
deleted file mode 100644
index 5466739c1..000000000
--- a/server/lib/activitypub/process/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './process'
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
deleted file mode 100644
index 077b01eda..000000000
--- a/server/lib/activitypub/process/process-accept.ts
+++ /dev/null
@@ -1,32 +0,0 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { ActorFollowModel } from '../../../models/actor/actor-follow'
3import { APProcessorOptions } from '../../../types/activitypub-processor.model'
4import { MActorDefault, MActorSignature } from '../../../types/models'
5import { addFetchOutboxJob } from '../outbox'
6
7async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) {
8 const { byActor: targetActor, inboxActor } = options
9 if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.')
10
11 return processAccept(inboxActor, targetActor)
12}
13
14// ---------------------------------------------------------------------------
15
16export {
17 processAcceptActivity
18}
19
20// ---------------------------------------------------------------------------
21
22async function processAccept (actor: MActorDefault, targetActor: MActorSignature) {
23 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
24 if (!follow) throw new Error('Cannot find associated follow.')
25
26 if (follow.state !== 'accepted') {
27 follow.state = 'accepted'
28 await follow.save()
29
30 await addFetchOutboxJob(targetActor)
31 }
32}
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
deleted file mode 100644
index 9cc87ee27..000000000
--- a/server/lib/activitypub/process/process-announce.ts
+++ /dev/null
@@ -1,75 +0,0 @@
1import { getAPId } from '@server/lib/activitypub/activity'
2import { ActivityAnnounce } from '../../../../shared/models/activitypub'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger'
5import { sequelizeTypescript } from '../../../initializers/database'
6import { VideoShareModel } from '../../../models/video/video-share'
7import { APProcessorOptions } from '../../../types/activitypub-processor.model'
8import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
9import { Notifier } from '../../notifier'
10import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
11import { getOrCreateAPVideo } from '../videos'
12
13async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) {
14 const { activity, byActor: actorAnnouncer } = options
15 // Only notify if it is not from a fetcher job
16 const notify = options.fromFetch !== true
17
18 // Announces on accounts are not supported
19 if (actorAnnouncer.type !== 'Application' && actorAnnouncer.type !== 'Group') return
20
21 return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity, notify)
22}
23
24// ---------------------------------------------------------------------------
25
26export {
27 processAnnounceActivity
28}
29
30// ---------------------------------------------------------------------------
31
32async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) {
33 const objectUri = getAPId(activity.object)
34
35 let video: MVideoAccountLightBlacklistAllFiles
36 let videoCreated: boolean
37
38 try {
39 const result = await getOrCreateAPVideo({ videoObject: objectUri })
40 video = result.video
41 videoCreated = result.created
42 } catch (err) {
43 logger.debug('Cannot process share of %s. Maybe this is not a video object, so just skipping.', objectUri, { err })
44 return
45 }
46
47 await sequelizeTypescript.transaction(async t => {
48 // Add share entry
49
50 const share = {
51 actorId: actorAnnouncer.id,
52 videoId: video.id,
53 url: activity.id
54 }
55
56 const [ , created ] = await VideoShareModel.findOrCreate({
57 where: {
58 url: activity.id
59 },
60 defaults: share,
61 transaction: t
62 })
63
64 if (video.isOwned() && created === true) {
65 // Don't resend the activity to the sender
66 const exceptions = [ actorAnnouncer ]
67
68 await forwardVideoRelatedActivity(activity, t, exceptions, video)
69 }
70
71 return undefined
72 })
73
74 if (videoCreated && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
75}
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
deleted file mode 100644
index 5f980de65..000000000
--- a/server/lib/activitypub/process/process-create.ts
+++ /dev/null
@@ -1,170 +0,0 @@
1import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
2import { isRedundancyAccepted } from '@server/lib/redundancy'
3import { VideoModel } from '@server/models/video/video'
4import {
5 AbuseObject,
6 ActivityCreate,
7 ActivityCreateObject,
8 ActivityObject,
9 CacheFileObject,
10 PlaylistObject,
11 VideoCommentObject,
12 VideoObject,
13 WatchActionObject
14} from '@shared/models'
15import { retryTransactionWrapper } from '../../../helpers/database-utils'
16import { logger } from '../../../helpers/logger'
17import { sequelizeTypescript } from '../../../initializers/database'
18import { APProcessorOptions } from '../../../types/activitypub-processor.model'
19import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
20import { Notifier } from '../../notifier'
21import { fetchAPObjectIfNeeded } from '../activity'
22import { createOrUpdateCacheFile } from '../cache-file'
23import { createOrUpdateLocalVideoViewer } from '../local-video-viewer'
24import { createOrUpdateVideoPlaylist } from '../playlists'
25import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
26import { resolveThread } from '../video-comments'
27import { getOrCreateAPVideo } from '../videos'
28
29async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
30 const { activity, byActor } = options
31
32 // Only notify if it is not from a fetcher job
33 const notify = options.fromFetch !== true
34 const activityObject = await fetchAPObjectIfNeeded<Exclude<ActivityObject, AbuseObject>>(activity.object)
35 const activityType = activityObject.type
36
37 if (activityType === 'Video') {
38 return processCreateVideo(activityObject, notify)
39 }
40
41 if (activityType === 'Note') {
42 // Comments will be fetched from videos
43 if (options.fromFetch) return
44
45 return retryTransactionWrapper(processCreateVideoComment, activity, activityObject, byActor, notify)
46 }
47
48 if (activityType === 'WatchAction') {
49 return retryTransactionWrapper(processCreateWatchAction, activityObject)
50 }
51
52 if (activityType === 'CacheFile') {
53 return retryTransactionWrapper(processCreateCacheFile, activity, activityObject, byActor)
54 }
55
56 if (activityType === 'Playlist') {
57 return retryTransactionWrapper(processCreatePlaylist, activity, activityObject, byActor)
58 }
59
60 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
61 return Promise.resolve(undefined)
62}
63
64// ---------------------------------------------------------------------------
65
66export {
67 processCreateActivity
68}
69
70// ---------------------------------------------------------------------------
71
72async function processCreateVideo (videoToCreateData: VideoObject, notify: boolean) {
73 const syncParam = { rates: false, shares: false, comments: false, refreshVideo: false }
74 const { video, created } = await getOrCreateAPVideo({ videoObject: videoToCreateData, syncParam })
75
76 if (created && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
77
78 return video
79}
80
81async function processCreateCacheFile (
82 activity: ActivityCreate<CacheFileObject | string>,
83 cacheFile: CacheFileObject,
84 byActor: MActorSignature
85) {
86 if (await isRedundancyAccepted(activity, byActor) !== true) return
87
88 const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
89
90 await sequelizeTypescript.transaction(async t => {
91 return createOrUpdateCacheFile(cacheFile, video, byActor, t)
92 })
93
94 if (video.isOwned()) {
95 // Don't resend the activity to the sender
96 const exceptions = [ byActor ]
97 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
98 }
99}
100
101async function processCreateWatchAction (watchAction: WatchActionObject) {
102 if (watchAction.actionStatus !== 'CompletedActionStatus') return
103
104 const video = await VideoModel.loadByUrl(watchAction.object)
105 if (video.remote) return
106
107 await sequelizeTypescript.transaction(async t => {
108 return createOrUpdateLocalVideoViewer(watchAction, video, t)
109 })
110}
111
112async function processCreateVideoComment (
113 activity: ActivityCreate<VideoCommentObject | string>,
114 commentObject: VideoCommentObject,
115 byActor: MActorSignature,
116 notify: boolean
117) {
118 const byAccount = byActor.Account
119
120 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
121
122 let video: MVideoAccountLightBlacklistAllFiles
123 let created: boolean
124 let comment: MCommentOwnerVideo
125
126 try {
127 const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false })
128 if (!resolveThreadResult) return // Comment not accepted
129
130 video = resolveThreadResult.video
131 created = resolveThreadResult.commentCreated
132 comment = resolveThreadResult.comment
133 } catch (err) {
134 logger.debug(
135 'Cannot process video comment because we could not resolve thread %s. Maybe it was not a video thread, so skip it.',
136 commentObject.inReplyTo,
137 { err }
138 )
139 return
140 }
141
142 // Try to not forward unwanted comments on our videos
143 if (video.isOwned()) {
144 if (await isBlockedByServerOrAccount(comment.Account, video.VideoChannel.Account)) {
145 logger.info('Skip comment forward from blocked account or server %s.', comment.Account.Actor.url)
146 return
147 }
148
149 if (created === true) {
150 // Don't resend the activity to the sender
151 const exceptions = [ byActor ]
152
153 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
154 }
155 }
156
157 if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
158}
159
160async function processCreatePlaylist (
161 activity: ActivityCreate<PlaylistObject | string>,
162 playlistObject: PlaylistObject,
163 byActor: MActorSignature
164) {
165 const byAccount = byActor.Account
166
167 if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url)
168
169 await createOrUpdateVideoPlaylist(playlistObject, activity.to)
170}
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
deleted file mode 100644
index ac0e7e235..000000000
--- a/server/lib/activitypub/process/process-delete.ts
+++ /dev/null
@@ -1,153 +0,0 @@
1import { ActivityDelete } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers/database-utils'
3import { logger } from '../../../helpers/logger'
4import { sequelizeTypescript } from '../../../initializers/database'
5import { ActorModel } from '../../../models/actor/actor'
6import { VideoModel } from '../../../models/video/video'
7import { VideoCommentModel } from '../../../models/video/video-comment'
8import { VideoPlaylistModel } from '../../../models/video/video-playlist'
9import { APProcessorOptions } from '../../../types/activitypub-processor.model'
10import {
11 MAccountActor,
12 MActor,
13 MActorFull,
14 MActorSignature,
15 MChannelAccountActor,
16 MChannelActor,
17 MCommentOwnerVideo
18} from '../../../types/models'
19import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
20
21async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) {
22 const { activity, byActor } = options
23
24 const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
25
26 if (activity.actor === objectUrl) {
27 // We need more attributes (all the account and channel)
28 const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url)
29
30 if (byActorFull.type === 'Person') {
31 if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.')
32
33 const accountToDelete = byActorFull.Account as MAccountActor
34 accountToDelete.Actor = byActorFull
35
36 return retryTransactionWrapper(processDeleteAccount, accountToDelete)
37 } else if (byActorFull.type === 'Group') {
38 if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.')
39
40 const channelToDelete = byActorFull.VideoChannel as MChannelAccountActor & { Actor: MActorFull }
41 channelToDelete.Actor = byActorFull
42 return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete)
43 }
44 }
45
46 {
47 const videoCommentInstance = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(objectUrl)
48 if (videoCommentInstance) {
49 return retryTransactionWrapper(processDeleteVideoComment, byActor, videoCommentInstance, activity)
50 }
51 }
52
53 {
54 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(objectUrl)
55 if (videoInstance) {
56 if (videoInstance.isOwned()) throw new Error(`Remote instance cannot delete owned video ${videoInstance.url}.`)
57
58 return retryTransactionWrapper(processDeleteVideo, byActor, videoInstance)
59 }
60 }
61
62 {
63 const videoPlaylist = await VideoPlaylistModel.loadByUrlAndPopulateAccount(objectUrl)
64 if (videoPlaylist) {
65 if (videoPlaylist.isOwned()) throw new Error(`Remote instance cannot delete owned playlist ${videoPlaylist.url}.`)
66
67 return retryTransactionWrapper(processDeleteVideoPlaylist, byActor, videoPlaylist)
68 }
69 }
70
71 return undefined
72}
73
74// ---------------------------------------------------------------------------
75
76export {
77 processDeleteActivity
78}
79
80// ---------------------------------------------------------------------------
81
82async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) {
83 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
84
85 await sequelizeTypescript.transaction(async t => {
86 if (videoToDelete.VideoChannel.Account.Actor.id !== actor.id) {
87 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoToDelete.VideoChannel.Actor.url)
88 }
89
90 await videoToDelete.destroy({ transaction: t })
91 })
92
93 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
94}
95
96async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) {
97 logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid)
98
99 await sequelizeTypescript.transaction(async t => {
100 if (playlistToDelete.OwnerAccount.Actor.id !== actor.id) {
101 throw new Error('Account ' + actor.url + ' does not own video playlist ' + playlistToDelete.url)
102 }
103
104 await playlistToDelete.destroy({ transaction: t })
105 })
106
107 logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid)
108}
109
110async function processDeleteAccount (accountToRemove: MAccountActor) {
111 logger.debug('Removing remote account "%s".', accountToRemove.Actor.url)
112
113 await sequelizeTypescript.transaction(async t => {
114 await accountToRemove.destroy({ transaction: t })
115 })
116
117 logger.info('Remote account %s removed.', accountToRemove.Actor.url)
118}
119
120async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) {
121 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url)
122
123 await sequelizeTypescript.transaction(async t => {
124 await videoChannelToRemove.destroy({ transaction: t })
125 })
126
127 logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url)
128}
129
130function processDeleteVideoComment (byActor: MActorSignature, videoComment: MCommentOwnerVideo, activity: ActivityDelete) {
131 // Already deleted
132 if (videoComment.isDeleted()) return Promise.resolve()
133
134 logger.debug('Removing remote video comment "%s".', videoComment.url)
135
136 return sequelizeTypescript.transaction(async t => {
137 if (byActor.Account.id !== videoComment.Account.id && byActor.Account.id !== videoComment.Video.VideoChannel.accountId) {
138 throw new Error(`Account ${byActor.url} does not own video comment ${videoComment.url} or video ${videoComment.Video.url}`)
139 }
140
141 videoComment.markAsDeleted()
142
143 await videoComment.save({ transaction: t })
144
145 if (videoComment.Video.isOwned()) {
146 // Don't resend the activity to the sender
147 const exceptions = [ byActor ]
148 await forwardVideoRelatedActivity(activity, t, exceptions, videoComment.Video)
149 }
150
151 logger.info('Remote video comment %s removed.', videoComment.url)
152 })
153}
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts
deleted file mode 100644
index 4e270f917..000000000
--- a/server/lib/activitypub/process/process-dislike.ts
+++ /dev/null
@@ -1,58 +0,0 @@
1import { VideoModel } from '@server/models/video/video'
2import { ActivityDislike } from '@shared/models'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { sequelizeTypescript } from '../../../initializers/database'
5import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
6import { APProcessorOptions } from '../../../types/activitypub-processor.model'
7import { MActorSignature } from '../../../types/models'
8import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos'
9
10async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
11 const { activity, byActor } = options
12 return retryTransactionWrapper(processDislike, activity, byActor)
13}
14
15// ---------------------------------------------------------------------------
16
17export {
18 processDislikeActivity
19}
20
21// ---------------------------------------------------------------------------
22
23async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
24 const dislikeObject = activity.object
25 const byAccount = byActor.Account
26
27 if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
28
29 const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: dislikeObject, fetchType: 'only-video' })
30
31 // We don't care about dislikes of remote videos
32 if (!onlyVideo.isOwned()) return
33
34 return sequelizeTypescript.transaction(async t => {
35 const video = await VideoModel.loadFull(onlyVideo.id, t)
36
37 const existingRate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byAccount.id, video.id, activity.id, t)
38 if (existingRate && existingRate.type === 'dislike') return
39
40 await video.increment('dislikes', { transaction: t })
41 video.dislikes++
42
43 if (existingRate && existingRate.type === 'like') {
44 await video.decrement('likes', { transaction: t })
45 video.likes--
46 }
47
48 const rate = existingRate || new AccountVideoRateModel()
49 rate.type = 'dislike'
50 rate.videoId = video.id
51 rate.accountId = byAccount.id
52 rate.url = activity.id
53
54 await rate.save({ transaction: t })
55
56 await federateVideoIfNeeded(video, false, t)
57 })
58}
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
deleted file mode 100644
index bea285670..000000000
--- a/server/lib/activitypub/process/process-flag.ts
+++ /dev/null
@@ -1,103 +0,0 @@
1import { createAccountAbuse, createVideoAbuse, createVideoCommentAbuse } from '@server/lib/moderation'
2import { AccountModel } from '@server/models/account/account'
3import { VideoModel } from '@server/models/video/video'
4import { VideoCommentModel } from '@server/models/video/video-comment'
5import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
6import { AbuseState, ActivityFlag } from '@shared/models'
7import { retryTransactionWrapper } from '../../../helpers/database-utils'
8import { logger } from '../../../helpers/logger'
9import { sequelizeTypescript } from '../../../initializers/database'
10import { getAPId } from '../../../lib/activitypub/activity'
11import { APProcessorOptions } from '../../../types/activitypub-processor.model'
12import { MAccountDefault, MActorSignature, MCommentOwnerVideo } from '../../../types/models'
13
14async function processFlagActivity (options: APProcessorOptions<ActivityFlag>) {
15 const { activity, byActor } = options
16
17 return retryTransactionWrapper(processCreateAbuse, activity, byActor)
18}
19
20// ---------------------------------------------------------------------------
21
22export {
23 processFlagActivity
24}
25
26// ---------------------------------------------------------------------------
27
28async function processCreateAbuse (flag: ActivityFlag, byActor: MActorSignature) {
29 const account = byActor.Account
30 if (!account) throw new Error('Cannot create abuse with the non account actor ' + byActor.url)
31
32 const reporterAccount = await AccountModel.load(account.id)
33
34 const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ]
35
36 const tags = Array.isArray(flag.tag) ? flag.tag : []
37 const predefinedReasons = tags.map(tag => abusePredefinedReasonsMap[tag.name])
38 .filter(v => !isNaN(v))
39
40 const startAt = flag.startAt
41 const endAt = flag.endAt
42
43 for (const object of objects) {
44 try {
45 const uri = getAPId(object)
46
47 logger.debug('Reporting remote abuse for object %s.', uri)
48
49 await sequelizeTypescript.transaction(async t => {
50 const video = await VideoModel.loadByUrlAndPopulateAccount(uri, t)
51 let videoComment: MCommentOwnerVideo
52 let flaggedAccount: MAccountDefault
53
54 if (!video) videoComment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(uri, t)
55 if (!videoComment) flaggedAccount = await AccountModel.loadByUrl(uri, t)
56
57 if (!video && !videoComment && !flaggedAccount) {
58 logger.warn('Cannot flag unknown entity %s.', object)
59 return
60 }
61
62 const baseAbuse = {
63 reporterAccountId: reporterAccount.id,
64 reason: flag.content,
65 state: AbuseState.PENDING,
66 predefinedReasons
67 }
68
69 if (video) {
70 return createVideoAbuse({
71 baseAbuse,
72 startAt,
73 endAt,
74 reporterAccount,
75 transaction: t,
76 videoInstance: video,
77 skipNotification: false
78 })
79 }
80
81 if (videoComment) {
82 return createVideoCommentAbuse({
83 baseAbuse,
84 reporterAccount,
85 transaction: t,
86 commentInstance: videoComment,
87 skipNotification: false
88 })
89 }
90
91 return await createAccountAbuse({
92 baseAbuse,
93 reporterAccount,
94 transaction: t,
95 accountInstance: flaggedAccount,
96 skipNotification: false
97 })
98 })
99 } catch (err) {
100 logger.debug('Cannot process report of %s', getAPId(object), { err })
101 }
102 }
103}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
deleted file mode 100644
index 7def753d5..000000000
--- a/server/lib/activitypub/process/process-follow.ts
+++ /dev/null
@@ -1,156 +0,0 @@
1import { Transaction } from 'sequelize/types'
2import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
3import { AccountModel } from '@server/models/account/account'
4import { getServerActor } from '@server/models/application/application'
5import { ActivityFollow } from '../../../../shared/models/activitypub'
6import { retryTransactionWrapper } from '../../../helpers/database-utils'
7import { logger } from '../../../helpers/logger'
8import { CONFIG } from '../../../initializers/config'
9import { sequelizeTypescript } from '../../../initializers/database'
10import { getAPId } from '../../../lib/activitypub/activity'
11import { ActorModel } from '../../../models/actor/actor'
12import { ActorFollowModel } from '../../../models/actor/actor-follow'
13import { APProcessorOptions } from '../../../types/activitypub-processor.model'
14import { MActorFollow, MActorFull, MActorId, MActorSignature } from '../../../types/models'
15import { Notifier } from '../../notifier'
16import { autoFollowBackIfNeeded } from '../follow'
17import { sendAccept, sendReject } from '../send'
18
19async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) {
20 const { activity, byActor } = options
21
22 const activityId = activity.id
23 const objectId = getAPId(activity.object)
24
25 return retryTransactionWrapper(processFollow, byActor, activityId, objectId)
26}
27
28// ---------------------------------------------------------------------------
29
30export {
31 processFollowActivity
32}
33
34// ---------------------------------------------------------------------------
35
36async function processFollow (byActor: MActorSignature, activityId: string, targetActorURL: string) {
37 const { actorFollow, created, targetActor } = await sequelizeTypescript.transaction(async t => {
38 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
39
40 if (!targetActor) throw new Error('Unknown actor')
41 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
42
43 if (await rejectIfInstanceFollowDisabled(byActor, activityId, targetActor)) return { actorFollow: undefined }
44 if (await rejectIfMuted(byActor, activityId, targetActor)) return { actorFollow: undefined }
45
46 const [ actorFollow, created ] = await ActorFollowModel.findOrCreateCustom({
47 byActor,
48 targetActor,
49 activityId,
50 state: await isFollowingInstance(targetActor) && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
51 ? 'pending'
52 : 'accepted',
53 transaction: t
54 })
55
56 if (rejectIfAlreadyRejected(actorFollow, byActor, activityId, targetActor)) return { actorFollow: undefined }
57
58 await acceptIfNeeded(actorFollow, targetActor, t)
59
60 await fixFollowURLIfNeeded(actorFollow, activityId, t)
61
62 actorFollow.ActorFollower = byActor
63 actorFollow.ActorFollowing = targetActor
64
65 // Target sends to actor he accepted the follow request
66 if (actorFollow.state === 'accepted') {
67 sendAccept(actorFollow)
68
69 await autoFollowBackIfNeeded(actorFollow, t)
70 }
71
72 return { actorFollow, created, targetActor }
73 })
74
75 // Rejected
76 if (!actorFollow) return
77
78 if (created) {
79 const follower = await ActorModel.loadFull(byActor.id)
80 const actorFollowFull = Object.assign(actorFollow, { ActorFollowing: targetActor, ActorFollower: follower })
81
82 if (await isFollowingInstance(targetActor)) {
83 Notifier.Instance.notifyOfNewInstanceFollow(actorFollowFull)
84 } else {
85 Notifier.Instance.notifyOfNewUserFollow(actorFollowFull)
86 }
87 }
88
89 logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url)
90}
91
92async function rejectIfInstanceFollowDisabled (byActor: MActorSignature, activityId: string, targetActor: MActorFull) {
93 if (await isFollowingInstance(targetActor) && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) {
94 logger.info('Rejecting %s because instance followers are disabled.', targetActor.url)
95
96 sendReject(activityId, byActor, targetActor)
97
98 return true
99 }
100
101 return false
102}
103
104async function rejectIfMuted (byActor: MActorSignature, activityId: string, targetActor: MActorFull) {
105 const followerAccount = await AccountModel.load(byActor.Account.id)
106 const followingAccountId = targetActor.Account
107
108 if (followerAccount && await isBlockedByServerOrAccount(followerAccount, followingAccountId)) {
109 logger.info('Rejecting %s because follower is muted.', byActor.url)
110
111 sendReject(activityId, byActor, targetActor)
112
113 return true
114 }
115
116 return false
117}
118
119function rejectIfAlreadyRejected (actorFollow: MActorFollow, byActor: MActorSignature, activityId: string, targetActor: MActorFull) {
120 // Already rejected
121 if (actorFollow.state === 'rejected') {
122 logger.info('Rejecting %s because follow is already rejected.', byActor.url)
123
124 sendReject(activityId, byActor, targetActor)
125
126 return true
127 }
128
129 return false
130}
131
132async function acceptIfNeeded (actorFollow: MActorFollow, targetActor: MActorFull, transaction: Transaction) {
133 // Set the follow as accepted if the remote actor follows a channel or account
134 // Or if the instance automatically accepts followers
135 if (actorFollow.state === 'accepted') return
136 if (!await isFollowingInstance(targetActor)) return
137 if (CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === true && await isFollowingInstance(targetActor)) return
138
139 actorFollow.state = 'accepted'
140
141 await actorFollow.save({ transaction })
142}
143
144async function fixFollowURLIfNeeded (actorFollow: MActorFollow, activityId: string, transaction: Transaction) {
145 // Before PeerTube V3 we did not save the follow ID. Try to fix these old follows
146 if (!actorFollow.url) {
147 actorFollow.url = activityId
148 await actorFollow.save({ transaction })
149 }
150}
151
152async function isFollowingInstance (targetActor: MActorId) {
153 const serverActor = await getServerActor()
154
155 return targetActor.id === serverActor.id
156}
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
deleted file mode 100644
index 580a05bcd..000000000
--- a/server/lib/activitypub/process/process-like.ts
+++ /dev/null
@@ -1,60 +0,0 @@
1import { VideoModel } from '@server/models/video/video'
2import { ActivityLike } from '../../../../shared/models/activitypub'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { sequelizeTypescript } from '../../../initializers/database'
5import { getAPId } from '../../../lib/activitypub/activity'
6import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
7import { APProcessorOptions } from '../../../types/activitypub-processor.model'
8import { MActorSignature } from '../../../types/models'
9import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos'
10
11async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
12 const { activity, byActor } = options
13
14 return retryTransactionWrapper(processLikeVideo, byActor, activity)
15}
16
17// ---------------------------------------------------------------------------
18
19export {
20 processLikeActivity
21}
22
23// ---------------------------------------------------------------------------
24
25async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) {
26 const videoUrl = getAPId(activity.object)
27
28 const byAccount = byActor.Account
29 if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
30
31 const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video' })
32
33 // We don't care about likes of remote videos
34 if (!onlyVideo.isOwned()) return
35
36 return sequelizeTypescript.transaction(async t => {
37 const video = await VideoModel.loadFull(onlyVideo.id, t)
38
39 const existingRate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byAccount.id, video.id, activity.id, t)
40 if (existingRate && existingRate.type === 'like') return
41
42 if (existingRate && existingRate.type === 'dislike') {
43 await video.decrement('dislikes', { transaction: t })
44 video.dislikes--
45 }
46
47 await video.increment('likes', { transaction: t })
48 video.likes++
49
50 const rate = existingRate || new AccountVideoRateModel()
51 rate.type = 'like'
52 rate.videoId = video.id
53 rate.accountId = byAccount.id
54 rate.url = activity.id
55
56 await rate.save({ transaction: t })
57
58 await federateVideoIfNeeded(video, false, t)
59 })
60}
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts
deleted file mode 100644
index db7ff24d8..000000000
--- a/server/lib/activitypub/process/process-reject.ts
+++ /dev/null
@@ -1,33 +0,0 @@
1import { ActivityReject } from '../../../../shared/models/activitypub/activity'
2import { sequelizeTypescript } from '../../../initializers/database'
3import { ActorFollowModel } from '../../../models/actor/actor-follow'
4import { APProcessorOptions } from '../../../types/activitypub-processor.model'
5import { MActor } from '../../../types/models'
6
7async function processRejectActivity (options: APProcessorOptions<ActivityReject>) {
8 const { byActor: targetActor, inboxActor } = options
9 if (inboxActor === undefined) throw new Error('Need to reject on explicit inbox.')
10
11 return processReject(inboxActor, targetActor)
12}
13
14// ---------------------------------------------------------------------------
15
16export {
17 processRejectActivity
18}
19
20// ---------------------------------------------------------------------------
21
22async function processReject (follower: MActor, targetActor: MActor) {
23 return sequelizeTypescript.transaction(async t => {
24 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t)
25
26 if (!actorFollow) throw new Error(`'Unknown actor follow ${follower.id} -> ${targetActor.id}.`)
27
28 actorFollow.state = 'rejected'
29 await actorFollow.save({ transaction: t })
30
31 return undefined
32 })
33}
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
deleted file mode 100644
index a9d8199de..000000000
--- a/server/lib/activitypub/process/process-undo.ts
+++ /dev/null
@@ -1,183 +0,0 @@
1import { VideoModel } from '@server/models/video/video'
2import {
3 ActivityAnnounce,
4 ActivityCreate,
5 ActivityDislike,
6 ActivityFollow,
7 ActivityLike,
8 ActivityUndo,
9 ActivityUndoObject,
10 CacheFileObject
11} from '../../../../shared/models/activitypub'
12import { retryTransactionWrapper } from '../../../helpers/database-utils'
13import { logger } from '../../../helpers/logger'
14import { sequelizeTypescript } from '../../../initializers/database'
15import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
16import { ActorModel } from '../../../models/actor/actor'
17import { ActorFollowModel } from '../../../models/actor/actor-follow'
18import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
19import { VideoShareModel } from '../../../models/video/video-share'
20import { APProcessorOptions } from '../../../types/activitypub-processor.model'
21import { MActorSignature } from '../../../types/models'
22import { fetchAPObjectIfNeeded } from '../activity'
23import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
24import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos'
25
26async function processUndoActivity (options: APProcessorOptions<ActivityUndo<ActivityUndoObject>>) {
27 const { activity, byActor } = options
28 const activityToUndo = activity.object
29
30 if (activityToUndo.type === 'Like') {
31 return retryTransactionWrapper(processUndoLike, byActor, activity)
32 }
33
34 if (activityToUndo.type === 'Create') {
35 const objectToUndo = await fetchAPObjectIfNeeded<CacheFileObject>(activityToUndo.object)
36
37 if (objectToUndo.type === 'CacheFile') {
38 return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo)
39 }
40 }
41
42 if (activityToUndo.type === 'Dislike') {
43 return retryTransactionWrapper(processUndoDislike, byActor, activity)
44 }
45
46 if (activityToUndo.type === 'Follow') {
47 return retryTransactionWrapper(processUndoFollow, byActor, activityToUndo)
48 }
49
50 if (activityToUndo.type === 'Announce') {
51 return retryTransactionWrapper(processUndoAnnounce, byActor, activityToUndo)
52 }
53
54 logger.warn('Unknown activity object type %s -> %s when undo activity.', activityToUndo.type, { activity: activity.id })
55
56 return undefined
57}
58
59// ---------------------------------------------------------------------------
60
61export {
62 processUndoActivity
63}
64
65// ---------------------------------------------------------------------------
66
67async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo<ActivityLike>) {
68 const likeActivity = activity.object
69
70 const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: likeActivity.object })
71 // We don't care about likes of remote videos
72 if (!onlyVideo.isOwned()) return
73
74 return sequelizeTypescript.transaction(async t => {
75 if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
76
77 const video = await VideoModel.loadFull(onlyVideo.id, t)
78 const rate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byActor.Account.id, video.id, likeActivity.id, t)
79 if (!rate || rate.type !== 'like') {
80 logger.warn('Unknown like by account %d for video %d.', byActor.Account.id, video.id)
81 return
82 }
83
84 await rate.destroy({ transaction: t })
85 await video.decrement('likes', { transaction: t })
86
87 video.likes--
88 await federateVideoIfNeeded(video, false, t)
89 })
90}
91
92async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo<ActivityDislike>) {
93 const dislikeActivity = activity.object
94
95 const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: dislikeActivity.object })
96 // We don't care about likes of remote videos
97 if (!onlyVideo.isOwned()) return
98
99 return sequelizeTypescript.transaction(async t => {
100 if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
101
102 const video = await VideoModel.loadFull(onlyVideo.id, t)
103 const rate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byActor.Account.id, video.id, dislikeActivity.id, t)
104 if (!rate || rate.type !== 'dislike') {
105 logger.warn(`Unknown dislike by account %d for video %d.`, byActor.Account.id, video.id)
106 return
107 }
108
109 await rate.destroy({ transaction: t })
110 await video.decrement('dislikes', { transaction: t })
111 video.dislikes--
112
113 await federateVideoIfNeeded(video, false, t)
114 })
115}
116
117// ---------------------------------------------------------------------------
118
119async function processUndoCacheFile (
120 byActor: MActorSignature,
121 activity: ActivityUndo<ActivityCreate<CacheFileObject>>,
122 cacheFileObject: CacheFileObject
123) {
124 const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
125
126 return sequelizeTypescript.transaction(async t => {
127 const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
128 if (!cacheFile) {
129 logger.debug('Cannot undo unknown video cache %s.', cacheFileObject.id)
130 return
131 }
132
133 if (cacheFile.actorId !== byActor.id) throw new Error('Cannot delete redundancy ' + cacheFile.url + ' of another actor.')
134
135 await cacheFile.destroy({ transaction: t })
136
137 if (video.isOwned()) {
138 // Don't resend the activity to the sender
139 const exceptions = [ byActor ]
140
141 await forwardVideoRelatedActivity(activity, t, exceptions, video)
142 }
143 })
144}
145
146function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) {
147 return sequelizeTypescript.transaction(async t => {
148 const share = await VideoShareModel.loadByUrl(announceActivity.id, t)
149 if (!share) {
150 logger.warn('Unknown video share %d', announceActivity.id)
151 return
152 }
153
154 if (share.actorId !== byActor.id) throw new Error(`${share.url} is not shared by ${byActor.url}.`)
155
156 await share.destroy({ transaction: t })
157
158 if (share.Video.isOwned()) {
159 // Don't resend the activity to the sender
160 const exceptions = [ byActor ]
161
162 await forwardVideoRelatedActivity(announceActivity, t, exceptions, share.Video)
163 }
164 })
165}
166
167// ---------------------------------------------------------------------------
168
169function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) {
170 return sequelizeTypescript.transaction(async t => {
171 const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t)
172 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
173
174 if (!actorFollow) {
175 logger.warn('Unknown actor follow %d -> %d.', follower.id, following.id)
176 return
177 }
178
179 await actorFollow.destroy({ transaction: t })
180
181 return undefined
182 })
183}
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
deleted file mode 100644
index 304ed9de6..000000000
--- a/server/lib/activitypub/process/process-update.ts
+++ /dev/null
@@ -1,119 +0,0 @@
1import { isRedundancyAccepted } from '@server/lib/redundancy'
2import { ActivityUpdate, ActivityUpdateObject, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub'
3import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
4import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
5import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file'
6import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-validators/activitypub/videos'
7import { retryTransactionWrapper } from '../../../helpers/database-utils'
8import { logger } from '../../../helpers/logger'
9import { sequelizeTypescript } from '../../../initializers/database'
10import { ActorModel } from '../../../models/actor/actor'
11import { APProcessorOptions } from '../../../types/activitypub-processor.model'
12import { MActorFull, MActorSignature } from '../../../types/models'
13import { fetchAPObjectIfNeeded } from '../activity'
14import { APActorUpdater } from '../actors/updater'
15import { createOrUpdateCacheFile } from '../cache-file'
16import { createOrUpdateVideoPlaylist } from '../playlists'
17import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
18import { APVideoUpdater, getOrCreateAPVideo } from '../videos'
19
20async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
21 const { activity, byActor } = options
22
23 const object = await fetchAPObjectIfNeeded(activity.object)
24 const objectType = object.type
25
26 if (objectType === 'Video') {
27 return retryTransactionWrapper(processUpdateVideo, activity)
28 }
29
30 if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
31 // We need more attributes
32 const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url)
33 return retryTransactionWrapper(processUpdateActor, byActorFull, object)
34 }
35
36 if (objectType === 'CacheFile') {
37 // We need more attributes
38 const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url)
39 return retryTransactionWrapper(processUpdateCacheFile, byActorFull, activity, object)
40 }
41
42 if (objectType === 'Playlist') {
43 return retryTransactionWrapper(processUpdatePlaylist, byActor, activity, object)
44 }
45
46 return undefined
47}
48
49// ---------------------------------------------------------------------------
50
51export {
52 processUpdateActivity
53}
54
55// ---------------------------------------------------------------------------
56
57async function processUpdateVideo (activity: ActivityUpdate<VideoObject | string>) {
58 const videoObject = activity.object as VideoObject
59
60 if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
61 logger.debug('Video sent by update is not valid.', { videoObject })
62 return undefined
63 }
64
65 const { video, created } = await getOrCreateAPVideo({
66 videoObject: videoObject.id,
67 allowRefresh: false,
68 fetchType: 'all'
69 })
70 // We did not have this video, it has been created so no need to update
71 if (created) return
72
73 const updater = new APVideoUpdater(videoObject, video)
74 return updater.update(activity.to)
75}
76
77async function processUpdateCacheFile (
78 byActor: MActorSignature,
79 activity: ActivityUpdate<CacheFileObject | string>,
80 cacheFileObject: CacheFileObject
81) {
82 if (await isRedundancyAccepted(activity, byActor) !== true) return
83
84 if (!isCacheFileObjectValid(cacheFileObject)) {
85 logger.debug('Cache file object sent by update is not valid.', { cacheFileObject })
86 return undefined
87 }
88
89 const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
90
91 await sequelizeTypescript.transaction(async t => {
92 await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
93 })
94
95 if (video.isOwned()) {
96 // Don't resend the activity to the sender
97 const exceptions = [ byActor ]
98
99 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
100 }
101}
102
103async function processUpdateActor (actor: MActorFull, actorObject: ActivityPubActor) {
104 logger.debug('Updating remote account "%s".', actorObject.url)
105
106 const updater = new APActorUpdater(actorObject, actor)
107 return updater.update()
108}
109
110async function processUpdatePlaylist (
111 byActor: MActorSignature,
112 activity: ActivityUpdate<PlaylistObject | string>,
113 playlistObject: PlaylistObject
114) {
115 const byAccount = byActor.Account
116 if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url)
117
118 await createOrUpdateVideoPlaylist(playlistObject, activity.to)
119}
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts
deleted file mode 100644
index e49506d82..000000000
--- a/server/lib/activitypub/process/process-view.ts
+++ /dev/null
@@ -1,42 +0,0 @@
1import { VideoViewsManager } from '@server/lib/views/video-views-manager'
2import { ActivityView } from '../../../../shared/models/activitypub'
3import { APProcessorOptions } from '../../../types/activitypub-processor.model'
4import { MActorSignature } from '../../../types/models'
5import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
6import { getOrCreateAPVideo } from '../videos'
7
8async function processViewActivity (options: APProcessorOptions<ActivityView>) {
9 const { activity, byActor } = options
10
11 return processCreateView(activity, byActor)
12}
13
14// ---------------------------------------------------------------------------
15
16export {
17 processViewActivity
18}
19
20// ---------------------------------------------------------------------------
21
22async function processCreateView (activity: ActivityView, byActor: MActorSignature) {
23 const videoObject = activity.object
24
25 const { video } = await getOrCreateAPVideo({
26 videoObject,
27 fetchType: 'only-video',
28 allowRefresh: false
29 })
30
31 const viewerExpires = activity.expires
32 ? new Date(activity.expires)
33 : undefined
34
35 await VideoViewsManager.Instance.processRemoteView({ video, viewerId: activity.id, viewerExpires })
36
37 if (video.isOwned()) {
38 // Forward the view but don't resend the activity to the sender
39 const exceptions = [ byActor ]
40 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
41 }
42}
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
deleted file mode 100644
index 2bc3dce03..000000000
--- a/server/lib/activitypub/process/process.ts
+++ /dev/null
@@ -1,92 +0,0 @@
1import { StatsManager } from '@server/lib/stat-manager'
2import { Activity, ActivityType } from '../../../../shared/models/activitypub'
3import { logger } from '../../../helpers/logger'
4import { APProcessorOptions } from '../../../types/activitypub-processor.model'
5import { MActorDefault, MActorSignature } from '../../../types/models'
6import { getAPId } from '../activity'
7import { getOrCreateAPActor } from '../actors'
8import { checkUrlsSameHost } from '../url'
9import { processAcceptActivity } from './process-accept'
10import { processAnnounceActivity } from './process-announce'
11import { processCreateActivity } from './process-create'
12import { processDeleteActivity } from './process-delete'
13import { processDislikeActivity } from './process-dislike'
14import { processFlagActivity } from './process-flag'
15import { processFollowActivity } from './process-follow'
16import { processLikeActivity } from './process-like'
17import { processRejectActivity } from './process-reject'
18import { processUndoActivity } from './process-undo'
19import { processUpdateActivity } from './process-update'
20import { processViewActivity } from './process-view'
21
22const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = {
23 Create: processCreateActivity,
24 Update: processUpdateActivity,
25 Delete: processDeleteActivity,
26 Follow: processFollowActivity,
27 Accept: processAcceptActivity,
28 Reject: processRejectActivity,
29 Announce: processAnnounceActivity,
30 Undo: processUndoActivity,
31 Like: processLikeActivity,
32 Dislike: processDislikeActivity,
33 Flag: processFlagActivity,
34 View: processViewActivity
35}
36
37async function processActivities (
38 activities: Activity[],
39 options: {
40 signatureActor?: MActorSignature
41 inboxActor?: MActorDefault
42 outboxUrl?: string
43 fromFetch?: boolean
44 } = {}
45) {
46 const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options
47
48 const actorsCache: { [ url: string ]: MActorSignature } = {}
49
50 for (const activity of activities) {
51 if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
52 logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
53 continue
54 }
55
56 const actorUrl = getAPId(activity.actor)
57
58 // When we fetch remote data, we don't have signature
59 if (signatureActor && actorUrl !== signatureActor.url) {
60 logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, signatureActor.url)
61 continue
62 }
63
64 if (outboxUrl && checkUrlsSameHost(outboxUrl, actorUrl) !== true) {
65 logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', outboxUrl, actorUrl)
66 continue
67 }
68
69 const byActor = signatureActor || actorsCache[actorUrl] || await getOrCreateAPActor(actorUrl)
70 actorsCache[actorUrl] = byActor
71
72 const activityProcessor = processActivity[activity.type]
73 if (activityProcessor === undefined) {
74 logger.warn('Unknown activity type %s.', activity.type, { activityId: activity.id })
75 continue
76 }
77
78 try {
79 await activityProcessor({ activity, byActor, inboxActor, fromFetch })
80
81 StatsManager.Instance.addInboxProcessedSuccess(activity.type)
82 } catch (err) {
83 logger.warn('Cannot process activity %s.', activity.type, { err })
84
85 StatsManager.Instance.addInboxProcessedError(activity.type)
86 }
87 }
88}
89
90export {
91 processActivities
92}