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 '_mixins';
form {
margin-bottom: 30px;
}
.avatar-and-textarea {
display: flex;
margin-bottom: 10px;

View File

@ -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)
}
})
}

View File

@ -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
}

View File

@ -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 (

View File

@ -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'