diff options
Diffstat (limited to 'server/lib/activitypub/video-comments.ts')
-rw-r--r-- | server/lib/activitypub/video-comments.ts | 156 |
1 files changed, 156 insertions, 0 deletions
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts new file mode 100644 index 000000000..17c86a381 --- /dev/null +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -0,0 +1,156 @@ | |||
1 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | ||
2 | import { isVideoCommentObjectValid } from '../../helpers/custom-validators/activitypub/video-comments' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import { doRequest } from '../../helpers/requests' | ||
5 | import { ACTIVITY_PUB } from '../../initializers' | ||
6 | import { ActorModel } from '../../models/activitypub/actor' | ||
7 | import { VideoModel } from '../../models/video/video' | ||
8 | import { VideoCommentModel } from '../../models/video/video-comment' | ||
9 | import { getOrCreateActorAndServerAndModel } from './actor' | ||
10 | import { getOrCreateAccountAndVideoAndChannel } from './videos' | ||
11 | |||
12 | async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) { | ||
13 | let originCommentId: number = null | ||
14 | let inReplyToCommentId: number = null | ||
15 | |||
16 | // If this is not a reply to the video (thread), create or get the parent comment | ||
17 | if (video.url !== comment.inReplyTo) { | ||
18 | const [ parent ] = await addVideoComment(video, comment.inReplyTo) | ||
19 | if (!parent) { | ||
20 | logger.warn('Cannot fetch or get parent comment %s of comment %s.', comment.inReplyTo, comment.id) | ||
21 | return undefined | ||
22 | } | ||
23 | |||
24 | originCommentId = parent.originCommentId || parent.id | ||
25 | inReplyToCommentId = parent.id | ||
26 | } | ||
27 | |||
28 | return { | ||
29 | url: comment.url, | ||
30 | text: comment.content, | ||
31 | videoId: video.id, | ||
32 | accountId: actor.Account.id, | ||
33 | inReplyToCommentId, | ||
34 | originCommentId, | ||
35 | createdAt: new Date(comment.published), | ||
36 | updatedAt: new Date(comment.updated) | ||
37 | } | ||
38 | } | ||
39 | |||
40 | async function addVideoComments (instance: VideoModel, commentUrls: string[]) { | ||
41 | for (const commentUrl of commentUrls) { | ||
42 | await addVideoComment(instance, commentUrl) | ||
43 | } | ||
44 | } | ||
45 | |||
46 | async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { | ||
47 | logger.info('Fetching remote video comment %s.', commentUrl) | ||
48 | |||
49 | const { body } = await doRequest({ | ||
50 | uri: commentUrl, | ||
51 | json: true, | ||
52 | activityPub: true | ||
53 | }) | ||
54 | |||
55 | if (isVideoCommentObjectValid(body) === false) { | ||
56 | logger.debug('Remote video comment JSON is not valid.', { body }) | ||
57 | return undefined | ||
58 | } | ||
59 | |||
60 | const actorUrl = body.attributedTo | ||
61 | if (!actorUrl) return [] | ||
62 | |||
63 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) | ||
64 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) | ||
65 | if (!entry) return [] | ||
66 | |||
67 | return VideoCommentModel.findOrCreate({ | ||
68 | where: { | ||
69 | url: body.id | ||
70 | }, | ||
71 | defaults: entry | ||
72 | }) | ||
73 | } | ||
74 | |||
75 | async function resolveThread (url: string, comments: VideoCommentModel[] = []) { | ||
76 | // Already have this comment? | ||
77 | const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideo(url) | ||
78 | if (commentFromDatabase) { | ||
79 | let parentComments = comments.concat([ commentFromDatabase ]) | ||
80 | |||
81 | // Speed up things and resolve directly the thread | ||
82 | if (commentFromDatabase.InReplyToVideoComment) { | ||
83 | const data = await VideoCommentModel.listThreadParentComments(commentFromDatabase, undefined, 'DESC') | ||
84 | console.log(data) | ||
85 | |||
86 | parentComments = parentComments.concat(data) | ||
87 | } | ||
88 | |||
89 | return resolveThread(commentFromDatabase.Video.url, parentComments) | ||
90 | } | ||
91 | |||
92 | try { | ||
93 | // Maybe it's a reply to a video? | ||
94 | const { video } = await getOrCreateAccountAndVideoAndChannel(url) | ||
95 | |||
96 | if (comments.length !== 0) { | ||
97 | const firstReply = comments[ comments.length - 1 ] | ||
98 | firstReply.inReplyToCommentId = null | ||
99 | firstReply.originCommentId = null | ||
100 | firstReply.videoId = video.id | ||
101 | comments[comments.length - 1] = await firstReply.save() | ||
102 | |||
103 | for (let i = comments.length - 2; i >= 0; i--) { | ||
104 | const comment = comments[ i ] | ||
105 | comment.originCommentId = firstReply.id | ||
106 | comment.inReplyToCommentId = comments[ i + 1 ].id | ||
107 | comment.videoId = video.id | ||
108 | |||
109 | comments[i] = await comment.save() | ||
110 | } | ||
111 | } | ||
112 | |||
113 | return { video, parents: comments } | ||
114 | } catch (err) { | ||
115 | logger.debug('Cannot get or create account and video and channel for reply %s, fetch comment', url, err) | ||
116 | |||
117 | if (comments.length > ACTIVITY_PUB.MAX_RECURSION_COMMENTS) { | ||
118 | throw new Error('Recursion limit reached when resolving a thread') | ||
119 | } | ||
120 | |||
121 | const { body } = await doRequest({ | ||
122 | uri: url, | ||
123 | json: true, | ||
124 | activityPub: true | ||
125 | }) | ||
126 | |||
127 | if (isVideoCommentObjectValid(body) === false) { | ||
128 | throw new Error('Remote video comment JSON is not valid :' + JSON.stringify(body)) | ||
129 | } | ||
130 | |||
131 | const actorUrl = body.attributedTo | ||
132 | if (!actorUrl) throw new Error('Miss attributed to in comment') | ||
133 | |||
134 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) | ||
135 | const comment = new VideoCommentModel({ | ||
136 | url: body.url, | ||
137 | text: body.content, | ||
138 | videoId: null, | ||
139 | accountId: actor.Account.id, | ||
140 | inReplyToCommentId: null, | ||
141 | originCommentId: null, | ||
142 | createdAt: new Date(body.published), | ||
143 | updatedAt: new Date(body.updated) | ||
144 | }) | ||
145 | |||
146 | return resolveThread(body.inReplyTo, comments.concat([ comment ])) | ||
147 | } | ||
148 | |||
149 | } | ||
150 | |||
151 | export { | ||
152 | videoCommentActivityObjectToDBAttributes, | ||
153 | addVideoComments, | ||
154 | addVideoComment, | ||
155 | resolveThread | ||
156 | } | ||