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.ts8
-rw-r--r--server/lib/activitypub/process/process-accept.ts1
-rw-r--r--server/lib/activitypub/process/process-announce.ts8
-rw-r--r--server/lib/activitypub/process/process-create.ts122
-rw-r--r--server/lib/activitypub/process/process-dislike.ts52
-rw-r--r--server/lib/activitypub/process/process-flag.ts49
-rw-r--r--server/lib/activitypub/process/process-follow.ts14
-rw-r--r--server/lib/activitypub/process/process-like.ts6
-rw-r--r--server/lib/activitypub/process/process-undo.ts14
-rw-r--r--server/lib/activitypub/process/process-update.ts3
-rw-r--r--server/lib/activitypub/process/process-view.ts35
-rw-r--r--server/lib/activitypub/process/process.ts35
12 files changed, 228 insertions, 119 deletions
diff --git a/server/lib/activitypub/process/index.ts b/server/lib/activitypub/process/index.ts
index db4980a72..5466739c1 100644
--- a/server/lib/activitypub/process/index.ts
+++ b/server/lib/activitypub/process/index.ts
@@ -1,9 +1 @@
1export * from './process' export * from './process'
2export * from './process-accept'
3export * from './process-announce'
4export * from './process-create'
5export * from './process-delete'
6export * from './process-follow'
7export * from './process-like'
8export * from './process-undo'
9export * from './process-update'
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 89bda9c32..ebb275e34 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -24,6 +24,7 @@ async function processAccept (actor: ActorModel, targetActor: ActorModel) {
24 if (follow.state !== 'accepted') { 24 if (follow.state !== 'accepted') {
25 follow.set('state', 'accepted') 25 follow.set('state', 'accepted')
26 await follow.save() 26 await follow.save()
27
27 await addFetchOutboxJob(targetActor) 28 await addFetchOutboxJob(targetActor)
28 } 29 }
29} 30}
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index cc88b5423..23310b41e 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -5,6 +5,8 @@ import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoShareModel } from '../../../models/video/video-share' 5import { VideoShareModel } from '../../../models/video/video-share'
6import { forwardVideoRelatedActivity } from '../send/utils' 6import { forwardVideoRelatedActivity } from '../send/utils'
7import { getOrCreateVideoAndAccountAndChannel } from '../videos' 7import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { VideoPrivacy } from '../../../../shared/models/videos'
9import { Notifier } from '../../notifier'
8 10
9async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) { 11async function processAnnounceActivity (activity: ActivityAnnounce, actorAnnouncer: ActorModel) {
10 return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity) 12 return retryTransactionWrapper(processVideoShare, actorAnnouncer, activity)
@@ -21,9 +23,9 @@ export {
21async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) { 23async function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
22 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id 24 const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id
23 25
24 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri }) 26 const { video, created: videoCreated } = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri })
25 27
26 return sequelizeTypescript.transaction(async t => { 28 await sequelizeTypescript.transaction(async t => {
27 // Add share entry 29 // Add share entry
28 30
29 const share = { 31 const share = {
@@ -49,4 +51,6 @@ async function processVideoShare (actorAnnouncer: ActorModel, activity: Activity
49 51
50 return undefined 52 return undefined
51 }) 53 })
54
55 if (videoCreated) Notifier.Instance.notifyOnNewVideo(video)
52} 56}
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index cefe89db0..5f4d793a5 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,34 +1,44 @@
1import { ActivityCreate, CacheFileObject, VideoAbuseState, VideoTorrentObject } from '../../../../shared' 1import { ActivityCreate, CacheFileObject, VideoTorrentObject } from '../../../../shared'
2import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
3import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' 2import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
4import { retryTransactionWrapper } from '../../../helpers/database-utils' 3import { retryTransactionWrapper } from '../../../helpers/database-utils'
5import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
6import { sequelizeTypescript } from '../../../initializers' 5import { sequelizeTypescript } from '../../../initializers'
7import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
8import { ActorModel } from '../../../models/activitypub/actor' 6import { ActorModel } from '../../../models/activitypub/actor'
9import { VideoAbuseModel } from '../../../models/video/video-abuse'
10import { addVideoComment, resolveThread } from '../video-comments' 7import { addVideoComment, resolveThread } from '../video-comments'
11import { getOrCreateVideoAndAccountAndChannel } from '../videos' 8import { getOrCreateVideoAndAccountAndChannel } from '../videos'
12import { forwardVideoRelatedActivity } from '../send/utils' 9import { forwardVideoRelatedActivity } from '../send/utils'
13import { Redis } from '../../redis'
14import { createOrUpdateCacheFile } from '../cache-file' 10import { createOrUpdateCacheFile } from '../cache-file'
11import { Notifier } from '../../notifier'
12import { processViewActivity } from './process-view'
13import { processDislikeActivity } from './process-dislike'
14import { processFlagActivity } from './process-flag'
15 15
16async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { 16async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) {
17 const activityObject = activity.object 17 const activityObject = activity.object
18 const activityType = activityObject.type 18 const activityType = activityObject.type
19 19
20 if (activityType === 'View') { 20 if (activityType === 'View') {
21 return processCreateView(byActor, activity) 21 return processViewActivity(activity, byActor)
22 } else if (activityType === 'Dislike') { 22 }
23 return retryTransactionWrapper(processCreateDislike, byActor, activity) 23
24 } else if (activityType === 'Video') { 24 if (activityType === 'Dislike') {
25 return retryTransactionWrapper(processDislikeActivity, activity, byActor)
26 }
27
28 if (activityType === 'Flag') {
29 return retryTransactionWrapper(processFlagActivity, activity, byActor)
30 }
31
32 if (activityType === 'Video') {
25 return processCreateVideo(activity) 33 return processCreateVideo(activity)
26 } else if (activityType === 'Flag') { 34 }
27 return retryTransactionWrapper(processCreateVideoAbuse, byActor, activityObject as VideoAbuseObject) 35
28 } else if (activityType === 'Note') { 36 if (activityType === 'Note') {
29 return retryTransactionWrapper(processCreateVideoComment, byActor, activity) 37 return retryTransactionWrapper(processCreateVideoComment, activity, byActor)
30 } else if (activityType === 'CacheFile') { 38 }
31 return retryTransactionWrapper(processCacheFile, byActor, activity) 39
40 if (activityType === 'CacheFile') {
41 return retryTransactionWrapper(processCacheFile, activity, byActor)
32 } 42 }
33 43
34 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) 44 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -46,60 +56,14 @@ export {
46async function processCreateVideo (activity: ActivityCreate) { 56async function processCreateVideo (activity: ActivityCreate) {
47 const videoToCreateData = activity.object as VideoTorrentObject 57 const videoToCreateData = activity.object as VideoTorrentObject
48 58
49 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData }) 59 const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData })
50
51 return video
52}
53
54async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
55 const dislike = activity.object as DislikeObject
56 const byAccount = byActor.Account
57
58 if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
59
60 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
61
62 return sequelizeTypescript.transaction(async t => {
63 const rate = {
64 type: 'dislike' as 'dislike',
65 videoId: video.id,
66 accountId: byAccount.id
67 }
68 const [ , created ] = await AccountVideoRateModel.findOrCreate({
69 where: rate,
70 defaults: rate,
71 transaction: t
72 })
73 if (created === true) await video.increment('dislikes', { transaction: t })
74
75 if (video.isOwned() && created === true) {
76 // Don't resend the activity to the sender
77 const exceptions = [ byActor ]
78
79 await forwardVideoRelatedActivity(activity, t, exceptions, video)
80 }
81 })
82}
83
84async function processCreateView (byActor: ActorModel, activity: ActivityCreate) {
85 const view = activity.object as ViewObject
86
87 const options = {
88 videoObject: view.object,
89 fetchType: 'only-video' as 'only-video'
90 }
91 const { video } = await getOrCreateVideoAndAccountAndChannel(options)
92 60
93 await Redis.Instance.addVideoView(video.id) 61 if (created) Notifier.Instance.notifyOnNewVideo(video)
94 62
95 if (video.isOwned()) { 63 return video
96 // Don't resend the activity to the sender
97 const exceptions = [ byActor ]
98 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
99 }
100} 64}
101 65
102async function processCacheFile (byActor: ActorModel, activity: ActivityCreate) { 66async function processCacheFile (activity: ActivityCreate, byActor: ActorModel) {
103 const cacheFile = activity.object as CacheFileObject 67 const cacheFile = activity.object as CacheFileObject
104 68
105 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) 69 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
@@ -115,29 +79,7 @@ async function processCacheFile (byActor: ActorModel, activity: ActivityCreate)
115 } 79 }
116} 80}
117 81
118async function processCreateVideoAbuse (byActor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) { 82async function processCreateVideoComment (activity: ActivityCreate, byActor: ActorModel) {
119 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
120
121 const account = byActor.Account
122 if (!account) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
123
124 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoAbuseToCreateData.object })
125
126 return sequelizeTypescript.transaction(async t => {
127 const videoAbuseData = {
128 reporterAccountId: account.id,
129 reason: videoAbuseToCreateData.content,
130 videoId: video.id,
131 state: VideoAbuseState.PENDING
132 }
133
134 await VideoAbuseModel.create(videoAbuseData, { transaction: t })
135
136 logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
137 })
138}
139
140async function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) {
141 const commentObject = activity.object as VideoCommentObject 83 const commentObject = activity.object as VideoCommentObject
142 const byAccount = byActor.Account 84 const byAccount = byActor.Account
143 85
@@ -145,7 +87,7 @@ async function processCreateVideoComment (byActor: ActorModel, activity: Activit
145 87
146 const { video } = await resolveThread(commentObject.inReplyTo) 88 const { video } = await resolveThread(commentObject.inReplyTo)
147 89
148 const { created } = await addVideoComment(video, commentObject.id) 90 const { comment, created } = await addVideoComment(video, commentObject.id)
149 91
150 if (video.isOwned() && created === true) { 92 if (video.isOwned() && created === true) {
151 // Don't resend the activity to the sender 93 // Don't resend the activity to the sender
@@ -153,4 +95,6 @@ async function processCreateVideoComment (byActor: ActorModel, activity: Activit
153 95
154 await forwardVideoRelatedActivity(activity, undefined, exceptions, video) 96 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
155 } 97 }
98
99 if (created === true) Notifier.Instance.notifyOnNewComment(comment)
156} 100}
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts
new file mode 100644
index 000000000..bfd69e07a
--- /dev/null
+++ b/server/lib/activitypub/process/process-dislike.ts
@@ -0,0 +1,52 @@
1import { ActivityCreate, ActivityDislike } from '../../../../shared'
2import { DislikeObject } from '../../../../shared/models/activitypub/objects'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { sequelizeTypescript } from '../../../initializers'
5import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
6import { ActorModel } from '../../../models/activitypub/actor'
7import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { forwardVideoRelatedActivity } from '../send/utils'
9import { getVideoDislikeActivityPubUrl } from '../url'
10
11async function processDislikeActivity (activity: ActivityCreate | ActivityDislike, byActor: ActorModel) {
12 return retryTransactionWrapper(processDislike, activity, byActor)
13}
14
15// ---------------------------------------------------------------------------
16
17export {
18 processDislikeActivity
19}
20
21// ---------------------------------------------------------------------------
22
23async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: ActorModel) {
24 const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).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 } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislikeObject })
30
31 return sequelizeTypescript.transaction(async t => {
32 const rate = {
33 type: 'dislike' as 'dislike',
34 videoId: video.id,
35 accountId: byAccount.id
36 }
37
38 const [ , created ] = await AccountVideoRateModel.findOrCreate({
39 where: rate,
40 defaults: Object.assign({}, rate, { url: getVideoDislikeActivityPubUrl(byActor, video) }),
41 transaction: t
42 })
43 if (created === true) await video.increment('dislikes', { transaction: t })
44
45 if (video.isOwned() && created === true) {
46 // Don't resend the activity to the sender
47 const exceptions = [ byActor ]
48
49 await forwardVideoRelatedActivity(activity, t, exceptions, video)
50 }
51 })
52}
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts
new file mode 100644
index 000000000..79ce6fb41
--- /dev/null
+++ b/server/lib/activitypub/process/process-flag.ts
@@ -0,0 +1,49 @@
1import { ActivityCreate, ActivityFlag, VideoAbuseState } from '../../../../shared'
2import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects'
3import { retryTransactionWrapper } from '../../../helpers/database-utils'
4import { logger } from '../../../helpers/logger'
5import { sequelizeTypescript } from '../../../initializers'
6import { ActorModel } from '../../../models/activitypub/actor'
7import { VideoAbuseModel } from '../../../models/video/video-abuse'
8import { getOrCreateVideoAndAccountAndChannel } from '../videos'
9import { Notifier } from '../../notifier'
10import { getAPId } from '../../../helpers/activitypub'
11
12async function processFlagActivity (activity: ActivityCreate | ActivityFlag, byActor: ActorModel) {
13 return retryTransactionWrapper(processCreateVideoAbuse, activity, byActor)
14}
15
16// ---------------------------------------------------------------------------
17
18export {
19 processFlagActivity
20}
21
22// ---------------------------------------------------------------------------
23
24async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: ActorModel) {
25 const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject)
26
27 logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object))
28
29 const account = byActor.Account
30 if (!account) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
31
32 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object })
33
34 return sequelizeTypescript.transaction(async t => {
35 const videoAbuseData = {
36 reporterAccountId: account.id,
37 reason: flag.content,
38 videoId: video.id,
39 state: VideoAbuseState.PENDING
40 }
41
42 const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t })
43 videoAbuseInstance.Video = video
44
45 Notifier.Instance.notifyOnNewVideoAbuse(videoAbuseInstance)
46
47 logger.info('Remote abuse for video uuid %s created', flag.object)
48 })
49}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index 24c9085f7..0cd537187 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -5,9 +5,11 @@ import { sequelizeTypescript } from '../../../initializers'
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/activitypub/actor'
6import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 6import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
7import { sendAccept } from '../send' 7import { sendAccept } from '../send'
8import { Notifier } from '../../notifier'
9import { getAPId } from '../../../helpers/activitypub'
8 10
9async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) { 11async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) {
10 const activityObject = activity.object 12 const activityObject = getAPId(activity.object)
11 13
12 return retryTransactionWrapper(processFollow, byActor, activityObject) 14 return retryTransactionWrapper(processFollow, byActor, activityObject)
13} 15}
@@ -21,13 +23,13 @@ export {
21// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
22 24
23async function processFollow (actor: ActorModel, targetActorURL: string) { 25async function processFollow (actor: ActorModel, targetActorURL: string) {
24 await sequelizeTypescript.transaction(async t => { 26 const { actorFollow, created } = await sequelizeTypescript.transaction(async t => {
25 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) 27 const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t)
26 28
27 if (!targetActor) throw new Error('Unknown actor') 29 if (!targetActor) throw new Error('Unknown actor')
28 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.') 30 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
29 31
30 const [ actorFollow ] = await ActorFollowModel.findOrCreate({ 32 const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({
31 where: { 33 where: {
32 actorId: actor.id, 34 actorId: actor.id,
33 targetActorId: targetActor.id 35 targetActorId: targetActor.id
@@ -52,8 +54,12 @@ async function processFollow (actor: ActorModel, targetActorURL: string) {
52 actorFollow.ActorFollowing = targetActor 54 actorFollow.ActorFollowing = targetActor
53 55
54 // Target sends to actor he accepted the follow request 56 // Target sends to actor he accepted the follow request
55 return sendAccept(actorFollow) 57 await sendAccept(actorFollow)
58
59 return { actorFollow, created }
56 }) 60 })
57 61
62 if (created) Notifier.Instance.notifyOfNewFollow(actorFollow)
63
58 logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url) 64 logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url)
59} 65}
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index f7200db61..2a04167d7 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -5,6 +5,8 @@ import { AccountVideoRateModel } from '../../../models/account/account-video-rat
5import { ActorModel } from '../../../models/activitypub/actor' 5import { ActorModel } from '../../../models/activitypub/actor'
6import { forwardVideoRelatedActivity } from '../send/utils' 6import { forwardVideoRelatedActivity } from '../send/utils'
7import { getOrCreateVideoAndAccountAndChannel } from '../videos' 7import { getOrCreateVideoAndAccountAndChannel } from '../videos'
8import { getVideoLikeActivityPubUrl } from '../url'
9import { getAPId } from '../../../helpers/activitypub'
8 10
9async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) { 11async function processLikeActivity (activity: ActivityLike, byActor: ActorModel) {
10 return retryTransactionWrapper(processLikeVideo, byActor, activity) 12 return retryTransactionWrapper(processLikeVideo, byActor, activity)
@@ -19,7 +21,7 @@ export {
19// --------------------------------------------------------------------------- 21// ---------------------------------------------------------------------------
20 22
21async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) { 23async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) {
22 const videoUrl = activity.object 24 const videoUrl = getAPId(activity.object)
23 25
24 const byAccount = byActor.Account 26 const byAccount = byActor.Account
25 if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url) 27 if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
@@ -34,7 +36,7 @@ async function processLikeVideo (byActor: ActorModel, activity: ActivityLike) {
34 } 36 }
35 const [ , created ] = await AccountVideoRateModel.findOrCreate({ 37 const [ , created ] = await AccountVideoRateModel.findOrCreate({
36 where: rate, 38 where: rate,
37 defaults: rate, 39 defaults: Object.assign({}, rate, { url: getVideoLikeActivityPubUrl(byActor, video) }),
38 transaction: t 40 transaction: t
39 }) 41 })
40 if (created === true) await video.increment('likes', { transaction: t }) 42 if (created === true) await video.increment('likes', { transaction: t })
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index ff019cd8c..ed0177a67 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -26,6 +26,10 @@ async function processUndoActivity (activity: ActivityUndo, byActor: ActorModel)
26 } 26 }
27 } 27 }
28 28
29 if (activityToUndo.type === 'Dislike') {
30 return retryTransactionWrapper(processUndoDislike, byActor, activity)
31 }
32
29 if (activityToUndo.type === 'Follow') { 33 if (activityToUndo.type === 'Follow') {
30 return retryTransactionWrapper(processUndoFollow, byActor, activityToUndo) 34 return retryTransactionWrapper(processUndoFollow, byActor, activityToUndo)
31 } 35 }
@@ -55,7 +59,8 @@ async function processUndoLike (byActor: ActorModel, activity: ActivityUndo) {
55 return sequelizeTypescript.transaction(async t => { 59 return sequelizeTypescript.transaction(async t => {
56 if (!byActor.Account) throw new Error('Unknown account ' + byActor.url) 60 if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
57 61
58 const rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t) 62 let rate = await AccountVideoRateModel.loadByUrl(likeActivity.id, t)
63 if (!rate) rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t)
59 if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`) 64 if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`)
60 65
61 await rate.destroy({ transaction: t }) 66 await rate.destroy({ transaction: t })
@@ -71,14 +76,17 @@ async function processUndoLike (byActor: ActorModel, activity: ActivityUndo) {
71} 76}
72 77
73async function processUndoDislike (byActor: ActorModel, activity: ActivityUndo) { 78async function processUndoDislike (byActor: ActorModel, activity: ActivityUndo) {
74 const dislike = activity.object.object as DislikeObject 79 const dislike = activity.object.type === 'Dislike'
80 ? activity.object
81 : activity.object.object as DislikeObject
75 82
76 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object }) 83 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
77 84
78 return sequelizeTypescript.transaction(async t => { 85 return sequelizeTypescript.transaction(async t => {
79 if (!byActor.Account) throw new Error('Unknown account ' + byActor.url) 86 if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
80 87
81 const rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t) 88 let rate = await AccountVideoRateModel.loadByUrl(dislike.id, t)
89 if (!rate) rate = await AccountVideoRateModel.load(byActor.Account.id, video.id, t)
82 if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`) 90 if (!rate) throw new Error(`Unknown rate by account ${byActor.Account.id} for video ${video.id}.`)
83 91
84 await rate.destroy({ transaction: t }) 92 await rate.destroy({ transaction: t })
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index bd4013555..c6b42d846 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -51,7 +51,7 @@ async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate)
51 return undefined 51 return undefined
52 } 52 }
53 53
54 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id }) 54 const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false })
55 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) 55 const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject)
56 56
57 const updateOptions = { 57 const updateOptions = {
@@ -59,7 +59,6 @@ async function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate)
59 videoObject, 59 videoObject,
60 account: actor.Account, 60 account: actor.Account,
61 channel: channelActor.VideoChannel, 61 channel: channelActor.VideoChannel,
62 updateViews: true,
63 overrideTo: activity.to 62 overrideTo: activity.to
64 } 63 }
65 return updateVideoFromAP(updateOptions) 64 return updateVideoFromAP(updateOptions)
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts
new file mode 100644
index 000000000..8f66d3630
--- /dev/null
+++ b/server/lib/activitypub/process/process-view.ts
@@ -0,0 +1,35 @@
1import { ActorModel } from '../../../models/activitypub/actor'
2import { getOrCreateVideoAndAccountAndChannel } from '../videos'
3import { forwardVideoRelatedActivity } from '../send/utils'
4import { Redis } from '../../redis'
5import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
6
7async function processViewActivity (activity: ActivityView | ActivityCreate, byActor: ActorModel) {
8 return processCreateView(activity, byActor)
9}
10
11// ---------------------------------------------------------------------------
12
13export {
14 processViewActivity
15}
16
17// ---------------------------------------------------------------------------
18
19async function processCreateView (activity: ActivityView | ActivityCreate, byActor: ActorModel) {
20 const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object
21
22 const options = {
23 videoObject: videoObject,
24 fetchType: 'only-video' as 'only-video'
25 }
26 const { video } = await getOrCreateVideoAndAccountAndChannel(options)
27
28 await Redis.Instance.addVideoView(video.id)
29
30 if (video.isOwned()) {
31 // Don't resend the activity to the sender
32 const exceptions = [ byActor ]
33 await forwardVideoRelatedActivity(activity, undefined, exceptions, video)
34 }
35}
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
index b263f1ea2..9dd241402 100644
--- a/server/lib/activitypub/process/process.ts
+++ b/server/lib/activitypub/process/process.ts
@@ -1,5 +1,5 @@
1import { Activity, ActivityType } from '../../../../shared/models/activitypub' 1import { Activity, ActivityType } from '../../../../shared/models/activitypub'
2import { getActorUrl } from '../../../helpers/activitypub' 2import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { ActorModel } from '../../../models/activitypub/actor' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { processAcceptActivity } from './process-accept' 5import { processAcceptActivity } from './process-accept'
@@ -12,6 +12,9 @@ import { processRejectActivity } from './process-reject'
12import { processUndoActivity } from './process-undo' 12import { processUndoActivity } from './process-undo'
13import { processUpdateActivity } from './process-update' 13import { processUpdateActivity } from './process-update'
14import { getOrCreateActorAndServerAndModel } from '../actor' 14import { getOrCreateActorAndServerAndModel } from '../actor'
15import { processDislikeActivity } from './process-dislike'
16import { processFlagActivity } from './process-flag'
17import { processViewActivity } from './process-view'
15 18
16const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: ActorModel, inboxActor?: ActorModel) => Promise<any> } = { 19const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: ActorModel, inboxActor?: ActorModel) => Promise<any> } = {
17 Create: processCreateActivity, 20 Create: processCreateActivity,
@@ -22,27 +25,41 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, byActor: Ac
22 Reject: processRejectActivity, 25 Reject: processRejectActivity,
23 Announce: processAnnounceActivity, 26 Announce: processAnnounceActivity,
24 Undo: processUndoActivity, 27 Undo: processUndoActivity,
25 Like: processLikeActivity 28 Like: processLikeActivity,
29 Dislike: processDislikeActivity,
30 Flag: processFlagActivity,
31 View: processViewActivity
26} 32}
27 33
28async function processActivities (activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel) { 34async function processActivities (
35 activities: Activity[],
36 options: {
37 signatureActor?: ActorModel
38 inboxActor?: ActorModel
39 outboxUrl?: string
40 } = {}) {
29 const actorsCache: { [ url: string ]: ActorModel } = {} 41 const actorsCache: { [ url: string ]: ActorModel } = {}
30 42
31 for (const activity of activities) { 43 for (const activity of activities) {
32 if (!signatureActor && [ 'Create', 'Announce', 'Like' ].indexOf(activity.type) === -1) { 44 if (!options.signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) {
33 logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type) 45 logger.error('Cannot process activity %s (type: %s) without the actor signature.', activity.id, activity.type)
34 continue 46 continue
35 } 47 }
36 48
37 const actorUrl = getActorUrl(activity.actor) 49 const actorUrl = getAPId(activity.actor)
38 50
39 // When we fetch remote data, we don't have signature 51 // When we fetch remote data, we don't have signature
40 if (signatureActor && actorUrl !== signatureActor.url) { 52 if (options.signatureActor && actorUrl !== options.signatureActor.url) {
41 logger.warn('Signature mismatch between %s and %s.', actorUrl, signatureActor.url) 53 logger.warn('Signature mismatch between %s and %s, skipping.', actorUrl, options.signatureActor.url)
42 continue 54 continue
43 } 55 }
44 56
45 const byActor = signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl) 57 if (options.outboxUrl && checkUrlsSameHost(options.outboxUrl, actorUrl) !== true) {
58 logger.warn('Host mismatch between outbox URL %s and actor URL %s, skipping.', options.outboxUrl, actorUrl)
59 continue
60 }
61
62 const byActor = options.signatureActor || actorsCache[actorUrl] || await getOrCreateActorAndServerAndModel(actorUrl)
46 actorsCache[actorUrl] = byActor 63 actorsCache[actorUrl] = byActor
47 64
48 const activityProcessor = processActivity[activity.type] 65 const activityProcessor = processActivity[activity.type]
@@ -52,7 +69,7 @@ async function processActivities (activities: Activity[], signatureActor?: Actor
52 } 69 }
53 70
54 try { 71 try {
55 await activityProcessor(activity, byActor, inboxActor) 72 await activityProcessor(activity, byActor, options.inboxActor)
56 } catch (err) { 73 } catch (err) {
57 logger.warn('Cannot process activity %s.', activity.type, { err }) 74 logger.warn('Cannot process activity %s.', activity.type, { err })
58 } 75 }