import * as Bluebird from 'bluebird'
import { ActivityCreate, VideoTorrentObject } from '../../../../shared'
import { DislikeObject, VideoAbuseObject, ViewObject } from '../../../../shared/models/activitypub/objects'
+import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
import { VideoRateType } from '../../../../shared/models/videos'
-import { logger, retryTransactionWrapper } from '../../../helpers'
+import { retryTransactionWrapper } from '../../../helpers/database-utils'
+import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers'
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
import { ActorModel } from '../../../models/activitypub/actor'
import { TagModel } from '../../../models/video/tag'
import { VideoModel } from '../../../models/video/video'
import { VideoAbuseModel } from '../../../models/video/video-abuse'
+import { VideoCommentModel } from '../../../models/video/video-comment'
import { VideoFileModel } from '../../../models/video/video-file'
import { getOrCreateActorAndServerAndModel } from '../actor'
-import { forwardActivity } from '../send/misc'
+import { forwardActivity, getActorsInvolvedInVideo } from '../send/misc'
import { generateThumbnailFromUrl } from '../videos'
-import { addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
+import { addVideoComments, addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
async function processCreateActivity (activity: ActivityCreate) {
const activityObject = activity.object
return processCreateVideo(actor, activity)
} else if (activityType === 'Flag') {
return processCreateVideoAbuse(actor, activityObject as VideoAbuseObject)
+ } else if (activityType === 'Note') {
+ return processCreateVideoComment(actor, activity)
}
logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
// Process outside the transaction because we could fetch remote data
if (videoToCreateData.likes && Array.isArray(videoToCreateData.likes.orderedItems)) {
+ logger.info('Adding likes of video %s.', video.uuid)
await createRates(videoToCreateData.likes.orderedItems, video, 'like')
}
if (videoToCreateData.dislikes && Array.isArray(videoToCreateData.dislikes.orderedItems)) {
+ logger.info('Adding dislikes of video %s.', video.uuid)
await createRates(videoToCreateData.dislikes.orderedItems, video, 'dislike')
}
if (videoToCreateData.shares && Array.isArray(videoToCreateData.shares.orderedItems)) {
+ logger.info('Adding shares of video %s.', video.uuid)
await addVideoShares(video, videoToCreateData.shares.orderedItems)
}
+ if (videoToCreateData.comments && Array.isArray(videoToCreateData.comments.orderedItems)) {
+ logger.info('Adding comments of video %s.', video.uuid)
+ await addVideoComments(video, videoToCreateData.comments.orderedItems)
+ }
+
return video
}
})
}
-async function processCreateView (byAccount: ActorModel, activity: ActivityCreate) {
+async function processCreateView (byActor: ActorModel, activity: ActivityCreate) {
const view = activity.object as ViewObject
const video = await VideoModel.loadByUrlAndPopulateAccount(view.object)
if (video.isOwned()) {
// Don't resend the activity to the sender
- const exceptions = [ byAccount ]
+ const exceptions = [ byActor ]
await forwardActivity(activity, undefined, exceptions)
}
}
logger.info('Remote abuse for video uuid %s created', videoAbuseToCreateData.object)
})
}
+
+function processCreateVideoComment (byActor: ActorModel, activity: ActivityCreate) {
+ const options = {
+ arguments: [ byActor, activity ],
+ errorMessage: 'Cannot create video comment with many retries.'
+ }
+
+ return retryTransactionWrapper(createVideoComment, options)
+}
+
+function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
+ const comment = activity.object as VideoCommentObject
+ const byAccount = byActor.Account
+
+ if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
+
+ return sequelizeTypescript.transaction(async t => {
+ let video = await VideoModel.loadByUrlAndPopulateAccount(comment.inReplyTo, t)
+ let objectToCreate
+
+ // This is a new thread
+ if (video) {
+ objectToCreate = {
+ url: comment.id,
+ text: comment.content,
+ originCommentId: null,
+ inReplyToComment: null,
+ videoId: video.id,
+ accountId: byAccount.id
+ }
+ } else {
+ const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t)
+ if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo)
+
+ video = await VideoModel.load(inReplyToComment.videoId)
+
+ const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id
+ objectToCreate = {
+ url: comment.id,
+ text: comment.content,
+ originCommentId,
+ inReplyToCommentId: inReplyToComment.id,
+ videoId: video.id,
+ accountId: byAccount.id
+ }
+ }
+
+ const options = {
+ where: {
+ url: objectToCreate.url
+ },
+ defaults: objectToCreate,
+ transaction: t
+ }
+ const [ ,created ] = await VideoCommentModel.findOrCreate(options)
+
+ if (video.isOwned() && created === true) {
+ // Don't resend the activity to the sender
+ const exceptions = [ byActor ]
+
+ // Mastodon does not add our announces in audience, so we forward to them manually
+ const additionalActors = await getActorsInvolvedInVideo(video, t)
+ const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)
+
+ await forwardActivity(activity, t, exceptions, additionalFollowerUrls)
+ }
+ })
+}