diff options
-rw-r--r-- | server/controllers/api/videos/comment.ts | 4 | ||||
-rw-r--r-- | server/lib/activitypub/process/process-create.ts | 36 | ||||
-rw-r--r-- | server/lib/activitypub/send/send-create.ts | 71 | ||||
-rw-r--r-- | server/lib/video-comment.ts | 26 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 32 |
5 files changed, 118 insertions, 51 deletions
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index ac64f0154..e9dbb6d1b 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -78,7 +78,7 @@ function addVideoCommentThread (req: express.Request, res: express.Response) { | |||
78 | return sequelizeTypescript.transaction(async t => { | 78 | return sequelizeTypescript.transaction(async t => { |
79 | return createVideoComment({ | 79 | return createVideoComment({ |
80 | text: videoCommentInfo.text, | 80 | text: videoCommentInfo.text, |
81 | inReplyToCommentId: null, | 81 | inReplyToComment: null, |
82 | video: res.locals.video, | 82 | video: res.locals.video, |
83 | accountId: res.locals.oauth.token.User.Account.id | 83 | accountId: res.locals.oauth.token.User.Account.id |
84 | }, t) | 84 | }, t) |
@@ -106,7 +106,7 @@ function addVideoCommentReply (req: express.Request, res: express.Response, next | |||
106 | return sequelizeTypescript.transaction(async t => { | 106 | return sequelizeTypescript.transaction(async t => { |
107 | return createVideoComment({ | 107 | return createVideoComment({ |
108 | text: videoCommentInfo.text, | 108 | text: videoCommentInfo.text, |
109 | inReplyToCommentId: res.locals.videoComment.id, | 109 | inReplyToComment: res.locals.videoComment, |
110 | video: res.locals.video, | 110 | video: res.locals.video, |
111 | accountId: res.locals.oauth.token.User.Account.id | 111 | accountId: res.locals.oauth.token.User.Account.id |
112 | }, t) | 112 | }, t) |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 6c2ee97eb..628942a58 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -257,11 +257,11 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { | |||
257 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) | 257 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) |
258 | 258 | ||
259 | return sequelizeTypescript.transaction(async t => { | 259 | return sequelizeTypescript.transaction(async t => { |
260 | const video = await VideoModel.loadByUrl(comment.inReplyTo, t) | 260 | let video = await VideoModel.loadByUrl(comment.inReplyTo, t) |
261 | 261 | ||
262 | // This is a new thread | 262 | // This is a new thread |
263 | if (video) { | 263 | if (video) { |
264 | return VideoCommentModel.create({ | 264 | await VideoCommentModel.create({ |
265 | url: comment.id, | 265 | url: comment.id, |
266 | text: comment.content, | 266 | text: comment.content, |
267 | originCommentId: null, | 267 | originCommentId: null, |
@@ -269,19 +269,27 @@ function createVideoComment (byActor: ActorModel, activity: ActivityCreate) { | |||
269 | videoId: video.id, | 269 | videoId: video.id, |
270 | accountId: byAccount.id | 270 | accountId: byAccount.id |
271 | }, { transaction: t }) | 271 | }, { transaction: t }) |
272 | } | 272 | } else { |
273 | const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t) | ||
274 | if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo) | ||
273 | 275 | ||
274 | const inReplyToComment = await VideoCommentModel.loadByUrl(comment.inReplyTo, t) | 276 | video = await VideoModel.load(inReplyToComment.videoId) |
275 | if (!inReplyToComment) throw new Error('Unknown replied comment ' + comment.inReplyTo) | ||
276 | 277 | ||
277 | const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id | 278 | const originCommentId = inReplyToComment.originCommentId || inReplyToComment.id |
278 | return VideoCommentModel.create({ | 279 | await VideoCommentModel.create({ |
279 | url: comment.id, | 280 | url: comment.id, |
280 | text: comment.content, | 281 | text: comment.content, |
281 | originCommentId, | 282 | originCommentId, |
282 | inReplyToCommentId: inReplyToComment.id, | 283 | inReplyToCommentId: inReplyToComment.id, |
283 | videoId: inReplyToComment.videoId, | 284 | videoId: video.id, |
284 | accountId: byAccount.id | 285 | accountId: byAccount.id |
285 | }, { transaction: t }) | 286 | }, { transaction: t }) |
287 | } | ||
288 | |||
289 | if (video.isOwned()) { | ||
290 | // Don't resend the activity to the sender | ||
291 | const exceptions = [ byActor ] | ||
292 | await forwardActivity(activity, t, exceptions) | ||
293 | } | ||
286 | }) | 294 | }) |
287 | } | 295 | } |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 27b03c45f..ca50460be 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -5,14 +5,10 @@ import { getServerActor } from '../../../helpers' | |||
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
7 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 7 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
8 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
8 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' | 9 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' |
9 | import { | 10 | import { |
10 | audiencify, | 11 | audiencify, broadcastToFollowers, getActorsInvolvedInVideo, getAudience, getObjectFollowersAudience, getOriginVideoAudience, |
11 | broadcastToFollowers, | ||
12 | getActorsInvolvedInVideo, | ||
13 | getAudience, | ||
14 | getObjectFollowersAudience, | ||
15 | getOriginVideoAudience, | ||
16 | unicastTo | 12 | unicastTo |
17 | } from './misc' | 13 | } from './misc' |
18 | 14 | ||
@@ -37,24 +33,49 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, | |||
37 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) | 33 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) |
38 | } | 34 | } |
39 | 35 | ||
36 | async function sendCreateVideoCommentToOrigin (comment: VideoCommentModel, t: Transaction) { | ||
37 | const byActor = comment.Account.Actor | ||
38 | |||
39 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(comment.Video, t) | ||
40 | const audience = getOriginVideoAudience(comment.Video, actorsInvolvedInVideo) | ||
41 | |||
42 | const commentObject = comment.toActivityPubObject() | ||
43 | const data = await createActivityData(comment.url, byActor, commentObject, t, audience) | ||
44 | |||
45 | return unicastTo(data, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl, t) | ||
46 | } | ||
47 | |||
48 | async function sendCreateVideoCommentToVideoFollowers (comment: VideoCommentModel, t: Transaction) { | ||
49 | const byActor = comment.Account.Actor | ||
50 | |||
51 | const actorsToForwardView = await getActorsInvolvedInVideo(comment.Video, t) | ||
52 | const audience = getObjectFollowersAudience(actorsToForwardView) | ||
53 | |||
54 | const commentObject = comment.toActivityPubObject() | ||
55 | const data = await createActivityData(comment.url, byActor, commentObject, t, audience) | ||
56 | |||
57 | const followersException = [ byActor ] | ||
58 | return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException) | ||
59 | } | ||
60 | |||
40 | async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { | 61 | async function sendCreateViewToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { |
41 | const url = getVideoViewActivityPubUrl(byActor, video) | 62 | const url = getVideoViewActivityPubUrl(byActor, video) |
42 | const viewActivity = createViewActivityData(byActor, video) | 63 | const viewActivityData = createViewActivityData(byActor, video) |
43 | 64 | ||
44 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) | 65 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) |
45 | const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) | 66 | const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) |
46 | const data = await createActivityData(url, byActor, viewActivity, t, audience) | 67 | const data = await createActivityData(url, byActor, viewActivityData, t, audience) |
47 | 68 | ||
48 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) | 69 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) |
49 | } | 70 | } |
50 | 71 | ||
51 | async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 72 | async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { |
52 | const url = getVideoViewActivityPubUrl(byActor, video) | 73 | const url = getVideoViewActivityPubUrl(byActor, video) |
53 | const viewActivity = createViewActivityData(byActor, video) | 74 | const viewActivityData = createViewActivityData(byActor, video) |
54 | 75 | ||
55 | const actorsToForwardView = await getActorsInvolvedInVideo(video, t) | 76 | const actorsToForwardView = await getActorsInvolvedInVideo(video, t) |
56 | const audience = getObjectFollowersAudience(actorsToForwardView) | 77 | const audience = getObjectFollowersAudience(actorsToForwardView) |
57 | const data = await createActivityData(url, byActor, viewActivity, t, audience) | 78 | const data = await createActivityData(url, byActor, viewActivityData, t, audience) |
58 | 79 | ||
59 | // Use the server actor to send the view | 80 | // Use the server actor to send the view |
60 | const serverActor = await getServerActor() | 81 | const serverActor = await getServerActor() |
@@ -64,22 +85,22 @@ async function sendCreateViewToVideoFollowers (byActor: ActorModel, video: Video | |||
64 | 85 | ||
65 | async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { | 86 | async function sendCreateDislikeToOrigin (byActor: ActorModel, video: VideoModel, t: Transaction) { |
66 | const url = getVideoDislikeActivityPubUrl(byActor, video) | 87 | const url = getVideoDislikeActivityPubUrl(byActor, video) |
67 | const dislikeActivity = createDislikeActivityData(byActor, video) | 88 | const dislikeActivityData = createDislikeActivityData(byActor, video) |
68 | 89 | ||
69 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) | 90 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, t) |
70 | const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) | 91 | const audience = getOriginVideoAudience(video, actorsInvolvedInVideo) |
71 | const data = await createActivityData(url, byActor, dislikeActivity, t, audience) | 92 | const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) |
72 | 93 | ||
73 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) | 94 | return unicastTo(data, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl, t) |
74 | } | 95 | } |
75 | 96 | ||
76 | async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { | 97 | async function sendCreateDislikeToVideoFollowers (byActor: ActorModel, video: VideoModel, t: Transaction) { |
77 | const url = getVideoDislikeActivityPubUrl(byActor, video) | 98 | const url = getVideoDislikeActivityPubUrl(byActor, video) |
78 | const dislikeActivity = createDislikeActivityData(byActor, video) | 99 | const dislikeActivityData = createDislikeActivityData(byActor, video) |
79 | 100 | ||
80 | const actorsToForwardView = await getActorsInvolvedInVideo(video, t) | 101 | const actorsToForwardView = await getActorsInvolvedInVideo(video, t) |
81 | const audience = getObjectFollowersAudience(actorsToForwardView) | 102 | const audience = getObjectFollowersAudience(actorsToForwardView) |
82 | const data = await createActivityData(url, byActor, dislikeActivity, t, audience) | 103 | const data = await createActivityData(url, byActor, dislikeActivityData, t, audience) |
83 | 104 | ||
84 | const followersException = [ byActor ] | 105 | const followersException = [ byActor ] |
85 | return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException) | 106 | return broadcastToFollowers(data, byActor, actorsToForwardView, t, followersException) |
@@ -112,6 +133,14 @@ function createDislikeActivityData (byActor: ActorModel, video: VideoModel) { | |||
112 | } | 133 | } |
113 | } | 134 | } |
114 | 135 | ||
136 | function createViewActivityData (byActor: ActorModel, video: VideoModel) { | ||
137 | return { | ||
138 | type: 'View', | ||
139 | actor: byActor.url, | ||
140 | object: video.url | ||
141 | } | ||
142 | } | ||
143 | |||
115 | // --------------------------------------------------------------------------- | 144 | // --------------------------------------------------------------------------- |
116 | 145 | ||
117 | export { | 146 | export { |
@@ -122,15 +151,7 @@ export { | |||
122 | sendCreateViewToVideoFollowers, | 151 | sendCreateViewToVideoFollowers, |
123 | sendCreateDislikeToOrigin, | 152 | sendCreateDislikeToOrigin, |
124 | sendCreateDislikeToVideoFollowers, | 153 | sendCreateDislikeToVideoFollowers, |
125 | createDislikeActivityData | 154 | createDislikeActivityData, |
126 | } | 155 | sendCreateVideoCommentToOrigin, |
127 | 156 | sendCreateVideoCommentToVideoFollowers | |
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | function createViewActivityData (byActor: ActorModel, video: VideoModel) { | ||
131 | return { | ||
132 | type: 'View', | ||
133 | actor: byActor.url, | ||
134 | object: video.url | ||
135 | } | ||
136 | } | 157 | } |
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index e3fe26e35..ef6a8f097 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -3,27 +3,25 @@ import { ResultList } from '../../shared/models' | |||
3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' |
4 | import { VideoModel } from '../models/video/video' | 4 | import { VideoModel } from '../models/video/video' |
5 | import { VideoCommentModel } from '../models/video/video-comment' | 5 | import { VideoCommentModel } from '../models/video/video-comment' |
6 | import { getVideoCommentActivityPubUrl } from './activitypub' | 6 | import { getVideoCommentActivityPubUrl, sendVideoRateChangeToFollowers } from './activitypub' |
7 | import { sendCreateVideoCommentToOrigin, sendCreateVideoCommentToVideoFollowers } from './activitypub/send' | ||
7 | 8 | ||
8 | async function createVideoComment (obj: { | 9 | async function createVideoComment (obj: { |
9 | text: string, | 10 | text: string, |
10 | inReplyToCommentId: number, | 11 | inReplyToComment: VideoCommentModel, |
11 | video: VideoModel | 12 | video: VideoModel |
12 | accountId: number | 13 | accountId: number |
13 | }, t: Sequelize.Transaction) { | 14 | }, t: Sequelize.Transaction) { |
14 | let originCommentId: number = null | 15 | let originCommentId: number = null |
15 | 16 | ||
16 | if (obj.inReplyToCommentId) { | 17 | if (obj.inReplyToComment) { |
17 | const repliedComment = await VideoCommentModel.loadById(obj.inReplyToCommentId) | 18 | originCommentId = obj.inReplyToComment.originCommentId || obj.inReplyToComment.id |
18 | if (!repliedComment) throw new Error('Unknown replied comment.') | ||
19 | |||
20 | originCommentId = repliedComment.originCommentId || repliedComment.id | ||
21 | } | 19 | } |
22 | 20 | ||
23 | const comment = await VideoCommentModel.create({ | 21 | const comment = await VideoCommentModel.create({ |
24 | text: obj.text, | 22 | text: obj.text, |
25 | originCommentId, | 23 | originCommentId, |
26 | inReplyToCommentId: obj.inReplyToCommentId, | 24 | inReplyToCommentId: obj.inReplyToComment.id, |
27 | videoId: obj.video.id, | 25 | videoId: obj.video.id, |
28 | accountId: obj.accountId, | 26 | accountId: obj.accountId, |
29 | url: 'fake url' | 27 | url: 'fake url' |
@@ -31,7 +29,17 @@ async function createVideoComment (obj: { | |||
31 | 29 | ||
32 | comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) | 30 | comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) |
33 | 31 | ||
34 | return comment.save({ transaction: t }) | 32 | const savedComment = await comment.save({ transaction: t }) |
33 | savedComment.InReplyToVideoComment = obj.inReplyToComment | ||
34 | savedComment.Video = obj.video | ||
35 | |||
36 | if (savedComment.Video.isOwned()) { | ||
37 | await sendCreateVideoCommentToVideoFollowers(savedComment, t) | ||
38 | } else { | ||
39 | await sendCreateVideoCommentToOrigin(savedComment, t) | ||
40 | } | ||
41 | |||
42 | return savedComment | ||
35 | } | 43 | } |
36 | 44 | ||
37 | function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThreadTree { | 45 | function buildFormattedCommentTree (resultList: ResultList<VideoCommentModel>): VideoCommentThreadTree { |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 8e84bfc06..25cd6d563 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -3,6 +3,7 @@ import { | |||
3 | AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, | 3 | AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, IFindOptions, Is, Model, Scopes, Table, |
4 | UpdatedAt | 4 | UpdatedAt |
5 | } from 'sequelize-typescript' | 5 | } from 'sequelize-typescript' |
6 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | ||
6 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' | 7 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' |
7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' | 8 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub' |
8 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 9 | import { CONSTRAINTS_FIELDS } from '../../initializers' |
@@ -11,7 +12,8 @@ import { getSort, throwIfNotValid } from '../utils' | |||
11 | import { VideoModel } from './video' | 12 | import { VideoModel } from './video' |
12 | 13 | ||
13 | enum ScopeNames { | 14 | enum ScopeNames { |
14 | WITH_ACCOUNT = 'WITH_ACCOUNT' | 15 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
16 | WITH_IN_REPLY_TO = 'WITH_IN_REPLY_TO' | ||
15 | } | 17 | } |
16 | 18 | ||
17 | @Scopes({ | 19 | @Scopes({ |
@@ -19,6 +21,14 @@ enum ScopeNames { | |||
19 | include: [ | 21 | include: [ |
20 | () => AccountModel | 22 | () => AccountModel |
21 | ] | 23 | ] |
24 | }, | ||
25 | [ScopeNames.WITH_IN_REPLY_TO]: { | ||
26 | include: [ | ||
27 | { | ||
28 | model: () => VideoCommentModel, | ||
29 | as: 'InReplyTo' | ||
30 | } | ||
31 | ] | ||
22 | } | 32 | } |
23 | }) | 33 | }) |
24 | @Table({ | 34 | @Table({ |
@@ -68,6 +78,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
68 | foreignKey: { | 78 | foreignKey: { |
69 | allowNull: true | 79 | allowNull: true |
70 | }, | 80 | }, |
81 | as: 'InReplyTo', | ||
71 | onDelete: 'CASCADE' | 82 | onDelete: 'CASCADE' |
72 | }) | 83 | }) |
73 | InReplyToVideoComment: VideoCommentModel | 84 | InReplyToVideoComment: VideoCommentModel |
@@ -180,4 +191,23 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
180 | } | 191 | } |
181 | } as VideoComment | 192 | } as VideoComment |
182 | } | 193 | } |
194 | |||
195 | toActivityPubObject (): VideoCommentObject { | ||
196 | let inReplyTo: string | ||
197 | // New thread, so in AS we reply to the video | ||
198 | if (this.inReplyToCommentId === null) { | ||
199 | inReplyTo = this.Video.url | ||
200 | } else { | ||
201 | inReplyTo = this.InReplyToVideoComment.url | ||
202 | } | ||
203 | |||
204 | return { | ||
205 | type: 'Note' as 'Note', | ||
206 | id: this.url, | ||
207 | content: this.text, | ||
208 | inReplyTo, | ||
209 | published: this.createdAt.toISOString(), | ||
210 | url: this.url | ||
211 | } | ||
212 | } | ||
183 | } | 213 | } |