Send comment to followers and parents

pull/200/head
Chocobozzz 2018-01-08 10:00:35 +01:00
parent 57a49263e4
commit 93ef8a9d02
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
5 changed files with 94 additions and 44 deletions

View File

@ -1,6 +1,10 @@
@import '_variables'; @import '_variables';
@import '_mixins'; @import '_mixins';
form {
margin-bottom: 30px;
}
.avatar-and-textarea { .avatar-and-textarea {
display: flex; display: flex;
margin-bottom: 10px; margin-bottom: 10px;

View File

@ -13,8 +13,9 @@ import { VideoModel } from '../../../models/video/video'
import { VideoAbuseModel } from '../../../models/video/video-abuse' import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoCommentModel } from '../../../models/video/video-comment'
import { VideoFileModel } from '../../../models/video/video-file' import { VideoFileModel } from '../../../models/video/video-file'
import { VideoShareModel } from '../../../models/video/video-share'
import { getOrCreateActorAndServerAndModel } from '../actor' import { getOrCreateActorAndServerAndModel } from '../actor'
import { forwardActivity } from '../send/misc' import { forwardActivity, getActorsInvolvedInVideo } from '../send/misc'
import { generateThumbnailFromUrl } from '../videos' import { generateThumbnailFromUrl } from '../videos'
import { addVideoComments, addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 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) if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
return sequelizeTypescript.transaction(async t => { 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 // This is a new thread
if (video) { if (video) {
await VideoCommentModel.create({ objectToCreate = {
url: comment.id, url: comment.id,
text: comment.content, text: comment.content,
originCommentId: null, originCommentId: null,
inReplyToComment: null, inReplyToComment: null,
videoId: video.id, videoId: video.id,
accountId: byAccount.id accountId: byAccount.id
}, { transaction: t }) }
} else { } else {
const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t) const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t)
if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo) 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) video = await VideoModel.load(inReplyToComment.videoId)
const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id
await VideoCommentModel.create({ objectToCreate = {
url: comment.id, url: comment.id,
text: comment.content, text: comment.content,
originCommentId, originCommentId,
inReplyToCommentId: inReplyToComment.id, inReplyToCommentId: inReplyToComment.id,
videoId: video.id, videoId: video.id,
accountId: byAccount.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 // Don't resend the activity to the sender
const exceptions = [ byActor ] 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)
} }
}) })
} }

View File

@ -12,12 +12,13 @@ import { activitypubHttpJobScheduler, ActivityPubHttpPayload } from '../../jobs/
async function forwardActivity ( async function forwardActivity (
activity: Activity, activity: Activity,
t: Transaction, t: Transaction,
followersException: ActorModel[] = [] followersException: ActorModel[] = [],
additionalFollowerUrls: string[] = []
) { ) {
const to = activity.to || [] const to = activity.to || []
const cc = activity.cc || [] const cc = activity.cc || []
const followersUrls: string[] = [] const followersUrls = additionalFollowerUrls
for (const dest of to.concat(cc)) { for (const dest of to.concat(cc)) {
if (dest.endsWith('/followers')) { if (dest.endsWith('/followers')) {
followersUrls.push(dest) followersUrls.push(dest)
@ -47,13 +48,25 @@ async function broadcastToFollowers (
byActor: ActorModel, byActor: ActorModel,
toActorFollowers: ActorModel[], toActorFollowers: ActorModel[],
t: Transaction, t: Transaction,
followersException: ActorModel[] = [] actorsException: ActorModel[] = []
) { ) {
const uris = await computeFollowerUris(toActorFollowers, followersException, t) const uris = await computeFollowerUris(toActorFollowers, actorsException, t)
if (uris.length === 0) { return broadcastTo(uris, data, byActor, t)
logger.info('0 followers for %s, no broadcasting.', toActorFollowers.map(a => a.id).join(', ')) }
return undefined
} 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 }) logger.debug('Creating broadcast job.', { uris })
@ -149,12 +162,20 @@ function audiencify (object: any, audience: ActivityAudience) {
return Object.assign(object, audience) 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 toActorFollowerIds = toActorFollower.map(a => a.id)
const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl)
return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) 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, getObjectFollowersAudience,
forwardActivity, forwardActivity,
audiencify, audiencify,
getOriginVideoCommentAudience getOriginVideoCommentAudience,
computeUris,
broadcastToActors
} }

View File

@ -8,7 +8,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse'
import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoCommentModel } from '../../../models/video/video-comment'
import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
import { import {
audiencify, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience,
getOriginVideoAudience, getOriginVideoCommentAudience, getOriginVideoAudience, getOriginVideoCommentAudience,
unicastTo unicastTo
} from './misc' } from './misc'
@ -39,11 +39,20 @@ async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Tr
const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t)
const commentObject = comment.toActivityPubObject(threadParentComments) const commentObject = comment.toActivityPubObject(threadParentComments)
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInVideo) actorsInvolvedInComment.push(byActor)
const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
const data = await createActivityData(comment.url, byActor, commentObject, t, audience) 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) 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 threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t)
const commentObject = comment.toActivityPubObject(threadParentComments) const commentObject = comment.toActivityPubObject(threadParentComments)
const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInVideo) actorsInvolvedInComment.push(byActor)
const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
const data = await createActivityData(comment.url, byActor, commentObject, t, audience) const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
const followersException = [ byActor ] // This was a reply, send it to the parent actors
return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) 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) { 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 // Use the server actor to send the view
const serverActor = await getServerActor() const serverActor = await getServerActor()
const followersException = [ byActor ] const actorsException = [ byActor ]
return broadcastToFollowers(data, serverActor, actorsToForwardView, t, followersException) return broadcastToFollowers(data, serverActor, actorsToForwardView, t, actorsException)
} }
async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { 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 audience = getObjectFollowersAudience(actorsToForwardView)
const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) const data = await createActivityData(url, byActor, dislikeActivityData, t, audience)
const followersException = [ byActor ] const actorsException = [ byActor ]
return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException) return broadcastToFollowers(data, byActor, actorsToForwardView, t, actorsException)
} }
async function createActivityData ( async function createActivityData (

View File

@ -1,26 +1,15 @@
import * as Sequelize from 'sequelize' import * as Sequelize from 'sequelize'
import { import {
AfterDestroy, AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Model, Table,
AllowNull,
BelongsTo,
Column,
CreatedAt,
DefaultScope,
ForeignKey,
HasMany,
Is,
Model,
Table,
UpdatedAt UpdatedAt
} from 'sequelize-typescript' } from 'sequelize-typescript'
import { Account } from '../../../shared/models/actors' import { Account } from '../../../shared/models/actors'
import { isUserUsernameValid } from '../../helpers/custom-validators/users'
import { sendDeleteActor } from '../../lib/activitypub/send' import { sendDeleteActor } from '../../lib/activitypub/send'
import { ActorModel } from '../activitypub/actor' import { ActorModel } from '../activitypub/actor'
import { ApplicationModel } from '../application/application' import { ApplicationModel } from '../application/application'
import { AvatarModel } from '../avatar/avatar' import { AvatarModel } from '../avatar/avatar'
import { ServerModel } from '../server/server' import { ServerModel } from '../server/server'
import { getSort, throwIfNotValid } from '../utils' import { getSort } from '../utils'
import { VideoChannelModel } from '../video/video-channel' import { VideoChannelModel } from '../video/video-channel'
import { UserModel } from './user' import { UserModel } from './user'