import { VideoChannelModel } from '../../../models/video/video-channel'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { getOrCreateActorAndServerAndModel } from '../actor'
+import { forwardActivity } from '../send/misc'
async function processDeleteActivity (activity: ActivityDelete) {
const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id
{
const videoCommentInstance = await VideoCommentModel.loadByUrlAndPopulateAccount(objectUrl)
if (videoCommentInstance) {
- return processDeleteVideoComment(actor, videoCommentInstance)
+ return processDeleteVideoComment(actor, videoCommentInstance, activity)
}
}
logger.info('Remote video channel with uuid %s removed.', videoChannelToRemove.Actor.uuid)
}
-async function processDeleteVideoComment (actor: ActorModel, videoComment: VideoCommentModel) {
+async function processDeleteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
const options = {
- arguments: [ actor, videoComment ],
+ arguments: [ byActor, videoComment, activity ],
errorMessage: 'Cannot remove the remote video comment with many retries.'
}
await retryTransactionWrapper(deleteRemoteVideoComment, options)
}
-function deleteRemoteVideoComment (actor: ActorModel, videoComment: VideoCommentModel) {
+function deleteRemoteVideoComment (byActor: ActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) {
logger.debug('Removing remote video comment "%s".', videoComment.url)
return sequelizeTypescript.transaction(async t => {
await videoComment.destroy({ transaction: t })
+ if (videoComment.Video.isOwned()) {
+ // Don't resend the activity to the sender
+ const exceptions = [ byActor ]
+ await forwardActivity(activity, t, exceptions)
+ }
+
logger.info('Remote video comment %s removed.', videoComment.url)
})
}
}
}
-function getOriginVideoCommentAudience (
+function getVideoCommentAudience (
videoComment: VideoCommentModel,
threadParentComments: VideoCommentModel[],
actorsInvolvedInVideo: ActorModel[],
return { to, cc }
}
-function audiencify (object: any, audience: ActivityAudience) {
+function audiencify <T> (object: T, audience: ActivityAudience) {
return Object.assign(object, audience)
}
getObjectFollowersAudience,
forwardActivity,
audiencify,
- getOriginVideoCommentAudience,
+ getVideoCommentAudience,
computeUris,
broadcastToActors
}
getAudience,
getObjectFollowersAudience,
getOriginVideoAudience,
- getOriginVideoCommentAudience,
+ getVideoCommentAudience,
unicastTo
} from './misc'
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
actorsInvolvedInComment.push(byActor)
- const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
+ const audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
actorsInvolvedInComment.push(byActor)
- const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
+ const audience = getVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment, true)
const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
// This was a reply, send it to the parent actors
}
return audiencify({
- type: 'Create',
+ type: 'Create' as 'Create',
id: url + '/activity',
actor: byActor.url,
object: audiencify(object, audience)
import { Transaction } from 'sequelize'
-import { ActivityDelete } from '../../../../shared/models/activitypub'
+import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub'
import { ActorModel } from '../../../models/activitypub/actor'
import { VideoModel } from '../../../models/video/video'
import { VideoCommentModel } from '../../../models/video/video-comment'
import { VideoShareModel } from '../../../models/video/video-share'
import { getDeleteActivityPubUrl } from '../url'
-import { broadcastToFollowers } from './misc'
+import { audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getVideoCommentAudience, unicastTo } from './misc'
async function sendDeleteVideo (video: VideoModel, t: Transaction) {
const url = getDeleteActivityPubUrl(video.url)
}
async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) {
- const url = getDeleteActivityPubUrl(videoComment.url)
+ const isVideoOrigin = videoComment.Video.isOwned()
+ const url = getDeleteActivityPubUrl(videoComment.url)
const byActor = videoComment.Account.Actor
- const data = deleteActivityData(url, videoComment.url, byActor)
+ const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, t)
- const actorsInvolved = await VideoShareModel.loadActorsByShare(videoComment.Video.id, t)
- actorsInvolved.push(videoComment.Video.VideoChannel.Account.Actor)
- actorsInvolved.push(byActor)
+ const actorsInvolvedInComment = await getActorsInvolvedInVideo(videoComment.Video, t)
+ actorsInvolvedInComment.push(byActor)
- return broadcastToFollowers(data, byActor, actorsInvolved, t)
+ const audience = getVideoCommentAudience(videoComment, threadParentComments, actorsInvolvedInComment, isVideoOrigin)
+ const data = deleteActivityData(url, videoComment.url, byActor, audience)
+
+ // This was a reply, send it to the parent actors
+ const actorsException = [ byActor ]
+ await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), actorsException)
+
+ // Broadcast to our followers
+ await broadcastToFollowers(data, byActor, [ byActor ], t)
+
+ // Send to actors involved in the comment
+ if (isVideoOrigin) return broadcastToFollowers(data, byActor, actorsInvolvedInComment, t, actorsException)
+
+ // Send to origin
+ return unicastTo(data, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)
}
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
-function deleteActivityData (url: string, object: string, byActor: ActorModel): ActivityDelete {
- return {
- type: 'Delete',
+function deleteActivityData (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete {
+ const activity = {
+ type: 'Delete' as 'Delete',
id: url,
actor: byActor.url,
object
}
+
+ if (audience) return audiencify(activity, audience)
+
+ return activity
}
}
return audiencify({
- type: 'Like',
+ type: 'Like' as 'Like',
id: url,
actor: byActor.url,
object: video.url
}
return audiencify({
- type: 'Undo',
+ type: 'Undo' as 'Undo',
id: url,
actor: byActor.url,
object
}
return audiencify({
- type: 'Update',
+ type: 'Update' as 'Update',
id: url,
actor: byActor.url,
object: audiencify(object, audience)
})
describe('Should comment these videos', function () {
+ let childOfFirstChild: VideoCommentThreadTree
+
it('Should add comment (threads and replies)', async function () {
this.timeout(25000)
expect(firstChild.comment.account.host).equal('localhost:9002')
expect(firstChild.children).to.have.lengthOf(1)
- const childOfFirstChild = firstChild.children[0]
+ childOfFirstChild = firstChild.children[0]
expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1')
expect(childOfFirstChild.comment.account.name).equal('root')
expect(childOfFirstChild.comment.account.host).equal('localhost:9003')
}
})
+ it('Should delete a reply', async function () {
+ this.timeout(10000)
+
+ await deleteVideoComment(servers[2].url, servers[2].accessToken, videoUUID, childOfFirstChild.comment.id)
+
+ await wait(5000)
+ })
+
+ it('Should not have this comment anymore', async function () {
+ for (const server of servers) {
+ const res1 = await getVideoCommentThreads(server.url, videoUUID, 0, 5)
+ const threadId = res1.body.data.find(c => c.text === 'my super first comment').id
+
+ const res2 = await getVideoThreadComments(server.url, videoUUID, threadId)
+
+ const tree: VideoCommentThreadTree = res2.body
+ expect(tree.comment.text).equal('my super first comment')
+
+ const firstChild = tree.children[0]
+ expect(firstChild.comment.text).to.equal('my super answer to thread 1')
+ expect(firstChild.children).to.have.lengthOf(0)
+
+ const secondChild = tree.children[1]
+ expect(secondChild.comment.text).to.equal('my second answer to thread 1')
+ }
+ })
+
it('Should delete the thread comments', async function () {
this.timeout(10000)