X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Flib%2Factivitypub%2Fvideo-comments.ts;h=902d877c466dc0b61d5382ed8ddbe083b01f12bf;hb=10c8b0b743ae5aa7e89c7fe5745821278b71d52c;hp=c3fc6b4621926179d24621a38e8138ab1811051b;hpb=a41b9da1a9ce49df82ea10c82de4c2fbc6d1b189;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index c3fc6b462..902d877c4 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts @@ -1,89 +1,63 @@ -import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' import { logger } from '../../helpers/logger' import { doRequest } from '../../helpers/requests' import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' -import { ActorModel } from '../../models/activitypub/actor' -import { VideoModel } from '../../models/video/video' import { VideoCommentModel } from '../../models/video/video-comment' import { getOrCreateActorAndServerAndModel } from './actor' import { getOrCreateVideoAndAccountAndChannel } from './videos' import * as Bluebird from 'bluebird' import { checkUrlsSameHost } from '../../helpers/activitypub' +import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video' -async function videoCommentActivityObjectToDBAttributes (video: VideoModel, actor: ActorModel, comment: VideoCommentObject) { - let originCommentId: number = null - let inReplyToCommentId: number = null - - // If this is not a reply to the video (thread), create or get the parent comment - if (video.url !== comment.inReplyTo) { - const { comment: parent } = await addVideoComment(video, comment.inReplyTo) - if (!parent) { - logger.warn('Cannot fetch or get parent comment %s of comment %s.', comment.inReplyTo, comment.id) - return undefined - } - - originCommentId = parent.originCommentId || parent.id - inReplyToCommentId = parent.id - } - - return { - url: comment.id, - text: comment.content, - videoId: video.id, - accountId: actor.Account.id, - inReplyToCommentId, - originCommentId, - createdAt: new Date(comment.published) - } +type ResolveThreadParams = { + url: string + comments?: MCommentOwner[] + isVideo?: boolean + commentCreated?: boolean } +type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> -async function addVideoComments (commentUrls: string[], instance: VideoModel) { +async function addVideoComments (commentUrls: string[]) { return Bluebird.map(commentUrls, commentUrl => { - return addVideoComment(instance, commentUrl) + return resolveThread({ url: commentUrl, isVideo: false }) }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) } -async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { - logger.info('Fetching remote video comment %s.', commentUrl) - - const { body } = await doRequest({ - uri: commentUrl, - json: true, - activityPub: true - }) +async function resolveThread (params: ResolveThreadParams): ResolveThreadResult { + const { url, isVideo } = params + if (params.commentCreated === undefined) params.commentCreated = false + if (params.comments === undefined) params.comments = [] - if (sanitizeAndCheckVideoCommentObject(body) === false) { - logger.debug('Remote video comment JSON is not valid.', { body }) - return { created: false } + // If it is not a video, or if we don't know if it's a video + if (isVideo === false || isVideo === undefined) { + const result = await resolveCommentFromDB(params) + if (result) return result } - const actorUrl = body.attributedTo - if (!actorUrl) return { created: false } - - if (checkUrlsSameHost(commentUrl, actorUrl) !== true) { - throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${commentUrl}`) + try { + // If it is a video, or if we don't know if it's a video + if (isVideo === true || isVideo === undefined) { + // Keep await so we catch the exception + return await tryResolveThreadFromVideo(params) + } + } catch (err) { + logger.debug('Cannot get or create account and video and channel for reply %s, fetch comment', url, { err }) } - if (checkUrlsSameHost(body.id, commentUrl) !== true) { - throw new Error(`Comment url ${commentUrl} host is different from the AP object id ${body.id}`) - } + return resolveParentComment(params) +} - const actor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') - const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) - if (!entry) return { created: false } +export { + addVideoComments, + resolveThread +} - const [ comment, created ] = await VideoCommentModel.upsert(entry, { returning: true }) - comment.Account = actor.Account - comment.Video = videoInstance +// --------------------------------------------------------------------------- - return { comment, created } -} +async function resolveCommentFromDB (params: ResolveThreadParams) { + const { url, comments, commentCreated } = params -type ResolveThreadResult = Promise<{ video: VideoModel, parents: VideoCommentModel[] }> -async function resolveThread (url: string, comments: VideoCommentModel[] = []): ResolveThreadResult { - // Already have this comment? - const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideo(url) + const commentFromDatabase = await VideoCommentModel.loadByUrlAndPopulateReplyAndVideoUrlAndAccount(url) if (commentFromDatabase) { let parentComments = comments.concat([ commentFromDatabase ]) @@ -94,79 +68,105 @@ async function resolveThread (url: string, comments: VideoCommentModel[] = []): parentComments = parentComments.concat(data) } - return resolveThread(commentFromDatabase.Video.url, parentComments) + return resolveThread({ + url: commentFromDatabase.Video.url, + comments: parentComments, + isVideo: true, + commentCreated + }) } - try { - // Maybe it's a reply to a video? - // If yes, it's done: we resolved all the thread - const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url }) - - if (comments.length !== 0) { - const firstReply = comments[ comments.length - 1 ] - firstReply.inReplyToCommentId = null - firstReply.originCommentId = null - firstReply.videoId = video.id - comments[comments.length - 1] = await firstReply.save() - - for (let i = comments.length - 2; i >= 0; i--) { - const comment = comments[ i ] - comment.originCommentId = firstReply.id - comment.inReplyToCommentId = comments[ i + 1 ].id - comment.videoId = video.id - - comments[i] = await comment.save() - } - } + return undefined +} - return { video, parents: comments } - } catch (err) { - logger.debug('Cannot get or create account and video and channel for reply %s, fetch comment', url, { err }) +async function tryResolveThreadFromVideo (params: ResolveThreadParams) { + const { url, comments, commentCreated } = params - if (comments.length > ACTIVITY_PUB.MAX_RECURSION_COMMENTS) { - throw new Error('Recursion limit reached when resolving a thread') - } + // Maybe it's a reply to a video? + // If yes, it's done: we resolved all the thread + const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } + const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) - const { body } = await doRequest({ - uri: url, - json: true, - activityPub: true - }) + if (video.isOwned() && !video.hasPrivacyForFederation()) { + throw new Error('Cannot resolve thread of video with privacy that is not compatible with federation') + } - if (sanitizeAndCheckVideoCommentObject(body) === false) { - throw new Error('Remote video comment JSON is not valid :' + JSON.stringify(body)) + let resultComment: MCommentOwnerVideo + if (comments.length !== 0) { + const firstReply = comments[comments.length - 1] as MCommentOwnerVideo + firstReply.inReplyToCommentId = null + firstReply.originCommentId = null + firstReply.videoId = video.id + firstReply.changed('updatedAt', true) + firstReply.Video = video + + comments[comments.length - 1] = await firstReply.save() + + for (let i = comments.length - 2; i >= 0; i--) { + const comment = comments[i] as MCommentOwnerVideo + comment.originCommentId = firstReply.id + comment.inReplyToCommentId = comments[i + 1].id + comment.videoId = video.id + comment.changed('updatedAt', true) + comment.Video = video + + comments[i] = await comment.save() } - const actorUrl = body.attributedTo - if (!actorUrl) throw new Error('Miss attributed to in comment') + resultComment = comments[0] as MCommentOwnerVideo + } - if (checkUrlsSameHost(url, actorUrl) !== true) { - throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${url}`) - } + return { video, comment: resultComment, commentCreated } +} - if (checkUrlsSameHost(body.id, url) !== true) { - throw new Error(`Comment url ${url} host is different from the AP object id ${body.id}`) - } +async function resolveParentComment (params: ResolveThreadParams) { + const { url, comments } = params - const actor = await getOrCreateActorAndServerAndModel(actorUrl) - const comment = new VideoCommentModel({ - url: body.id, - text: body.content, - videoId: null, - accountId: actor.Account.id, - inReplyToCommentId: null, - originCommentId: null, - createdAt: new Date(body.published), - updatedAt: new Date(body.updated) - }) + if (comments.length > ACTIVITY_PUB.MAX_RECURSION_COMMENTS) { + throw new Error('Recursion limit reached when resolving a thread') + } - return resolveThread(body.inReplyTo, comments.concat([ comment ])) + const { body } = await doRequest({ + uri: url, + json: true, + activityPub: true + }) + + if (sanitizeAndCheckVideoCommentObject(body) === false) { + throw new Error('Remote video comment JSON is not valid:' + JSON.stringify(body)) } -} -export { - videoCommentActivityObjectToDBAttributes, - addVideoComments, - addVideoComment, - resolveThread + const actorUrl = body.attributedTo + if (!actorUrl && body.type !== 'Tombstone') throw new Error('Miss attributed to in comment') + + if (actorUrl && checkUrlsSameHost(url, actorUrl) !== true) { + throw new Error(`Actor url ${actorUrl} has not the same host than the comment url ${url}`) + } + + if (checkUrlsSameHost(body.id, url) !== true) { + throw new Error(`Comment url ${url} host is different from the AP object id ${body.id}`) + } + + const actor = actorUrl + ? await getOrCreateActorAndServerAndModel(actorUrl, 'all') + : null + + const comment = new VideoCommentModel({ + url: body.id, + text: body.content ? body.content : '', + videoId: null, + accountId: actor ? actor.Account.id : null, + inReplyToCommentId: null, + originCommentId: null, + createdAt: new Date(body.published), + updatedAt: new Date(body.updated), + deletedAt: body.deleted ? new Date(body.deleted) : null + }) as MCommentOwner + comment.Account = actor ? actor.Account : null + + return resolveThread({ + url: body.inReplyTo, + comments: comments.concat([ comment ]), + commentCreated: true + }) }