]>
Commit | Line | Data |
---|---|---|
50d6de9c C |
1 | import * as Bluebird from 'bluebird' |
2 | import { ActivityCreate, VideoTorrentObject } from '../../../../shared' | |
3fd3ab2d | 3 | import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects' |
6d852470 | 4 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' |
50d6de9c | 5 | import { VideoRateType } from '../../../../shared/models/videos' |
54141398 | 6 | import { logger, retryTransactionWrapper } from '../../../helpers' |
3fd3ab2d | 7 | import { sequelizeTypescript } from '../../../initializers' |
3fd3ab2d | 8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
50d6de9c C |
9 | import { ActorModel } from '../../../models/activitypub/actor' |
10 | import { TagModel } from '../../../models/video/tag' | |
3fd3ab2d C |
11 | import { VideoModel } from '../../../models/video/video' |
12 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | |
6d852470 | 13 | import { VideoCommentModel } from '../../../models/video/video-comment' |
50d6de9c C |
14 | import { VideoFileModel } from '../../../models/video/video-file' |
15 | import { getOrCreateActorAndServerAndModel } from '../actor' | |
63c93323 | 16 | import { forwardActivity } from '../send/misc' |
50d6de9c C |
17 | import { generateThumbnailFromUrl } from '../videos' |
18 | import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' | |
e4f97bab | 19 | |
0d0e8dd0 | 20 | async function processCreateActivity (activity: ActivityCreate) { |
e4f97bab C |
21 | const activityObject = activity.object |
22 | const activityType = activityObject.type | |
50d6de9c | 23 | const actor = await getOrCreateActorAndServerAndModel(activity.actor) |
e4f97bab | 24 | |
40ff5707 | 25 | if (activityType === 'View') { |
50d6de9c | 26 | return processCreateView(actor, activity) |
0032ebe9 | 27 | } else if (activityType === 'Dislike') { |
50d6de9c C |
28 | return processCreateDislike(actor, activity) |
29 | } else if (activityType === 'Video') { | |
30 | return processCreateVideo(actor, activity) | |
8e13fa7d | 31 | } else if (activityType === 'Flag') { |
50d6de9c | 32 | return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject) |
6d852470 C |
33 | } else if (activityType === 'Note') { |
34 | return processCreateVideoComment(actor, activity) | |
e4f97bab C |
35 | } |
36 | ||
37 | logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) | |
0d0e8dd0 | 38 | return Promise.resolve(undefined) |
e4f97bab C |
39 | } |
40 | ||
41 | // --------------------------------------------------------------------------- | |
42 | ||
43 | export { | |
44 | processCreateActivity | |
45 | } | |
46 | ||
47 | // --------------------------------------------------------------------------- | |
48 | ||
50d6de9c C |
49 | async function processCreateVideo ( |
50 | actor: ActorModel, | |
51 | activity: ActivityCreate | |
52 | ) { | |
53 | const videoToCreateData = activity.object as VideoTorrentObject | |
54 | ||
55 | const channel = videoToCreateData.attributedTo.find(a => a.type === 'Group') | |
56 | if (!channel) throw new Error('Cannot find associated video channel to video ' + videoToCreateData.url) | |
57 | ||
58 | const channelActor = await getOrCreateActorAndServerAndModel(channel.id) | |
59 | ||
60 | const options = { | |
61 | arguments: [ actor, activity, videoToCreateData, channelActor ], | |
62 | errorMessage: 'Cannot insert the remote video with many retries.' | |
63 | } | |
64 | ||
65 | const video = await retryTransactionWrapper(createRemoteVideo, options) | |
66 | ||
67 | // Process outside the transaction because we could fetch remote data | |
68 | if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) { | |
69 | await createRates(videoToCreateData.likes.orderedItems, video, 'like') | |
70 | } | |
71 | ||
72 | if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) { | |
73 | await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike') | |
74 | } | |
75 | ||
76 | if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) { | |
77 | await addVideoShares(video, videoToCreateData.shares.orderedItems) | |
78 | } | |
79 | ||
80 | return video | |
81 | } | |
82 | ||
83 | function createRemoteVideo ( | |
84 | account: ActorModel, | |
85 | activity: ActivityCreate, | |
86 | videoToCreateData: VideoTorrentObject, | |
87 | channelActor: ActorModel | |
88 | ) { | |
89 | logger.debug('Adding remote video %s.', videoToCreateData.id) | |
90 | ||
91 | return sequelizeTypescript.transaction(async t => { | |
92 | const sequelizeOptions = { | |
93 | transaction: t | |
94 | } | |
95 | const videoFromDatabase = await VideoModel.loadByUUIDOrURL(videoToCreateData.uuid, videoToCreateData.id, t) | |
96 | if (videoFromDatabase) return videoFromDatabase | |
97 | ||
98 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoToCreateData, activity.to, activity.cc) | |
99 | const video = VideoModel.build(videoData) | |
100 | ||
101 | // Don't block on request | |
102 | generateThumbnailFromUrl(video, videoToCreateData.icon) | |
103 | .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoToCreateData.id, err)) | |
104 | ||
105 | const videoCreated = await video.save(sequelizeOptions) | |
106 | ||
107 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoToCreateData) | |
108 | if (videoFileAttributes.length === 0) { | |
109 | throw new Error('Cannot find valid files for video %s ' + videoToCreateData.url) | |
110 | } | |
111 | ||
112 | const tasks: Bluebird<any>[] = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t })) | |
113 | await Promise.all(tasks) | |
114 | ||
115 | const tags = videoToCreateData.tag.map(t => t.name) | |
116 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | |
117 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) | |
118 | ||
119 | logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid) | |
120 | ||
121 | return videoCreated | |
122 | }) | |
123 | } | |
124 | ||
125 | async function createRates (actorUrls: string[], video: VideoModel, rate: VideoRateType) { | |
126 | let rateCounts = 0 | |
127 | const tasks: Bluebird<any>[] = [] | |
128 | ||
129 | for (const actorUrl of actorUrls) { | |
130 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) | |
131 | const p = AccountVideoRateModel | |
132 | .create({ | |
133 | videoId: video.id, | |
134 | accountId: actor.Account.id, | |
135 | type: rate | |
136 | }) | |
137 | .then(() => rateCounts += 1) | |
138 | ||
139 | tasks.push(p) | |
140 | } | |
141 | ||
142 | await Promise.all(tasks) | |
143 | ||
144 | logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid) | |
145 | ||
146 | // This is "likes" and "dislikes" | |
147 | await video.increment(rate + 's', { by: rateCounts }) | |
148 | ||
149 | return | |
150 | } | |
151 | ||
152 | async function processCreateDislike (byActor: ActorModel, activity: ActivityCreate) { | |
0032ebe9 | 153 | const options = { |
50d6de9c | 154 | arguments: [ byActor, activity ], |
0032ebe9 C |
155 | errorMessage: 'Cannot dislike the video with many retries.' |
156 | } | |
157 | ||
158 | return retryTransactionWrapper(createVideoDislike, options) | |
159 | } | |
160 | ||
50d6de9c | 161 | function createVideoDislike (byActor: ActorModel, activity: ActivityCreate) { |
63c93323 | 162 | const dislike = activity.object as DislikeObject |
50d6de9c C |
163 | const byAccount = byActor.Account |
164 | ||
165 | if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url) | |
0032ebe9 | 166 | |
3fd3ab2d C |
167 | return sequelizeTypescript.transaction(async t => { |
168 | const video = await VideoModel.loadByUrlAndPopulateAccount(dislike.object, t) | |
0032ebe9 C |
169 | if (!video) throw new Error('Unknown video ' + dislike.object) |
170 | ||
171 | const rate = { | |
172 | type: 'dislike' as 'dislike', | |
173 | videoId: video.id, | |
174 | accountId: byAccount.id | |
175 | } | |
3fd3ab2d | 176 | const [ , created ] = await AccountVideoRateModel.findOrCreate({ |
0032ebe9 | 177 | where: rate, |
63c93323 C |
178 | defaults: rate, |
179 | transaction: t | |
0032ebe9 | 180 | }) |
f00984c0 | 181 | if (created === true) await video.increment('dislikes', { transaction: t }) |
0032ebe9 | 182 | |
63c93323 C |
183 | if (video.isOwned() && created === true) { |
184 | // Don't resend the activity to the sender | |
50d6de9c | 185 | const exceptions = [ byActor ] |
63c93323 C |
186 | await forwardActivity(activity, t, exceptions) |
187 | } | |
0032ebe9 C |
188 | }) |
189 | } | |
190 | ||
6d852470 | 191 | async function processCreateView (byActor: ActorModel, activity: ActivityCreate) { |
63c93323 C |
192 | const view = activity.object as ViewObject |
193 | ||
3fd3ab2d | 194 | const video = await VideoModel.loadByUrlAndPopulateAccount(view.object) |
40ff5707 C |
195 | |
196 | if (!video) throw new Error('Unknown video ' + view.object) | |
197 | ||
50d6de9c | 198 | const account = await ActorModel.loadByUrl(view.actor) |
40ff5707 C |
199 | if (!account) throw new Error('Unknown account ' + view.actor) |
200 | ||
201 | await video.increment('views') | |
202 | ||
63c93323 C |
203 | if (video.isOwned()) { |
204 | // Don't resend the activity to the sender | |
6d852470 | 205 | const exceptions = [ byActor ] |
63c93323 C |
206 | await forwardActivity(activity, undefined, exceptions) |
207 | } | |
40ff5707 C |
208 | } |
209 | ||
50d6de9c | 210 | function processCreateVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) { |
8e13fa7d | 211 | const options = { |
50d6de9c | 212 | arguments: [ actor, videoAbuseToCreateData ], |
8e13fa7d C |
213 | errorMessage: 'Cannot insert the remote video abuse with many retries.' |
214 | } | |
215 | ||
216 | return retryTransactionWrapper(addRemoteVideoAbuse, options) | |
217 | } | |
218 | ||
50d6de9c | 219 | function addRemoteVideoAbuse (actor: ActorModel, videoAbuseToCreateData: VideoAbuseObject) { |
8e13fa7d C |
220 | logger.debug('Reporting remote abuse for video %s.', videoAbuseToCreateData.object) |
221 | ||
50d6de9c C |
222 | const account = actor.Account |
223 | if (!account) throw new Error('Cannot create dislike with the non account actor ' + actor.url) | |
224 | ||
3fd3ab2d C |
225 | return sequelizeTypescript.transaction(async t => { |
226 | const video = await VideoModel.loadByUrlAndPopulateAccount(videoAbuseToCreateData.object, t) | |
8e13fa7d C |
227 | if (!video) { |
228 | logger.warn('Unknown video %s for remote video abuse.', videoAbuseToCreateData.object) | |
79d5caf9 | 229 | return undefined |
8e13fa7d C |
230 | } |
231 | ||
232 | const videoAbuseData = { | |
233 | reporterAccountId: account.id, | |
234 | reason: videoAbuseToCreateData.content, | |
235 | videoId: video.id | |
236 | } | |
237 | ||
3fd3ab2d | 238 | await VideoAbuseModel.create(videoAbuseData) |
8e13fa7d C |
239 | |
240 | logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object) | |
241 | }) | |
242 | } | |
6d852470 C |
243 | |
244 | function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) { | |
245 | const options = { | |
246 | arguments: [ byActor, activity ], | |
247 | errorMessage: 'Cannot create video comment with many retries.' | |
248 | } | |
249 | ||
250 | return retryTransactionWrapper(createVideoComment, options) | |
251 | } | |
252 | ||
253 | function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { | |
254 | const comment = activity.object as VideoCommentObject | |
255 | const byAccount = byActor.Account | |
256 | ||
257 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) | |
258 | ||
259 | return sequelizeTypescript.transaction(async t => { | |
ea44f375 | 260 | let video = await VideoModel.loadByUrl(comment.inReplyTo, t) |
6d852470 C |
261 | |
262 | // This is a new thread | |
263 | if (video) { | |
ea44f375 | 264 | await VideoCommentModel.create({ |
6d852470 C |
265 | url: comment.id, |
266 | text: comment.content, | |
267 | originCommentId: null, | |
268 | inReplyToComment: null, | |
269 | videoId: video.id, | |
d3ea8975 | 270 | accountId: byAccount.id |
6d852470 | 271 | }, { transaction: t }) |
ea44f375 C |
272 | } else { |
273 | const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t) | |
274 | if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo) | |
6d852470 | 275 | |
ea44f375 | 276 | video = await VideoModel.load(inReplyToComment.videoId) |
6d852470 | 277 | |
ea44f375 C |
278 | const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id |
279 | await VideoCommentModel.create({ | |
280 | url: comment.id, | |
281 | text: comment.content, | |
282 | originCommentId, | |
283 | inReplyToCommentId: inReplyToComment.id, | |
284 | videoId: video.id, | |
285 | accountId: byAccount.id | |
286 | }, { transaction: t }) | |
287 | } | |
288 | ||
289 | if (video.isOwned()) { | |
290 | // Don't resend the activity to the sender | |
291 | const exceptions = [ byActor ] | |
292 | await forwardActivity(activity, t, exceptions) | |
293 | } | |
6d852470 C |
294 | }) |
295 | } |