Add ability to disable video comments
[oweals/peertube.git] / server / lib / activitypub / process / misc.ts
1 import * as magnetUtil from 'magnet-uri'
2 import { VideoTorrentObject } from '../../../../shared'
3 import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object'
4 import { VideoPrivacy } from '../../../../shared/models/videos'
5 import { isVideoFileInfoHashValid } from '../../../helpers/custom-validators/videos'
6 import { logger } from '../../../helpers/logger'
7 import { doRequest } from '../../../helpers/requests'
8 import { ACTIVITY_PUB, VIDEO_MIMETYPE_EXT } from '../../../initializers'
9 import { ActorModel } from '../../../models/activitypub/actor'
10 import { VideoModel } from '../../../models/video/video'
11 import { VideoChannelModel } from '../../../models/video/video-channel'
12 import { VideoCommentModel } from '../../../models/video/video-comment'
13 import { VideoShareModel } from '../../../models/video/video-share'
14 import { getOrCreateActorAndServerAndModel } from '../actor'
15
16 async function videoActivityObjectToDBAttributes (
17   videoChannel: VideoChannelModel,
18   videoObject: VideoTorrentObject,
19   to: string[] = [],
20   cc: string[] = []
21 ) {
22   let privacy = VideoPrivacy.PRIVATE
23   if (to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.PUBLIC
24   else if (cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1) privacy = VideoPrivacy.UNLISTED
25
26   const duration = videoObject.duration.replace(/[^\d]+/, '')
27   let language = null
28   if (videoObject.language) {
29     language = parseInt(videoObject.language.identifier, 10)
30   }
31
32   let category = null
33   if (videoObject.category) {
34     category = parseInt(videoObject.category.identifier, 10)
35   }
36
37   let licence = null
38   if (videoObject.licence) {
39     licence = parseInt(videoObject.licence.identifier, 10)
40   }
41
42   let description = null
43   if (videoObject.content) {
44     description = videoObject.content
45   }
46
47   return {
48     name: videoObject.name,
49     uuid: videoObject.uuid,
50     url: videoObject.id,
51     category,
52     licence,
53     language,
54     description,
55     nsfw: videoObject.nsfw,
56     commentsEnabled: videoObject.commentsEnabled,
57     channelId: videoChannel.id,
58     duration: parseInt(duration, 10),
59     createdAt: new Date(videoObject.published),
60     // FIXME: updatedAt does not seems to be considered by Sequelize
61     updatedAt: new Date(videoObject.updated),
62     views: videoObject.views,
63     likes: 0,
64     dislikes: 0,
65     remote: true,
66     privacy
67   }
68 }
69
70 function videoFileActivityUrlToDBAttributes (videoCreated: VideoModel, videoObject: VideoTorrentObject) {
71   const mimeTypes = Object.keys(VIDEO_MIMETYPE_EXT)
72   const fileUrls = videoObject.url.filter(u => {
73     return mimeTypes.indexOf(u.mimeType) !== -1 && u.mimeType.startsWith('video/')
74   })
75
76   if (fileUrls.length === 0) {
77     throw new Error('Cannot find video files for ' + videoCreated.url)
78   }
79
80   const attributes = []
81   for (const fileUrl of fileUrls) {
82     // Fetch associated magnet uri
83     const magnet = videoObject.url.find(u => {
84       return u.mimeType === 'application/x-bittorrent;x-scheme-handler/magnet' && u.width === fileUrl.width
85     })
86
87     if (!magnet) throw new Error('Cannot find associated magnet uri for file ' + fileUrl.url)
88
89     const parsed = magnetUtil.decode(magnet.url)
90     if (!parsed || isVideoFileInfoHashValid(parsed.infoHash) === false) throw new Error('Cannot parse magnet URI ' + magnet.url)
91
92     const attribute = {
93       extname: VIDEO_MIMETYPE_EXT[fileUrl.mimeType],
94       infoHash: parsed.infoHash,
95       resolution: fileUrl.width,
96       size: fileUrl.size,
97       videoId: videoCreated.id
98     }
99     attributes.push(attribute)
100   }
101
102   return attributes
103 }
104
105 async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) {
106   let originCommentId: number = null
107   let inReplyToCommentId: number = null
108
109   // If this is not a reply to the video (thread), create or get the parent comment
110   if (video.url !== comment.inReplyTo) {
111     const [ parent ] = await addVideoComment(video, comment.inReplyTo)
112     if (!parent) {
113       logger.warn('Cannot fetch or get parent comment %s of comment %s.', comment.inReplyTo, comment.id)
114       return undefined
115     }
116
117     originCommentId = parent.originCommentId || parent.id
118     inReplyToCommentId = parent.id
119   }
120
121   return {
122     url: comment.url,
123     text: comment.content,
124     videoId: video.id,
125     accountId: actor.Account.id,
126     inReplyToCommentId,
127     originCommentId,
128     createdAt: new Date(comment.published),
129     updatedAt: new Date(comment.updated)
130   }
131 }
132
133 async function addVideoShares (instance: VideoModel, shareUrls: string[]) {
134   for (const shareUrl of shareUrls) {
135     // Fetch url
136     const { body } = await doRequest({
137       uri: shareUrl,
138       json: true,
139       activityPub: true
140     })
141     const actorUrl = body.actor
142     if (!actorUrl) continue
143
144     const actor = await getOrCreateActorAndServerAndModel(actorUrl)
145
146     const entry = {
147       actorId: actor.id,
148       videoId: instance.id
149     }
150
151     await VideoShareModel.findOrCreate({
152       where: entry,
153       defaults: entry
154     })
155   }
156 }
157
158 async function addVideoComments (instance: VideoModel, commentUrls: string[]) {
159   for (const commentUrl of commentUrls) {
160     await addVideoComment(instance, commentUrl)
161   }
162 }
163
164 async function addVideoComment (instance: VideoModel, commentUrl: string) {
165   // Fetch url
166   const { body } = await doRequest({
167     uri: commentUrl,
168     json: true,
169     activityPub: true
170   })
171
172   const actorUrl = body.attributedTo
173   if (!actorUrl) return []
174
175   const actor = await getOrCreateActorAndServerAndModel(actorUrl)
176   const entry = await videoCommentActivityObjectToDBAttributes(instance, actor, body)
177   if (!entry) return []
178
179   return VideoCommentModel.findOrCreate({
180     where: {
181       url: body.id
182     },
183     defaults: entry
184   })
185 }
186
187 // ---------------------------------------------------------------------------
188
189 export {
190   videoFileActivityUrlToDBAttributes,
191   videoActivityObjectToDBAttributes,
192   addVideoShares,
193   addVideoComments
194 }