aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/videos/+video-watch/comment/video-comment-add.component.scss4
-rw-r--r--server/lib/activitypub/process/process-create.ts32
-rw-r--r--server/lib/activitypub/send/misc.ts47
-rw-r--r--server/lib/activitypub/send/send-create.ts40
-rw-r--r--server/models/account/account.ts15
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 @@
1@import '_variables'; 1@import '_variables';
2@import '_mixins'; 2@import '_mixins';
3 3
4form {
5 margin-bottom: 30px;
6}
7
4.avatar-and-textarea { 8.avatar-and-textarea {
5 display: flex; 9 display: flex;
6 margin-bottom: 10px; 10 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'
13import { VideoAbuseModel } from '../../../models/video/video-abuse' 13import { VideoAbuseModel } from '../../../models/video/video-abuse'
14import { VideoCommentModel } from '../../../models/video/video-comment' 14import { VideoCommentModel } from '../../../models/video/video-comment'
15import { VideoFileModel } from '../../../models/video/video-file' 15import { VideoFileModel } from '../../../models/video/video-file'
16import { VideoShareModel } from '../../../models/video/video-share'
16import { getOrCreateActorAndServerAndModel } from '../actor' 17import { getOrCreateActorAndServerAndModel } from '../actor'
17import { forwardActivity } from '../send/misc' 18import { forwardActivity, getActorsInvolvedInVideo } from '../send/misc'
18import { generateThumbnailFromUrl } from '../videos' 19import { generateThumbnailFromUrl } from '../videos'
19import { addVideoComments, addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc' 20import { addVideoComments, addVideoShares, videoActivityObjectToDBAttributes, videoFileActivityUrlToDBAttributes } from './misc'
20 21
@@ -266,18 +267,19 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
266 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) 267 if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
267 268
268 return sequelizeTypescript.transaction(async t => { 269 return sequelizeTypescript.transaction(async t => {
269 let video = await VideoModel.loadByUrl(comment.inReplyTo, t) 270 let video = await VideoModel.loadByUrlAndPopulateAccount(comment.inReplyTo, t)
271 let objectToCreate
270 272
271 // This is a new thread 273 // This is a new thread
272 if (video) { 274 if (video) {
273 await VideoCommentModel.create({ 275 objectToCreate = {
274 url: comment.id, 276 url: comment.id,
275 text: comment.content, 277 text: comment.content,
276 originCommentId: null, 278 originCommentId: null,
277 inReplyToComment: null, 279 inReplyToComment: null,
278 videoId: video.id, 280 videoId: video.id,
279 accountId: byAccount.id 281 accountId: byAccount.id
280 }, { transaction: t }) 282 }
281 } else { 283 } else {
282 const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t) 284 const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t)
283 if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo) 285 if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo)
@@ -285,20 +287,34 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) {
285 video = await VideoModel.load(inReplyToComment.videoId) 287 video = await VideoModel.load(inReplyToComment.videoId)
286 288
287 const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id 289 const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id
288 await VideoCommentModel.create({ 290 objectToCreate = {
289 url: comment.id, 291 url: comment.id,
290 text: comment.content, 292 text: comment.content,
291 originCommentId, 293 originCommentId,
292 inReplyToCommentId: inReplyToComment.id, 294 inReplyToCommentId: inReplyToComment.id,
293 videoId: video.id, 295 videoId: video.id,
294 accountId: byAccount.id 296 accountId: byAccount.id
295 }, { transaction: t }) 297 }
296 } 298 }
297 299
298 if (video.isOwned()) { 300 const options = {
301 where: {
302 url: objectToCreate.url
303 },
304 defaults: objectToCreate,
305 transaction: t
306 }
307 const [ ,created ] = await VideoCommentModel.findOrCreate(options)
308
309 if (video.isOwned() && created === true) {
299 // Don't resend the activity to the sender 310 // Don't resend the activity to the sender
300 const exceptions = [ byActor ] 311 const exceptions = [ byActor ]
301 await forwardActivity(activity, t, exceptions) 312
313 // Mastodon does not add our announces in audience, so we forward to them manually
314 const additionalActors = await getActorsInvolvedInVideo(video, t)
315 const additionalFollowerUrls = additionalActors.map(a => a.followersUrl)
316
317 await forwardActivity(activity, t, exceptions, additionalFollowerUrls)
302 } 318 }
303 }) 319 })
304} 320}
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/
12async function forwardActivity ( 12async function forwardActivity (
13 activity: Activity, 13 activity: Activity,
14 t: Transaction, 14 t: Transaction,
15 followersException: ActorModel[] = [] 15 followersException: ActorModel[] = [],
16 additionalFollowerUrls: string[] = []
16) { 17) {
17 const to = activity.to || [] 18 const to = activity.to || []
18 const cc = activity.cc || [] 19 const cc = activity.cc || []
19 20
20 const followersUrls: string[] = [] 21 const followersUrls = additionalFollowerUrls
21 for (const dest of to.concat(cc)) { 22 for (const dest of to.concat(cc)) {
22 if (dest.endsWith('/followers')) { 23 if (dest.endsWith('/followers')) {
23 followersUrls.push(dest) 24 followersUrls.push(dest)
@@ -47,13 +48,25 @@ async function broadcastToFollowers (
47 byActor: ActorModel, 48 byActor: ActorModel,
48 toActorFollowers: ActorModel[], 49 toActorFollowers: ActorModel[],
49 t: Transaction, 50 t: Transaction,
50 followersException: ActorModel[] = [] 51 actorsException: ActorModel[] = []
51) { 52) {
52 const uris = await computeFollowerUris(toActorFollowers, followersException, t) 53 const uris = await computeFollowerUris(toActorFollowers, actorsException, t)
53 if (uris.length === 0) { 54 return broadcastTo(uris, data, byActor, t)
54 logger.info('0 followers for %s, no broadcasting.', toActorFollowers.map(a => a.id).join(', ')) 55}
55 return undefined 56
56 } 57async function broadcastToActors (
58 data: any,
59 byActor: ActorModel,
60 toActors: ActorModel[],
61 t: Transaction,
62 actorsException: ActorModel[] = []
63) {
64 const uris = await computeUris(toActors, actorsException)
65 return broadcastTo(uris, data, byActor, t)
66}
67
68async function broadcastTo (uris: string[], data: any, byActor: ActorModel, t: Transaction) {
69 if (uris.length === 0) return undefined
57 70
58 logger.debug('Creating broadcast job.', { uris }) 71 logger.debug('Creating broadcast job.', { uris })
59 72
@@ -149,12 +162,20 @@ function audiencify (object: any, audience: ActivityAudience) {
149 return Object.assign(object, audience) 162 return Object.assign(object, audience)
150} 163}
151 164
152async function computeFollowerUris (toActorFollower: ActorModel[], followersException: ActorModel[], t: Transaction) { 165async function computeFollowerUris (toActorFollower: ActorModel[], actorsException: ActorModel[], t: Transaction) {
153 const toActorFollowerIds = toActorFollower.map(a => a.id) 166 const toActorFollowerIds = toActorFollower.map(a => a.id)
154 167
155 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) 168 const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t)
156 const followersSharedInboxException = followersException.map(f => f.sharedInboxUrl) 169 const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl)
157 return result.data.filter(sharedInbox => followersSharedInboxException.indexOf(sharedInbox) === -1) 170 return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
171}
172
173async function computeUris (toActors: ActorModel[], actorsException: ActorModel[] = []) {
174 const toActorSharedInboxesSet = new Set(toActors.map(a => a.sharedInboxUrl))
175
176 const sharedInboxesException = actorsException.map(f => f.sharedInboxUrl)
177 return Array.from(toActorSharedInboxesSet)
178 .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1)
158} 179}
159 180
160// --------------------------------------------------------------------------- 181// ---------------------------------------------------------------------------
@@ -168,5 +189,7 @@ export {
168 getObjectFollowersAudience, 189 getObjectFollowersAudience,
169 forwardActivity, 190 forwardActivity,
170 audiencify, 191 audiencify,
171 getOriginVideoCommentAudience 192 getOriginVideoCommentAudience,
193 computeUris,
194 broadcastToActors
172} 195}
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'
8import { VideoCommentModel } from '../../../models/video/video-comment' 8import { VideoCommentModel } from '../../../models/video/video-comment'
9import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' 9import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url'
10import { 10import {
11 audiencify, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, 11 audiencify, broadcastToActors, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience,
12 getOriginVideoAudience, getOriginVideoCommentAudience, 12 getOriginVideoAudience, getOriginVideoCommentAudience,
13 unicastTo 13 unicastTo
14} from './misc' 14} from './misc'
@@ -39,11 +39,20 @@ async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Tr
39 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) 39 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t)
40 const commentObject = comment.toActivityPubObject(threadParentComments) 40 const commentObject = comment.toActivityPubObject(threadParentComments)
41 41
42 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) 42 const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
43 const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInVideo) 43 actorsInvolvedInComment.push(byActor)
44 const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
44 45
45 const data = await createActivityData(comment.url, byActor, commentObject, t, audience) 46 const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
46 47
48 // This was a reply, send it to the parent actors
49 const actorsException = [ byActor ]
50 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException)
51
52 // Broadcast to our followers
53 await broadcastToFollowers(data, byActor, [ byActor ], t)
54
55 // Send to origin
47 return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl, t) 56 return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl, t)
48} 57}
49 58
@@ -52,12 +61,21 @@ async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentMode
52 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t) 61 const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, t)
53 const commentObject = comment.toActivityPubObject(threadParentComments) 62 const commentObject = comment.toActivityPubObject(threadParentComments)
54 63
55 const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) 64 const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, t)
56 const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInVideo) 65 actorsInvolvedInComment.push(byActor)
66
67 const audience = getOriginVideoCommentAudience(comment, threadParentComments, actorsInvolvedInComment)
57 const data = await createActivityData(comment.url, byActor, commentObject, t, audience) 68 const data = await createActivityData(comment.url, byActor, commentObject, t, audience)
58 69
59 const followersException = [ byActor ] 70 // This was a reply, send it to the parent actors
60 return broadcastToFollowers(data, byActor, actorsInvolvedInVideo, t, followersException) 71 const actorsException = [ byActor ]
72 await broadcastToActors(data, byActor, threadParentComments.map(c => c.Account.Actor), t, actorsException)
73
74 // Broadcast to our followers
75 await broadcastToFollowers(data, byActor, [ byActor ], t)
76
77 // Send to actors involved in the comment
78 return broadcastToFollowers(data, byActor, actorsInvolvedInComment, t, actorsException)
61} 79}
62 80
63async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { 81async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -81,8 +99,8 @@ async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: Video
81 99
82 // Use the server actor to send the view 100 // Use the server actor to send the view
83 const serverActor = await getServerActor() 101 const serverActor = await getServerActor()
84 const followersException = [ byActor ] 102 const actorsException = [ byActor ]
85 return broadcastToFollowers(data, serverActor, actorsToForwardView, t, followersException) 103 return broadcastToFollowers(data, serverActor, actorsToForwardView, t, actorsException)
86} 104}
87 105
88async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { 106async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) {
@@ -104,8 +122,8 @@ async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: Vi
104 const audience = getObjectFollowersAudience(actorsToForwardView) 122 const audience = getObjectFollowersAudience(actorsToForwardView)
105 const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) 123 const data = await createActivityData(url, byActor, dislikeActivityData, t, audience)
106 124
107 const followersException = [ byActor ] 125 const actorsException = [ byActor ]
108 return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException) 126 return broadcastToFollowers(data, byActor, actorsToForwardView, t, actorsException)
109} 127}
110 128
111async function createActivityData ( 129async 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 @@
1import * as Sequelize from 'sequelize' 1import * as Sequelize from 'sequelize'
2import { 2import {
3 AfterDestroy, 3 AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DefaultScope, ForeignKey, HasMany, Model, Table,
4 AllowNull,
5 BelongsTo,
6 Column,
7 CreatedAt,
8 DefaultScope,
9 ForeignKey,
10 HasMany,
11 Is,
12 Model,
13 Table,
14 UpdatedAt 4 UpdatedAt
15} from 'sequelize-typescript' 5} from 'sequelize-typescript'
16import { Account } from '../../../shared/models/actors' 6import { Account } from '../../../shared/models/actors'
17import { isUserUsernameValid } from '../../helpers/custom-validators/users'
18import { sendDeleteActor } from '../../lib/activitypub/send' 7import { sendDeleteActor } from '../../lib/activitypub/send'
19import { ActorModel } from '../activitypub/actor' 8import { ActorModel } from '../activitypub/actor'
20import { ApplicationModel } from '../application/application' 9import { ApplicationModel } from '../application/application'
21import { AvatarModel } from '../avatar/avatar' 10import { AvatarModel } from '../avatar/avatar'
22import { ServerModel } from '../server/server' 11import { ServerModel } from '../server/server'
23import { getSort, throwIfNotValid } from '../utils' 12import { getSort } from '../utils'
24import { VideoChannelModel } from '../video/video-channel' 13import { VideoChannelModel } from '../video/video-channel'
25import { UserModel } from './user' 14import { UserModel } from './user'
26 15