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/misc.ts52
-rw-r--r--server/lib/activitypub/process/process-accept.ts18
-rw-r--r--server/lib/activitypub/process/process-add.ts137
-rw-r--r--server/lib/activitypub/process/process-announce.ts67
-rw-r--r--server/lib/activitypub/process/process-create.ts187
-rw-r--r--server/lib/activitypub/process/process-delete.ts72
-rw-r--r--server/lib/activitypub/process/process-follow.ts48
-rw-r--r--server/lib/activitypub/process/process-like.ts19
-rw-r--r--server/lib/activitypub/process/process-undo.ts43
-rw-r--r--server/lib/activitypub/process/process-update.ts63
-rw-r--r--server/lib/activitypub/process/process.ts14
12 files changed, 272 insertions, 449 deletions
diff --git a/server/lib/activitypub/process/index.ts b/server/lib/activitypub/process/index.ts
index e25c261cc..db4980a72 100644
--- a/server/lib/activitypub/process/index.ts
+++ b/server/lib/activitypub/process/index.ts
@@ -1,6 +1,5 @@
1export * from './process' 1export * from './process'
2export * from './process-accept' 2export * from './process-accept'
3export * from './process-add'
4export * from './process-announce' 3export * from './process-announce'
5export * from './process-create' 4export * from './process-create'
6export * from './process-delete' 5export * from './process-delete'
diff --git a/server/lib/activitypub/process/misc.ts b/server/lib/activitypub/process/misc.ts
index a775c858a..a9c6f913c 100644
--- a/server/lib/activitypub/process/misc.ts
+++ b/server/lib/activitypub/process/misc.ts
@@ -1,29 +1,13 @@
1import * as magnetUtil from 'magnet-uri' 1import * as magnetUtil from 'magnet-uri'
2import { VideoTorrentObject } from '../../../../shared' 2import { VideoTorrentObject } from '../../../../shared'
3import { VideoChannelObject } from '../../../../shared/models/activitypub/objects'
4import { VideoPrivacy } from '../../../../shared/models/videos' 3import { VideoPrivacy } from '../../../../shared/models/videos'
5import { doRequest } from '../../../helpers' 4import { doRequest } from '../../../helpers'
6import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos' 5import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
7import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers' 6import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers'
8import { AccountModel } from '../../../models/account/account'
9import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
10import { VideoChannelModel } from '../../../models/video/video-channel' 8import { VideoChannelModel } from '../../../models/video/video-channel'
11import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
12import { VideoShareModel } from '../../../models/video/video-share' 9import { VideoShareModel } from '../../../models/video/video-share'
13import { getOrCreateAccountAndServer } from '../account' 10import { getOrCreateActorAndServerAndModel } from '../actor'
14
15function videoChannelActivityObjectToDBAttributes (videoChannelObject: VideoChannelObject, account: AccountModel) {
16 return {
17 name: videoChannelObject.name,
18 description: videoChannelObject.content,
19 uuid: videoChannelObject.uuid,
20 url: videoChannelObject.id,
21 createdAt: new Date(videoChannelObject.published),
22 updatedAt: new Date(videoChannelObject.updated),
23 remote: true,
24 accountId: account.id
25 }
26}
27 11
28async function videoActivityObjectToDBAttributes ( 12async function videoActivityObjectToDBAttributes (
29 videoChannel: VideoChannelModel, 13 videoChannel: VideoChannelModel,
@@ -120,13 +104,13 @@ async function addVideoShares (instance: VideoModel, shares: string[]) {
120 uri: share, 104 uri: share,
121 json: true 105 json: true
122 }) 106 })
123 const actor = json['actor'] 107 const actorUrl = json['actor']
124 if (!actor) continue 108 if (!actorUrl) continue
125 109
126 const account = await getOrCreateAccountAndServer(actor) 110 const actor = await getOrCreateActorAndServerAndModel(actorUrl)
127 111
128 const entry = { 112 const entry = {
129 accountId: account.id, 113 actorId: actor.id,
130 videoId: instance.id 114 videoId: instance.id
131 } 115 }
132 116
@@ -137,36 +121,10 @@ async function addVideoShares (instance: VideoModel, shares: string[]) {
137 } 121 }
138} 122}
139 123
140async function addVideoChannelShares (instance: VideoChannelModel, shares: string[]) {
141 for (const share of shares) {
142 // Fetch url
143 const json = await doRequest({
144 uri: share,
145 json: true
146 })
147 const actor = json['actor']
148 if (!actor) continue
149
150 const account = await getOrCreateAccountAndServer(actor)
151
152 const entry = {
153 accountId: account.id,
154 videoChannelId: instance.id
155 }
156
157 await VideoChannelShareModel.findOrCreate({
158 where: entry,
159 defaults: entry
160 })
161 }
162}
163
164// --------------------------------------------------------------------------- 124// ---------------------------------------------------------------------------
165 125
166export { 126export {
167 videoFileActivityUrlToDBAttributes, 127 videoFileActivityUrlToDBAttributes,
168 videoActivityObjectToDBAttributes, 128 videoActivityObjectToDBAttributes,
169 videoChannelActivityObjectToDBAttributes,
170 addVideoChannelShares,
171 addVideoShares 129 addVideoShares
172} 130}
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts
index 5b321f771..b9d906ec9 100644
--- a/server/lib/activitypub/process/process-accept.ts
+++ b/server/lib/activitypub/process/process-accept.ts
@@ -1,14 +1,14 @@
1import { ActivityAccept } from '../../../../shared/models/activitypub' 1import { ActivityAccept } from '../../../../shared/models/activitypub'
2import { AccountModel } from '../../../models/account/account' 2import { ActorModel } from '../../../models/activitypub/actor'
3import { AccountFollowModel } from '../../../models/account/account-follow' 3import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
4import { addFetchOutboxJob } from '../fetch' 4import { addFetchOutboxJob } from '../fetch'
5 5
6async function processAcceptActivity (activity: ActivityAccept, inboxAccount?: AccountModel) { 6async function processAcceptActivity (activity: ActivityAccept, inboxActor?: ActorModel) {
7 if (inboxAccount === undefined) throw new Error('Need to accept on explicit inbox.') 7 if (inboxActor === undefined) throw new Error('Need to accept on explicit inbox.')
8 8
9 const targetAccount = await AccountModel.loadByUrl(activity.actor) 9 const targetActor = await ActorModel.loadByUrl(activity.actor)
10 10
11 return processAccept(inboxAccount, targetAccount) 11 return processAccept(inboxActor, targetActor)
12} 12}
13 13
14// --------------------------------------------------------------------------- 14// ---------------------------------------------------------------------------
@@ -19,11 +19,11 @@ export {
19 19
20// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
21 21
22async function processAccept (account: AccountModel, targetAccount: AccountModel) { 22async function processAccept (actor: ActorModel, targetActor: ActorModel) {
23 const follow = await AccountFollowModel.loadByAccountAndTarget(account.id, targetAccount.id) 23 const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id)
24 if (!follow) throw new Error('Cannot find associated follow.') 24 if (!follow) throw new Error('Cannot find associated follow.')
25 25
26 follow.set('state', 'accepted') 26 follow.set('state', 'accepted')
27 await follow.save() 27 await follow.save()
28 await addFetchOutboxJob(targetAccount, undefined) 28 await addFetchOutboxJob(targetActor, undefined)
29} 29}
diff --git a/server/lib/activitypub/process/process-add.ts b/server/lib/activitypub/process/process-add.ts
deleted file mode 100644
index 550593eab..000000000
--- a/server/lib/activitypub/process/process-add.ts
+++ /dev/null
@@ -1,137 +0,0 @@
1import * as Bluebird from 'bluebird'
2import { VideoTorrentObject } from '../../../../shared'
3import { ActivityAdd } from '../../../../shared/models/activitypub'
4import { VideoRateType } from '../../../../shared/models/videos'
5import { logger, retryTransactionWrapper } from '../../../helpers'
6import { sequelizeTypescript } from '../../../initializers'
7import { AccountModel } from '../../../models/account/account'
8import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
9import { TagModel } from '../../../models/video/tag'
10import { VideoModel } from '../../../models/video/video'
11import { VideoChannelModel } from '../../../models/video/video-channel'
12import { VideoFileModel } from '../../../models/video/video-file'
13import { getOrCreateAccountAndServer } from '../account'
14import { getOrCreateVideoChannel } from '../video-channels'
15import { generateThumbnailFromUrl } from '../videos'
16import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
17
18async function processAddActivity (activity: ActivityAdd) {
19 const activityObject = activity.object
20 const activityType = activityObject.type
21 const account = await getOrCreateAccountAndServer(activity.actor)
22
23 if (activityType === 'Video') {
24 const videoChannelUrl = activity.target
25 const videoChannel = await getOrCreateVideoChannel(account, videoChannelUrl)
26
27 return processAddVideo(account, activity, videoChannel, activityObject)
28 }
29
30 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
31 return Promise.resolve(undefined)
32}
33
34// ---------------------------------------------------------------------------
35
36export {
37 processAddActivity
38}
39
40// ---------------------------------------------------------------------------
41
42async function processAddVideo (account: AccountModel,
43 activity: ActivityAdd,
44 videoChannel: VideoChannelModel,
45 videoToCreateData: VideoTorrentObject) {
46 const options = {
47 arguments: [ account, activity, videoChannel, videoToCreateData ],
48 errorMessage: 'Cannot insert the remote video with many retries.'
49 }
50
51 const video = await retryTransactionWrapper(addRemoteVideo, options)
52
53 // Process outside the transaction because we could fetch remote data
54 if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
55 await createRates(videoToCreateData.likes.orderedItems, video, 'like')
56 }
57
58 if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
59 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
60 }
61
62 if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
63 await addVideoShares(video, videoToCreateData.shares.orderedItems)
64 }
65
66 return video
67}
68
69function addRemoteVideo (account: AccountModel,
70 activity: ActivityAdd,
71 videoChannel: VideoChannelModel,
72 videoToCreateData: VideoTorrentObject) {
73 logger.debug('Adding remote video %s.', videoToCreateData.id)
74
75 return sequelizeTypescript.transaction(async t => {
76 const sequelizeOptions = {
77 transaction: t
78 }
79
80 if (videoChannel.Account.id !== account.id) throw new Error('Video channel is not owned by this account.')
81
82 const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
83 if (videoFromDatabase) return videoFromDatabase
84
85 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoToCreateData, activity.to, activity.cc)
86 const video = VideoModel.build(videoData)
87
88 // Don't block on request
89 generateThumbnailFromUrl(video, videoToCreateData.icon)
90 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
91
92 const videoCreated = await video.save(sequelizeOptions)
93
94 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
95 if (videoFileAttributes.length === 0) {
96 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
97 }
98
99 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
100 await Promise.all(tasks)
101
102 const tags = videoToCreateData.tag.map(t => t.name)
103 const tagInstances = await TagModel.findOrCreateTags(tags, t)
104 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
105
106 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
107
108 return videoCreated
109 })
110}
111
112async function createRates (accountUrls: string[], video: VideoModel, rate: VideoRateType) {
113 let rateCounts = 0
114 const tasks: Bluebird<any>[] = []
115
116 for (const accountUrl of accountUrls) {
117 const account = await getOrCreateAccountAndServer(accountUrl)
118 const p = AccountVideoRateModel
119 .create({
120 videoId: video.id,
121 accountId: account.id,
122 type: rate
123 })
124 .then(() => rateCounts += 1)
125
126 tasks.push(p)
127 }
128
129 await Promise.all(tasks)
130
131 logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
132
133 // This is "likes" and "dislikes"
134 await video.increment(rate + 's', { by: rateCounts })
135
136 return
137}
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts
index ff2c6d708..7dfee2f60 100644
--- a/server/lib/activitypub/process/process-announce.ts
+++ b/server/lib/activitypub/process/process-announce.ts
@@ -1,24 +1,19 @@
1import { ActivityAdd, ActivityAnnounce, ActivityCreate } from '../../../../shared/models/activitypub' 1import { ActivityAnnounce } from '../../../../shared/models/activitypub'
2import { logger, retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 5import { VideoModel } from '../../../models/video/video'
6import { VideoChannelModel } from '../../../models/video/video-channel'
7import { VideoChannelShareModel } from '../../../models/video/video-channel-share'
8import { VideoShareModel } from '../../../models/video/video-share' 6import { VideoShareModel } from '../../../models/video/video-share'
9import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateActorAndServerAndModel } from '../actor'
10import { forwardActivity } from '../send/misc' 8import { forwardActivity } from '../send/misc'
11import { processAddActivity } from './process-add'
12import { processCreateActivity } from './process-create' 9import { processCreateActivity } from './process-create'
13 10
14async function processAnnounceActivity (activity: ActivityAnnounce) { 11async function processAnnounceActivity (activity: ActivityAnnounce) {
15 const announcedActivity = activity.object 12 const announcedActivity = activity.object
16 const accountAnnouncer = await getOrCreateAccountAndServer(activity.actor) 13 const actorAnnouncer = await getOrCreateActorAndServerAndModel(activity.actor)
17 14
18 if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'VideoChannel') { 15 if (announcedActivity.type === 'Create' && announcedActivity.object.type === 'Video') {
19 return processVideoChannelShare(accountAnnouncer, activity) 16 return processVideoShare(actorAnnouncer, activity)
20 } else if (announcedActivity.type === 'Add' && announcedActivity.object.type === 'Video') {
21 return processVideoShare(accountAnnouncer, activity)
22 } 17 }
23 18
24 logger.warn( 19 logger.warn(
@@ -37,60 +32,24 @@ export {
37 32
38// --------------------------------------------------------------------------- 33// ---------------------------------------------------------------------------
39 34
40function processVideoChannelShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { 35function processVideoShare (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
41 const options = { 36 const options = {
42 arguments: [ accountAnnouncer, activity ], 37 arguments: [ actorAnnouncer, activity ],
43 errorMessage: 'Cannot share the video channel with many retries.'
44 }
45
46 return retryTransactionWrapper(shareVideoChannel, options)
47}
48
49async function shareVideoChannel (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
50 const announcedActivity = activity.object as ActivityCreate
51
52 return sequelizeTypescript.transaction(async t => {
53 // Add share entry
54 const videoChannel: VideoChannelModel = await processCreateActivity(announcedActivity)
55 const share = {
56 accountId: accountAnnouncer.id,
57 videoChannelId: videoChannel.id
58 }
59
60 const [ , created ] = await VideoChannelShareModel.findOrCreate({
61 where: share,
62 defaults: share,
63 transaction: t
64 })
65
66 if (videoChannel.isOwned() && created === true) {
67 // Don't resend the activity to the sender
68 const exceptions = [ accountAnnouncer ]
69 await forwardActivity(activity, t, exceptions)
70 }
71
72 return undefined
73 })
74}
75
76function processVideoShare (accountAnnouncer: AccountModel, activity: ActivityAnnounce) {
77 const options = {
78 arguments: [ accountAnnouncer, activity ],
79 errorMessage: 'Cannot share the video with many retries.' 38 errorMessage: 'Cannot share the video with many retries.'
80 } 39 }
81 40
82 return retryTransactionWrapper(shareVideo, options) 41 return retryTransactionWrapper(shareVideo, options)
83} 42}
84 43
85function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce) { 44function shareVideo (actorAnnouncer: ActorModel, activity: ActivityAnnounce) {
86 const announcedActivity = activity.object as ActivityAdd 45 const announcedActivity = activity.object
87 46
88 return sequelizeTypescript.transaction(async t => { 47 return sequelizeTypescript.transaction(async t => {
89 // Add share entry 48 // Add share entry
90 const video: VideoModel = await processAddActivity(announcedActivity) 49 const video: VideoModel = await processCreateActivity(announcedActivity)
91 50
92 const share = { 51 const share = {
93 accountId: accountAnnouncer.id, 52 actorId: actorAnnouncer.id,
94 videoId: video.id 53 videoId: video.id
95 } 54 }
96 55
@@ -102,7 +61,7 @@ function shareVideo (accountAnnouncer: AccountModel, activity: ActivityAnnounce)
102 61
103 if (video.isOwned() && created === true) { 62 if (video.isOwned() && created === true) {
104 // Don't resend the activity to the sender 63 // Don't resend the activity to the sender
105 const exceptions = [ accountAnnouncer ] 64 const exceptions = [ actorAnnouncer ]
106 await forwardActivity(activity, t, exceptions) 65 await forwardActivity(activity, t, exceptions)
107 } 66 }
108 67
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts
index c1eb2a8ab..1ddd817db 100644
--- a/server/lib/activitypub/process/process-create.ts
+++ b/server/lib/activitypub/process/process-create.ts
@@ -1,30 +1,33 @@
1import { ActivityCreate, VideoChannelObject } from '../../../../shared' 1import * as Bluebird from 'bluebird'
2import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
2import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' 3import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
4import { VideoRateType } from '../../../../shared/models/videos'
3import { logger, retryTransactionWrapper } from '../../../helpers' 5import { logger, retryTransactionWrapper } from '../../../helpers'
4import { sequelizeTypescript } from '../../../initializers' 6import { sequelizeTypescript } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account'
6import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 7import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
8import { ActorModel } from '../../../models/activitypub/actor'
9import { TagModel } from '../../../models/video/tag'
7import { VideoModel } from '../../../models/video/video' 10import { VideoModel } from '../../../models/video/video'
8import { VideoAbuseModel } from '../../../models/video/video-abuse' 11import { VideoAbuseModel } from '../../../models/video/video-abuse'
9import { VideoChannelModel } from '../../../models/video/video-channel' 12import { VideoFileModel } from '../../../models/video/video-file'
10import { getOrCreateAccountAndServer } from '../account' 13import { getOrCreateActorAndServerAndModel } from '../actor'
11import { forwardActivity } from '../send/misc' 14import { forwardActivity } from '../send/misc'
12import { getVideoChannelActivityPubUrl } from '../url' 15import { generateThumbnailFromUrl } from '../videos'
13import { addVideoChannelShares, videoChannelActivityObjectToDBAttributes } from './misc' 16import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
14 17
15async function processCreateActivity (activity: ActivityCreate) { 18async function processCreateActivity (activity: ActivityCreate) {
16 const activityObject = activity.object 19 const activityObject = activity.object
17 const activityType = activityObject.type 20 const activityType = activityObject.type
18 const account = await getOrCreateAccountAndServer(activity.actor) 21 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
19 22
20 if (activityType === 'View') { 23 if (activityType === 'View') {
21 return processCreateView(account, activity) 24 return processCreateView(actor, activity)
22 } else if (activityType === 'Dislike') { 25 } else if (activityType === 'Dislike') {
23 return processCreateDislike(account, activity) 26 return processCreateDislike(actor, activity)
24 } else if (activityType === 'VideoChannel') { 27 } else if (activityType === 'Video') {
25 return processCreateVideoChannel(account, activityObject as VideoChannelObject) 28 return processCreateVideo(actor, activity)
26 } else if (activityType === 'Flag') { 29 } else if (activityType === 'Flag') {
27 return processCreateVideoAbuse(account, activityObject as VideoAbuseObject) 30 return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject)
28 } 31 }
29 32
30 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) 33 logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@@ -39,17 +42,123 @@ export {
39 42
40// --------------------------------------------------------------------------- 43// ---------------------------------------------------------------------------
41 44
42async function processCreateDislike (byAccount: AccountModel, activity: ActivityCreate) { 45async function processCreateVideo (
46 actor: ActorModel,
47 activity: ActivityCreate
48) {
49 const videoToCreateData = activity.object as VideoTorrentObject
50
51 const channel = videoToCreateData.attributedTo.find(a => a.type === 'Group')
52 if (!channel) throw new Error('Cannot find associated video channel to video ' + videoToCreateData.url)
53
54 const channelActor = await getOrCreateActorAndServerAndModel(channel.id)
55
56 const options = {
57 arguments: [ actor, activity, videoToCreateData, channelActor ],
58 errorMessage: 'Cannot insert the remote video with many retries.'
59 }
60
61 const video = await retryTransactionWrapper(createRemoteVideo, options)
62
63 // Process outside the transaction because we could fetch remote data
64 if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
65 await createRates(videoToCreateData.likes.orderedItems, video, 'like')
66 }
67
68 if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
69 await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
70 }
71
72 if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
73 await addVideoShares(video, videoToCreateData.shares.orderedItems)
74 }
75
76 return video
77}
78
79function createRemoteVideo (
80 account: ActorModel,
81 activity: ActivityCreate,
82 videoToCreateData: VideoTorrentObject,
83 channelActor: ActorModel
84) {
85 logger.debug('Adding remote video %s.', videoToCreateData.id)
86
87 return sequelizeTypescript.transaction(async t => {
88 const sequelizeOptions = {
89 transaction: t
90 }
91 const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t)
92 if (videoFromDatabase) return videoFromDatabase
93
94 const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoToCreateData, activity.to, activity.cc)
95 const video = VideoModel.build(videoData)
96
97 // Don't block on request
98 generateThumbnailFromUrl(video, videoToCreateData.icon)
99 .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err))
100
101 const videoCreated = await video.save(sequelizeOptions)
102
103 const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData)
104 if (videoFileAttributes.length === 0) {
105 throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url)
106 }
107
108 const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t }))
109 await Promise.all(tasks)
110
111 const tags = videoToCreateData.tag.map(t => t.name)
112 const tagInstances = await TagModel.findOrCreateTags(tags, t)
113 await videoCreated.$set('Tags', tagInstances, sequelizeOptions)
114
115 logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)
116
117 return videoCreated
118 })
119}
120
121async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) {
122 let rateCounts = 0
123 const tasks: Bluebird<any>[] = []
124
125 for (const actorUrl of actorUrls) {
126 const actor = await getOrCreateActorAndServerAndModel(actorUrl)
127 const p = AccountVideoRateModel
128 .create({
129 videoId: video.id,
130 accountId: actor.Account.id,
131 type: rate
132 })
133 .then(() => rateCounts += 1)
134
135 tasks.push(p)
136 }
137
138 await Promise.all(tasks)
139
140 logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid)
141
142 // This is "likes" and "dislikes"
143 await video.increment(rate + 's', { by: rateCounts })
144
145 return
146}
147
148async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) {
43 const options = { 149 const options = {
44 arguments: [ byAccount, activity ], 150 arguments: [ byActor, activity ],
45 errorMessage: 'Cannot dislike the video with many retries.' 151 errorMessage: 'Cannot dislike the video with many retries.'
46 } 152 }
47 153
48 return retryTransactionWrapper(createVideoDislike, options) 154 return retryTransactionWrapper(createVideoDislike, options)
49} 155}
50 156
51function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate) { 157function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) {
52 const dislike = activity.object as DislikeObject 158 const dislike = activity.object as DislikeObject
159 const byAccount = byActor.Account
160
161 if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
53 162
54 return sequelizeTypescript.transaction(async t => { 163 return sequelizeTypescript.transaction(async t => {
55 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) 164 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
@@ -69,20 +178,20 @@ function createVideoDislike (byAccount: AccountModel, activity: ActivityCreate)
69 178
70 if (video.isOwned() && created === true) { 179 if (video.isOwned() && created === true) {
71 // Don't resend the activity to the sender 180 // Don't resend the activity to the sender
72 const exceptions = [ byAccount ] 181 const exceptions = [ byActor ]
73 await forwardActivity(activity, t, exceptions) 182 await forwardActivity(activity, t, exceptions)
74 } 183 }
75 }) 184 })
76} 185}
77 186
78async function processCreateView (byAccount: AccountModel, activity: ActivityCreate) { 187async function processCreateView (byAccount: ActorModel, activity: ActivityCreate) {
79 const view = activity.object as ViewObject 188 const view = activity.object as ViewObject
80 189
81 const video = await VideoModel.loadByUrlAndPopulateAccount(view.object) 190 const video = await VideoModel.loadByUrlAndPopulateAccount(view.object)
82 191
83 if (!video) throw new Error('Unknown video ' + view.object) 192 if (!video) throw new Error('Unknown video ' + view.object)
84 193
85 const account = await AccountModel.loadByUrl(view.actor) 194 const account = await ActorModel.loadByUrl(view.actor)
86 if (!account) throw new Error('Unknown account ' + view.actor) 195 if (!account) throw new Error('Unknown account ' + view.actor)
87 196
88 await video.increment('views') 197 await video.increment('views')
@@ -94,51 +203,21 @@ async function processCreateView (byAccount: AccountModel, activity: ActivityCre
94 } 203 }
95} 204}
96 205
97async function processCreateVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) { 206function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
98 const options = {
99 arguments: [ account, videoChannelToCreateData ],
100 errorMessage: 'Cannot insert the remote video channel with many retries.'
101 }
102
103 const videoChannel = await retryTransactionWrapper(addRemoteVideoChannel, options)
104
105 if (videoChannelToCreateData.shares && Array.isArray(videoChannelToCreateData.shares.orderedItems)) {
106 await addVideoChannelShares(videoChannel, videoChannelToCreateData.shares.orderedItems)
107 }
108
109 return videoChannel
110}
111
112function addRemoteVideoChannel (account: AccountModel, videoChannelToCreateData: VideoChannelObject) {
113 logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid)
114
115 return sequelizeTypescript.transaction(async t => {
116 let videoChannel = await VideoChannelModel.loadByUUIDOrUrl(videoChannelToCreateData.uuid, videoChannelToCreateData.id, t)
117 if (videoChannel) return videoChannel
118
119 const videoChannelData = videoChannelActivityObjectToDBAttributes(videoChannelToCreateData, account)
120 videoChannel = new VideoChannelModel(videoChannelData)
121 videoChannel.url = getVideoChannelActivityPubUrl(videoChannel)
122
123 videoChannel = await videoChannel.save({ transaction: t })
124 logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)
125
126 return videoChannel
127 })
128}
129
130function processCreateVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) {
131 const options = { 207 const options = {
132 arguments: [ account, videoAbuseToCreateData ], 208 arguments: [ actor, videoAbuseToCreateData ],
133 errorMessage: 'Cannot insert the remote video abuse with many retries.' 209 errorMessage: 'Cannot insert the remote video abuse with many retries.'
134 } 210 }
135 211
136 return retryTransactionWrapper(addRemoteVideoAbuse, options) 212 return retryTransactionWrapper(addRemoteVideoAbuse, options)
137} 213}
138 214
139function addRemoteVideoAbuse (account: AccountModel, videoAbuseToCreateData: VideoAbuseObject) { 215function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) {
140 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) 216 logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object)
141 217
218 const account = actor.Account
219 if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url)
220
142 return sequelizeTypescript.transaction(async t => { 221 return sequelizeTypescript.transaction(async t => {
143 const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t) 222 const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t)
144 if (!video) { 223 if (!video) {
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts
index 8f280d37f..65a4e5bcc 100644
--- a/server/lib/activitypub/process/process-delete.ts
+++ b/server/lib/activitypub/process/process-delete.ts
@@ -2,28 +2,30 @@ import { ActivityDelete } from '../../../../shared/models/activitypub'
2import { logger, retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { AccountModel } from '../../../models/account/account'
5import { ActorModel } from '../../../models/activitypub/actor'
5import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
6import { VideoChannelModel } from '../../../models/video/video-channel' 7import { VideoChannelModel } from '../../../models/video/video-channel'
7import { getOrCreateAccountAndServer } from '../account' 8import { getOrCreateActorAndServerAndModel } from '../actor'
8 9
9async function processDeleteActivity (activity: ActivityDelete) { 10async function processDeleteActivity (activity: ActivityDelete) {
10 const account = await getOrCreateAccountAndServer(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
11 12
12 if (account.url === activity.id) { 13 if (actor.url === activity.id) {
13 return processDeleteAccount(account) 14 if (actor.type === 'Person') {
14 } 15 if (!actor.Account) throw new Error('Actor ' + actor.url + ' is a person but we cannot find it in database.')
15 16
16 { 17 return processDeleteAccount(actor.Account)
17 let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id) 18 } else if (actor.type === 'Group') {
18 if (videoObject !== undefined) { 19 if (!actor.VideoChannel) throw new Error('Actor ' + actor.url + ' is a group but we cannot find it in database.')
19 return processDeleteVideo(account, videoObject) 20
21 return processDeleteVideoChannel(actor.VideoChannel)
20 } 22 }
21 } 23 }
22 24
23 { 25 {
24 let videoChannelObject = await VideoChannelModel.loadByUrl(activity.id) 26 let videoObject = await VideoModel.loadByUrlAndPopulateAccount(activity.id)
25 if (videoChannelObject !== undefined) { 27 if (videoObject !== undefined) {
26 return processDeleteVideoChannel(account, videoChannelObject) 28 return processDeleteVideo(actor, videoObject)
27 } 29 }
28 } 30 }
29 31
@@ -38,21 +40,21 @@ export {
38 40
39// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
40 42
41async function processDeleteVideo (account: AccountModel, videoToDelete: VideoModel) { 43async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) {
42 const options = { 44 const options = {
43 arguments: [ account, videoToDelete ], 45 arguments: [ actor, videoToDelete ],
44 errorMessage: 'Cannot remove the remote video with many retries.' 46 errorMessage: 'Cannot remove the remote video with many retries.'
45 } 47 }
46 48
47 await retryTransactionWrapper(deleteRemoteVideo, options) 49 await retryTransactionWrapper(deleteRemoteVideo, options)
48} 50}
49 51
50async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoModel) { 52async function deleteRemoteVideo (actor: ActorModel, videoToDelete: VideoModel) {
51 logger.debug('Removing remote video "%s".', videoToDelete.uuid) 53 logger.debug('Removing remote video "%s".', videoToDelete.uuid)
52 54
53 await sequelizeTypescript.transaction(async t => { 55 await sequelizeTypescript.transaction(async t => {
54 if (videoToDelete.VideoChannel.Account.id !== account.id) { 56 if (videoToDelete.VideoChannel.Account.Actor.id !== actor.id) {
55 throw new Error('Account ' + account.url + ' does not own video channel ' + videoToDelete.VideoChannel.url) 57 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoToDelete.VideoChannel.Actor.url)
56 } 58 }
57 59
58 await videoToDelete.destroy({ transaction: t }) 60 await videoToDelete.destroy({ transaction: t })
@@ -61,44 +63,40 @@ async function deleteRemoteVideo (account: AccountModel, videoToDelete: VideoMod
61 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) 63 logger.info('Remote video with uuid %s removed.', videoToDelete.uuid)
62} 64}
63 65
64async function processDeleteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { 66async function processDeleteAccount (accountToRemove: AccountModel) {
65 const options = { 67 const options = {
66 arguments: [ account, videoChannelToRemove ], 68 arguments: [ accountToRemove ],
67 errorMessage: 'Cannot remove the remote video channel with many retries.' 69 errorMessage: 'Cannot remove the remote account with many retries.'
68 } 70 }
69 71
70 await retryTransactionWrapper(deleteRemoteVideoChannel, options) 72 await retryTransactionWrapper(deleteRemoteAccount, options)
71} 73}
72 74
73async function deleteRemoteVideoChannel (account: AccountModel, videoChannelToRemove: VideoChannelModel) { 75async function deleteRemoteAccount (accountToRemove: AccountModel) {
74 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.uuid) 76 logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid)
75 77
76 await sequelizeTypescript.transaction(async t => { 78 await sequelizeTypescript.transaction(async t => {
77 if (videoChannelToRemove.Account.id !== account.id) { 79 await accountToRemove.destroy({ transaction: t })
78 throw new Error('Account ' + account.url + ' does not own video channel ' + videoChannelToRemove.url)
79 }
80
81 await videoChannelToRemove.destroy({ transaction: t })
82 }) 80 })
83 81
84 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.uuid) 82 logger.info('Remote account with uuid %s removed.', accountToRemove.Actor.uuid)
85} 83}
86 84
87async function processDeleteAccount (accountToRemove: AccountModel) { 85async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) {
88 const options = { 86 const options = {
89 arguments: [ accountToRemove ], 87 arguments: [ videoChannelToRemove ],
90 errorMessage: 'Cannot remove the remote account with many retries.' 88 errorMessage: 'Cannot remove the remote video channel with many retries.'
91 } 89 }
92 90
93 await retryTransactionWrapper(deleteRemoteAccount, options) 91 await retryTransactionWrapper(deleteRemoteVideoChannel, options)
94} 92}
95 93
96async function deleteRemoteAccount (accountToRemove: AccountModel) { 94async function deleteRemoteVideoChannel (videoChannelToRemove: VideoChannelModel) {
97 logger.debug('Removing remote account "%s".', accountToRemove.uuid) 95 logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.uuid)
98 96
99 await sequelizeTypescript.transaction(async t => { 97 await sequelizeTypescript.transaction(async t => {
100 await accountToRemove.destroy({ transaction: t }) 98 await videoChannelToRemove.destroy({ transaction: t })
101 }) 99 })
102 100
103 logger.info('Remote account with uuid %s removed.', accountToRemove.uuid) 101 logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
104} 102}
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts
index ccaee43a6..ec7a331f3 100644
--- a/server/lib/activitypub/process/process-follow.ts
+++ b/server/lib/activitypub/process/process-follow.ts
@@ -1,16 +1,16 @@
1import { ActivityFollow } from '../../../../shared/models/activitypub' 1import { ActivityFollow } from '../../../../shared/models/activitypub'
2import { logger, retryTransactionWrapper } from '../../../helpers' 2import { logger, retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account' 4import { ActorModel } from '../../../models/activitypub/actor'
5import { AccountFollowModel } from '../../../models/account/account-follow' 5import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
6import { getOrCreateAccountAndServer } from '../account' 6import { getOrCreateActorAndServerAndModel } from '../actor'
7import { sendAccept } from '../send' 7import { sendAccept } from '../send'
8 8
9async function processFollowActivity (activity: ActivityFollow) { 9async function processFollowActivity (activity: ActivityFollow) {
10 const activityObject = activity.object 10 const activityObject = activity.object
11 const account = await getOrCreateAccountAndServer(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
12 12
13 return processFollow(account, activityObject) 13 return processFollow(actor, activityObject)
14} 14}
15 15
16// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
@@ -21,46 +21,46 @@ export {
21 21
22// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
23 23
24function processFollow (account: AccountModel, targetAccountURL: string) { 24function processFollow (actor: ActorModel, targetActorURL: string) {
25 const options = { 25 const options = {
26 arguments: [ account, targetAccountURL ], 26 arguments: [ actor, targetActorURL ],
27 errorMessage: 'Cannot follow with many retries.' 27 errorMessage: 'Cannot follow with many retries.'
28 } 28 }
29 29
30 return retryTransactionWrapper(follow, options) 30 return retryTransactionWrapper(follow, options)
31} 31}
32 32
33async function follow (account: AccountModel, targetAccountURL: string) { 33async function follow (actor: ActorModel, targetActorURL: string) {
34 await sequelizeTypescript.transaction(async t => { 34 await sequelizeTypescript.transaction(async t => {
35 const targetAccount = await AccountModel.loadByUrl(targetAccountURL, t) 35 const targetActor = await ActorModel.loadByUrl(targetActorURL, t)
36 36
37 if (!targetAccount) throw new Error('Unknown account') 37 if (!targetActor) throw new Error('Unknown actor')
38 if (targetAccount.isOwned() === false) throw new Error('This is not a local account.') 38 if (targetActor.isOwned() === false) throw new Error('This is not a local actor.')
39 39
40 const [ accountFollow ] = await AccountFollowModel.findOrCreate({ 40 const [ actorFollow ] = await ActorFollowModel.findOrCreate({
41 where: { 41 where: {
42 accountId: account.id, 42 actorId: actor.id,
43 targetAccountId: targetAccount.id 43 targetActorId: targetActor.id
44 }, 44 },
45 defaults: { 45 defaults: {
46 accountId: account.id, 46 actorId: actor.id,
47 targetAccountId: targetAccount.id, 47 targetActorId: targetActor.id,
48 state: 'accepted' 48 state: 'accepted'
49 }, 49 },
50 transaction: t 50 transaction: t
51 }) 51 })
52 52
53 if (accountFollow.state !== 'accepted') { 53 if (actorFollow.state !== 'accepted') {
54 accountFollow.state = 'accepted' 54 actorFollow.state = 'accepted'
55 await accountFollow.save({ transaction: t }) 55 await actorFollow.save({ transaction: t })
56 } 56 }
57 57
58 accountFollow.AccountFollower = account 58 actorFollow.ActorFollower = actor
59 accountFollow.AccountFollowing = targetAccount 59 actorFollow.ActorFollowing = targetActor
60 60
61 // Target sends to account he accepted the follow request 61 // Target sends to actor he accepted the follow request
62 return sendAccept(accountFollow, t) 62 return sendAccept(actorFollow, t)
63 }) 63 })
64 64
65 logger.info('Account uuid %s is followed by account %s.', account.url, targetAccountURL) 65 logger.info('Actor uuid %s is followed by actor %s.', actor.url, targetActorURL)
66} 66}
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts
index a6e391f1e..a7fcec21c 100644
--- a/server/lib/activitypub/process/process-like.ts
+++ b/server/lib/activitypub/process/process-like.ts
@@ -1,16 +1,16 @@
1import { ActivityLike } from '../../../../shared/models/activitypub' 1import { ActivityLike } from '../../../../shared/models/activitypub'
2import { retryTransactionWrapper } from '../../../helpers' 2import { retryTransactionWrapper } from '../../../helpers'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers'
4import { AccountModel } from '../../../models/account/account'
5import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 4import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
5import { ActorModel } from '../../../models/activitypub/actor'
6import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
7import { getOrCreateAccountAndServer } from '../account' 7import { getOrCreateActorAndServerAndModel } from '../actor'
8import { forwardActivity } from '../send/misc' 8import { forwardActivity } from '../send/misc'
9 9
10async function processLikeActivity (activity: ActivityLike) { 10async function processLikeActivity (activity: ActivityLike) {
11 const account = await getOrCreateAccountAndServer(activity.actor) 11 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
12 12
13 return processLikeVideo(account, activity) 13 return processLikeVideo(actor, activity)
14} 14}
15 15
16// --------------------------------------------------------------------------- 16// ---------------------------------------------------------------------------
@@ -21,18 +21,21 @@ export {
21 21
22// --------------------------------------------------------------------------- 22// ---------------------------------------------------------------------------
23 23
24async function processLikeVideo (byAccount: AccountModel, activity: ActivityLike) { 24async function processLikeVideo (actor: ActorModel, activity: ActivityLike) {
25 const options = { 25 const options = {
26 arguments: [ byAccount, activity ], 26 arguments: [ actor, activity ],
27 errorMessage: 'Cannot like the video with many retries.' 27 errorMessage: 'Cannot like the video with many retries.'
28 } 28 }
29 29
30 return retryTransactionWrapper(createVideoLike, options) 30 return retryTransactionWrapper(createVideoLike, options)
31} 31}
32 32
33function createVideoLike (byAccount: AccountModel, activity: ActivityLike) { 33function createVideoLike (byActor: ActorModel, activity: ActivityLike) {
34 const videoUrl = activity.object 34 const videoUrl = activity.object
35 35
36 const byAccount = byActor.Account
37 if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
38
36 return sequelizeTypescript.transaction(async t => { 39 return sequelizeTypescript.transaction(async t => {
37 const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl) 40 const video = await VideoModel.loadByUrlAndPopulateAccount(videoUrl)
38 41
@@ -52,7 +55,7 @@ function createVideoLike (byAccount: AccountModel, activity: ActivityLike) {
52 55
53 if (video.isOwned() && created === true) { 56 if (video.isOwned() && created === true) {
54 // Don't resend the activity to the sender 57 // Don't resend the activity to the sender
55 const exceptions = [ byAccount ] 58 const exceptions = [ byActor ]
56 await forwardActivity(activity, t, exceptions) 59 await forwardActivity(activity, t, exceptions)
57 } 60 }
58 }) 61 })
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts
index efa63122b..4a0181137 100644
--- a/server/lib/activitypub/process/process-undo.ts
+++ b/server/lib/activitypub/process/process-undo.ts
@@ -3,8 +3,9 @@ import { DislikeObject } from '../../../../shared/models/activitypub/objects'
3import { logger, retryTransactionWrapper } from '../../../helpers' 3import { logger, retryTransactionWrapper } from '../../../helpers'
4import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
5import { AccountModel } from '../../../models/account/account' 5import { AccountModel } from '../../../models/account/account'
6import { AccountFollowModel } from '../../../models/account/account-follow'
7import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 6import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
7import { ActorModel } from '../../../models/activitypub/actor'
8import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
8import { VideoModel } from '../../../models/video/video' 9import { VideoModel } from '../../../models/video/video'
9import { forwardActivity } from '../send/misc' 10import { forwardActivity } from '../send/misc'
10 11
@@ -32,21 +33,21 @@ export {
32 33
33// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
34 35
35function processUndoLike (actor: string, activity: ActivityUndo) { 36function processUndoLike (actorUrl: string, activity: ActivityUndo) {
36 const options = { 37 const options = {
37 arguments: [ actor, activity ], 38 arguments: [ actorUrl, activity ],
38 errorMessage: 'Cannot undo like with many retries.' 39 errorMessage: 'Cannot undo like with many retries.'
39 } 40 }
40 41
41 return retryTransactionWrapper(undoLike, options) 42 return retryTransactionWrapper(undoLike, options)
42} 43}
43 44
44function undoLike (actor: string, activity: ActivityUndo) { 45function undoLike (actorUrl: string, activity: ActivityUndo) {
45 const likeActivity = activity.object as ActivityLike 46 const likeActivity = activity.object as ActivityLike
46 47
47 return sequelizeTypescript.transaction(async t => { 48 return sequelizeTypescript.transaction(async t => {
48 const byAccount = await AccountModel.loadByUrl(actor, t) 49 const byAccount = await AccountModel.loadByUrl(actorUrl, t)
49 if (!byAccount) throw new Error('Unknown account ' + actor) 50 if (!byAccount) throw new Error('Unknown account ' + actorUrl)
50 51
51 const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t) 52 const video = await VideoModel.loadByUrlAndPopulateAccount(likeActivity.object, t)
52 if (!video) throw new Error('Unknown video ' + likeActivity.actor) 53 if (!video) throw new Error('Unknown video ' + likeActivity.actor)
@@ -59,27 +60,27 @@ function undoLike (actor: string, activity: ActivityUndo) {
59 60
60 if (video.isOwned()) { 61 if (video.isOwned()) {
61 // Don't resend the activity to the sender 62 // Don't resend the activity to the sender
62 const exceptions = [ byAccount ] 63 const exceptions = [ byAccount.Actor ]
63 await forwardActivity(activity, t, exceptions) 64 await forwardActivity(activity, t, exceptions)
64 } 65 }
65 }) 66 })
66} 67}
67 68
68function processUndoDislike (actor: string, activity: ActivityUndo) { 69function processUndoDislike (actorUrl: string, activity: ActivityUndo) {
69 const options = { 70 const options = {
70 arguments: [ actor, activity ], 71 arguments: [ actorUrl, activity ],
71 errorMessage: 'Cannot undo dislike with many retries.' 72 errorMessage: 'Cannot undo dislike with many retries.'
72 } 73 }
73 74
74 return retryTransactionWrapper(undoDislike, options) 75 return retryTransactionWrapper(undoDislike, options)
75} 76}
76 77
77function undoDislike (actor: string, activity: ActivityUndo) { 78function undoDislike (actorUrl: string, activity: ActivityUndo) {
78 const dislike = activity.object.object as DislikeObject 79 const dislike = activity.object.object as DislikeObject
79 80
80 return sequelizeTypescript.transaction(async t => { 81 return sequelizeTypescript.transaction(async t => {
81 const byAccount = await AccountModel.loadByUrl(actor, t) 82 const byAccount = await AccountModel.loadByUrl(actorUrl, t)
82 if (!byAccount) throw new Error('Unknown account ' + actor) 83 if (!byAccount) throw new Error('Unknown account ' + actorUrl)
83 84
84 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) 85 const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t)
85 if (!video) throw new Error('Unknown video ' + dislike.actor) 86 if (!video) throw new Error('Unknown video ' + dislike.actor)
@@ -92,30 +93,30 @@ function undoDislike (actor: string, activity: ActivityUndo) {
92 93
93 if (video.isOwned()) { 94 if (video.isOwned()) {
94 // Don't resend the activity to the sender 95 // Don't resend the activity to the sender
95 const exceptions = [ byAccount ] 96 const exceptions = [ byAccount.Actor ]
96 await forwardActivity(activity, t, exceptions) 97 await forwardActivity(activity, t, exceptions)
97 } 98 }
98 }) 99 })
99} 100}
100 101
101function processUndoFollow (actor: string, followActivity: ActivityFollow) { 102function processUndoFollow (actorUrl: string, followActivity: ActivityFollow) {
102 const options = { 103 const options = {
103 arguments: [ actor, followActivity ], 104 arguments: [ actorUrl, followActivity ],
104 errorMessage: 'Cannot undo follow with many retries.' 105 errorMessage: 'Cannot undo follow with many retries.'
105 } 106 }
106 107
107 return retryTransactionWrapper(undoFollow, options) 108 return retryTransactionWrapper(undoFollow, options)
108} 109}
109 110
110function undoFollow (actor: string, followActivity: ActivityFollow) { 111function undoFollow (actorUrl: string, followActivity: ActivityFollow) {
111 return sequelizeTypescript.transaction(async t => { 112 return sequelizeTypescript.transaction(async t => {
112 const follower = await AccountModel.loadByUrl(actor, t) 113 const follower = await ActorModel.loadByUrl(actorUrl, t)
113 const following = await AccountModel.loadByUrl(followActivity.object, t) 114 const following = await ActorModel.loadByUrl(followActivity.object, t)
114 const accountFollow = await AccountFollowModel.loadByAccountAndTarget(follower.id, following.id, t) 115 const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t)
115 116
116 if (!accountFollow) throw new Error(`'Unknown account follow ${follower.id} -> ${following.id}.`) 117 if (!actorFollow) throw new Error(`'Unknown actor follow ${follower.id} -> ${following.id}.`)
117 118
118 await accountFollow.destroy({ transaction: t }) 119 await actorFollow.destroy({ transaction: t })
119 120
120 return undefined 121 return undefined
121 }) 122 })
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts
index 771021f0c..35912ee87 100644
--- a/server/lib/activitypub/process/process-update.ts
+++ b/server/lib/activitypub/process/process-update.ts
@@ -1,23 +1,19 @@
1import * as Bluebird from 'bluebird' 1import * as Bluebird from 'bluebird'
2import { VideoChannelObject, VideoTorrentObject } from '../../../../shared'
3import { ActivityUpdate } from '../../../../shared/models/activitypub' 2import { ActivityUpdate } from '../../../../shared/models/activitypub'
4import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers' 3import { logger, resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers'
5import { sequelizeTypescript } from '../../../initializers' 4import { sequelizeTypescript } from '../../../initializers'
6import { AccountModel } from '../../../models/account/account' 5import { ActorModel } from '../../../models/activitypub/actor'
7import { TagModel } from '../../../models/video/tag' 6import { TagModel } from '../../../models/video/tag'
8import { VideoModel } from '../../../models/video/video' 7import { VideoModel } from '../../../models/video/video'
9import { VideoChannelModel } from '../../../models/video/video-channel'
10import { VideoFileModel } from '../../../models/video/video-file' 8import { VideoFileModel } from '../../../models/video/video-file'
11import { getOrCreateAccountAndServer } from '../account' 9import { getOrCreateActorAndServerAndModel } from '../actor'
12import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 10import { videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
13 11
14async function processUpdateActivity (activity: ActivityUpdate) { 12async function processUpdateActivity (activity: ActivityUpdate) {
15 const account = await getOrCreateAccountAndServer(activity.actor) 13 const actor = await getOrCreateActorAndServerAndModel(activity.actor)
16 14
17 if (activity.object.type === 'Video') { 15 if (activity.object.type === 'Video') {
18 return processUpdateVideo(account, activity.object) 16 return processUpdateVideo(actor, activity)
19 } else if (activity.object.type === 'VideoChannel') {
20 return processUpdateVideoChannel(account, activity.object)
21 } 17 }
22 18
23 return 19 return
@@ -31,16 +27,18 @@ export {
31 27
32// --------------------------------------------------------------------------- 28// ---------------------------------------------------------------------------
33 29
34function processUpdateVideo (account: AccountModel, video: VideoTorrentObject) { 30function processUpdateVideo (actor: ActorModel, activity: ActivityUpdate) {
35 const options = { 31 const options = {
36 arguments: [ account, video ], 32 arguments: [ actor, activity ],
37 errorMessage: 'Cannot update the remote video with many retries' 33 errorMessage: 'Cannot update the remote video with many retries'
38 } 34 }
39 35
40 return retryTransactionWrapper(updateRemoteVideo, options) 36 return retryTransactionWrapper(updateRemoteVideo, options)
41} 37}
42 38
43async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate: VideoTorrentObject) { 39async function updateRemoteVideo (actor: ActorModel, activity: ActivityUpdate) {
40 const videoAttributesToUpdate = activity.object
41
44 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) 42 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid)
45 let videoInstance: VideoModel 43 let videoInstance: VideoModel
46 let videoFieldsSave: object 44 let videoFieldsSave: object
@@ -54,23 +52,23 @@ async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate
54 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t) 52 const videoInstance = await VideoModel.loadByUrlAndPopulateAccount(videoAttributesToUpdate.id, t)
55 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.') 53 if (!videoInstance) throw new Error('Video ' + videoAttributesToUpdate.id + ' not found.')
56 54
57 if (videoInstance.VideoChannel.Account.id !== account.id) { 55 const videoChannel = videoInstance.VideoChannel
58 throw new Error('Account ' + account.url + ' does not own video channel ' + videoInstance.VideoChannel.url) 56 if (videoChannel.Account.Actor.id !== actor.id) {
57 throw new Error('Account ' + actor.url + ' does not own video channel ' + videoChannel.Actor.url)
59 } 58 }
60 59
61 const videoData = await videoActivityObjectToDBAttributes(videoInstance.VideoChannel, videoAttributesToUpdate) 60 const videoData = await videoActivityObjectToDBAttributes(videoChannel, videoAttributesToUpdate, activity.to, activity.cc)
62 videoInstance.set('name', videoData.name) 61 videoInstance.set('name', videoData.name)
63 videoInstance.set('category', videoData.category) 62 videoInstance.set('category', videoData.category)
64 videoInstance.set('licence', videoData.licence) 63 videoInstance.set('licence', videoData.licence)
65 videoInstance.set('language', videoData.language) 64 videoInstance.set('language', videoData.language)
66 videoInstance.set('nsfw', videoData.nsfw) 65 videoInstance.set('nsfw', videoData.nsfw)
66 videoInstance.set('privacy', videoData.privacy)
67 videoInstance.set('description', videoData.description) 67 videoInstance.set('description', videoData.description)
68 videoInstance.set('duration', videoData.duration) 68 videoInstance.set('duration', videoData.duration)
69 videoInstance.set('createdAt', videoData.createdAt) 69 videoInstance.set('createdAt', videoData.createdAt)
70 videoInstance.set('updatedAt', videoData.updatedAt) 70 videoInstance.set('updatedAt', videoData.updatedAt)
71 videoInstance.set('views', videoData.views) 71 videoInstance.set('views', videoData.views)
72 // videoInstance.set('likes', videoData.likes)
73 // videoInstance.set('dislikes', videoData.dislikes)
74 72
75 await videoInstance.save(sequelizeOptions) 73 await videoInstance.save(sequelizeOptions)
76 74
@@ -101,36 +99,3 @@ async function updateRemoteVideo (account: AccountModel, videoAttributesToUpdate
101 throw err 99 throw err
102 } 100 }
103} 101}
104
105async function processUpdateVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
106 const options = {
107 arguments: [ account, videoChannel ],
108 errorMessage: 'Cannot update the remote video channel with many retries.'
109 }
110
111 await retryTransactionWrapper(updateRemoteVideoChannel, options)
112}
113
114async function updateRemoteVideoChannel (account: AccountModel, videoChannel: VideoChannelObject) {
115 logger.debug('Updating remote video channel "%s".', videoChannel.uuid)
116
117 await sequelizeTypescript.transaction(async t => {
118 const sequelizeOptions = { transaction: t }
119
120 const videoChannelInstance = await VideoChannelModel.loadByUrl(videoChannel.id)
121 if (!videoChannelInstance) throw new Error('Video ' + videoChannel.id + ' not found.')
122
123 if (videoChannelInstance.Account.id !== account.id) {
124 throw new Error('Account ' + account.id + ' does not own video channel ' + videoChannelInstance.url)
125 }
126
127 videoChannelInstance.set('name', videoChannel.name)
128 videoChannelInstance.set('description', videoChannel.content)
129 videoChannelInstance.set('createdAt', videoChannel.published)
130 videoChannelInstance.set('updatedAt', videoChannel.updated)
131
132 await videoChannelInstance.save(sequelizeOptions)
133 })
134
135 logger.info('Remote video channel with uuid %s updated', videoChannel.uuid)
136}
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts
index bfbf8053c..dfb60c1bf 100644
--- a/server/lib/activitypub/process/process.ts
+++ b/server/lib/activitypub/process/process.ts
@@ -1,8 +1,7 @@
1import { Activity, ActivityType } from '../../../../shared/models/activitypub' 1import { Activity, ActivityType } from '../../../../shared/models/activitypub'
2import { logger } from '../../../helpers' 2import { logger } from '../../../helpers'
3import { AccountModel } from '../../../models/account/account' 3import { ActorModel } from '../../../models/activitypub/actor'
4import { processAcceptActivity } from './process-accept' 4import { processAcceptActivity } from './process-accept'
5import { processAddActivity } from './process-add'
6import { processAnnounceActivity } from './process-announce' 5import { processAnnounceActivity } from './process-announce'
7import { processCreateActivity } from './process-create' 6import { processCreateActivity } from './process-create'
8import { processDeleteActivity } from './process-delete' 7import { processDeleteActivity } from './process-delete'
@@ -11,9 +10,8 @@ import { processLikeActivity } from './process-like'
11import { processUndoActivity } from './process-undo' 10import { processUndoActivity } from './process-undo'
12import { processUpdateActivity } from './process-update' 11import { processUpdateActivity } from './process-update'
13 12
14const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccount?: AccountModel) => Promise<any> } = { 13const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxActor?: ActorModel) => Promise<any> } = {
15 Create: processCreateActivity, 14 Create: processCreateActivity,
16 Add: processAddActivity,
17 Update: processUpdateActivity, 15 Update: processUpdateActivity,
18 Delete: processDeleteActivity, 16 Delete: processDeleteActivity,
19 Follow: processFollowActivity, 17 Follow: processFollowActivity,
@@ -23,11 +21,11 @@ const processActivity: { [ P in ActivityType ]: (activity: Activity, inboxAccoun
23 Like: processLikeActivity 21 Like: processLikeActivity
24} 22}
25 23
26async function processActivities (activities: Activity[], signatureAccount?: AccountModel, inboxAccount?: AccountModel) { 24async function processActivities (activities: Activity[], signatureActor?: ActorModel, inboxActor?: ActorModel) {
27 for (const activity of activities) { 25 for (const activity of activities) {
28 // When we fetch remote data, we don't have signature 26 // When we fetch remote data, we don't have signature
29 if (signatureAccount && activity.actor !== signatureAccount.url) { 27 if (signatureActor && activity.actor !== signatureActor.url) {
30 logger.warn('Signature mismatch between %s and %s.', activity.actor, signatureAccount.url) 28 logger.warn('Signature mismatch between %s and %s.', activity.actor, signatureActor.url)
31 continue 29 continue
32 } 30 }
33 31
@@ -38,7 +36,7 @@ async function processActivities (activities: Activity[], signatureAccount?: Acc
38 } 36 }
39 37
40 try { 38 try {
41 await activityProcessor(activity, inboxAccount) 39 await activityProcessor(activity, inboxActor)
42 } catch (err) { 40 } catch (err) {
43 logger.warn('Cannot process activity %s.', activity.type, err) 41 logger.warn('Cannot process activity %s.', activity.type, err)
44 } 42 }