From 93ef8a9d02059da2fc90efedb7755c97e9e19ef4 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 8 Jan 2018 10:00:35 +0100 Subject: [PATCH] Send comment to followers and parents --- .../comment/video-comment-add.component.scss | 4 ++ .../lib/activitypub/process/process-create.ts | 32 +++++++++---- server/lib/activitypub/send/misc.ts | 47 ++++++++++++++----- server/lib/activitypub/send/send-create.ts | 40 +++++++++++----- server/models/account/account.ts | 15 +----- 5 files changed, 94 insertions(+), 44 deletions(-) diff --git a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss index 37097da72..e586880fc 100644 --- a/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss +++ b/client/src/app/videos/+video-watch/comment/video-comment-add.component.scss @@ -1,6 +1,10 @@ @import '_variables'; @import '_mixins'; +form { + margin-bottom: 30px; +} + .avatar-and-textarea { display: flex; margin-bottom: 10px; diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index ffd20fe74..a97f6ae83 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts @@ -13,8 +13,9 @@ 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 { VideoShareModel } from '../../../models/video/video-share' import { getOrCreateActorAndServerAndModel } from '../actor' -import { forwardActivity } from '../send/misc' +import { forwardActivity, getActorsInvolvedInVideo } from '../send/misc' import { generateThumbnailFromUrl } from '../videos' import { addVideoComments, addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' @@ -266,18 +267,19 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { 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.loadByUrl(comment.inReplyTo, t) + let video = await VideoModel.loadByUrlAndPopulateAccount(comment.inReplyTo, t) + let objectToCreate // This is a new thread if (video) { - await VideoCommentModel.create({ + objectToCreate = { url: comment.id, text: comment.content, originCommentId: null, inReplyToComment: null, videoId: video.id, accountId: byAccount.id - }, { transaction: t }) + } } else { const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t) if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo) @@ -285,20 +287,34 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { video = await VideoModel.load(inReplyToComment.videoId) const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id - await VideoCommentModel.create({ + objectToCreate = { url: comment.id, text: comment.content, originCommentId, inReplyToCommentId: inReplyToComment.id, videoId: video.id, accountId: byAccount.id - }, { transaction: t }) + } } - if (video.isOwned()) { + 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 ] - await forwardActivity(activity, t, exceptions) + + // 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) } }) } diff --git a/server/lib/activitypub/send/misc.ts b/server/lib/activitypub/send/misc.ts index 4aa514c15..2a9f4cae8 100644 --- a/server/lib/activitypub/send/misc.ts +++ b/server/lib/activitypub/send/misc.ts @@ -12,12 +12,13 @@ import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/ async function forwardActivity ( activity: Activity, t: Transaction, - followersException: ActorModel[] = [] + followersException: ActorModel[] = [], + additionalFollowerUrls: string[] = [] ) { const to = activity.to || [] const cc = activity.cc || [] - const followersUrls: string[] = [] + const followersUrls = additionalFollowerUrls for (const dest of to.concat(cc)) { if (dest.endsWith('/followers')) { followersUrls.push(dest) @@ -47,13 +48,25 @@ async function broadcastToFollowers ( byActor: ActorModel, toActorFollowers: ActorModel[], t: Transaction, - followersException: ActorModel[] = [] + actorsException: ActorModel[] = [] ) { - const uris = await computeFollowerUris(toActorFollowers, followersException, t) - if (uris.length === 0) { - logger.info('0 followers for %s, no broadcasting.', toActorFollowers.map(a => a.id).join(', ')) - return undefined - } + const uris = await computeFollowerUris(toActorFollowers, actorsException, t) + return broadcastTo(uris, data, byActor, t) +} + +async function broadcastToActors ( + data: any, + byActor: ActorModel, + toActors: ActorModel[], + t: Transaction, + actorsException: ActorModel[] = [] +) { + const uris = await computeUris(toActors, actorsException) + return broadcastTo(uris, data, byActor, t) +} + +async function broadcastTo (uris: string[], data: any, byActor: ActorModel, t: Transaction) { + if (uris.length === 0) return undefined logger.debug('Creating broadcast job.', { uris }) @@ -149,12 +162,20 @@ function audiencify (object: any, audience: ActivityAudience) { return Object.assign(object, audience) } -async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) { +async function computeFollowerUris (toActorFollower: ActorModel[], actorsException: ActorModel[], t: Transaction) { const toActorFollowerIds = toActorFollower.map(a => a.id) const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) - const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) - return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) + const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl) + return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) +} + +async function computeUris (toActors: ActorModel[], actorsException: ActorModel[] = []) { + const toActorSharedInboxesSet = new Set(toActors.map(a => a.sharedInboxUrl)) + + const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl) + return Array.from(toActorSharedInboxesSet) + .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) } // --------------------------------------------------------------------------- @@ -168,5 +189,7 @@ export { getObjectFollowersAudience, forwardActivity, audiencify, - getOriginVideoCommentAudience + getOriginVideoCommentAudience, + computeUris, + broadcastToActors } diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index e2ee639d9..9db663be1 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts @@ -8,7 +8,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse' import { VideoCommentModel } from '../../../models/video/video-comment' import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' import { - audiencify, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, + audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getOriginVideoAudience, getOriginVideoCommentAudience, unicastTo } from './misc' @@ -39,11 +39,20 @@ async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Tr const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) const commentObject = comment.toActivityPubObject(threadParentComments) - const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) - const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInVideo) + const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t) + actorsInvolvedInComment.push(byActor) + const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment) const data = await createActivityData(comment.url, byActor, commentObject, t, audience) + // This was a reply, send it to the parent actors + const actorsException = [ byActor ] + await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException) + + // Broadcast to our followers + await broadcastToFollowers(data, byActor, [ byActor ], t) + + // Send to origin return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl, t) } @@ -52,12 +61,21 @@ async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentMode const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) const commentObject = comment.toActivityPubObject(threadParentComments) - const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) - const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInVideo) + const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t) + actorsInvolvedInComment.push(byActor) + + const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment) const data = await createActivityData(comment.url, byActor, commentObject, t, audience) - const followersException = [ byActor ] - return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) + // This was a reply, send it to the parent actors + const actorsException = [ byActor ] + await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException) + + // Broadcast to our followers + await broadcastToFollowers(data, byActor, [ byActor ], t) + + // Send to actors involved in the comment + return broadcastToFollowers(data, byActor, actorsInvolvedInComment, t, actorsException) } async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { @@ -81,8 +99,8 @@ async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: Video // Use the server actor to send the view const serverActor = await getServerActor() - const followersException = [ byActor ] - return broadcastToFollowers(data, serverActor, actorsToForwardView, t, followersException) + const actorsException = [ byActor ] + return broadcastToFollowers(data, serverActor, actorsToForwardView, t, actorsException) } async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { @@ -104,8 +122,8 @@ async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: Vi const audience = getObjectFollowersAudience(actorsToForwardView) const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) - const followersException = [ byActor ] - return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException) + const actorsException = [ byActor ] + return broadcastToFollowers(data, byActor, actorsToForwardView, t, actorsException) } async function createActivityData ( diff --git a/server/models/account/account.ts b/server/models/account/account.ts index c85d12824..47336d1e0 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -1,26 +1,15 @@ import * as Sequelize from 'sequelize' import { - AfterDestroy, - AllowNull, - BelongsTo, - Column, - CreatedAt, - DefaultScope, - ForeignKey, - HasMany, - Is, - Model, - Table, + AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Model, Table, UpdatedAt } from 'sequelize-typescript' import { Account } from '../../../shared/models/actors' -import { isUserUsernameValid } from '../../helpers/custom-validators/users' import { sendDeleteActor } from '../../lib/activitypub/send' import { ActorModel } from '../activitypub/actor' import { ApplicationModel } from '../application/application' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' -import { getSort, throwIfNotValid } from '../utils' +import { getSort } from '../utils' import { VideoChannelModel } from '../video/video-channel' import { UserModel } from './user' -- 2.25.1