mirror of https://github.com/Chocobozzz/PeerTube
Send comment to followers and parents
parent
57a49263e4
commit
93ef8a9d02
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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'
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue