2021-02-26 09:26:27 -06:00
|
|
|
import * as Bluebird from 'bluebird'
|
|
|
|
import * as Bull from 'bull'
|
|
|
|
import { checkUrlsSameHost } from '@server/helpers/activitypub'
|
2021-03-05 06:49:58 -06:00
|
|
|
import {
|
|
|
|
isAnnounceActivityValid,
|
|
|
|
isDislikeActivityValid,
|
|
|
|
isLikeActivityValid
|
|
|
|
} from '@server/helpers/custom-validators/activitypub/activity'
|
2021-02-26 09:26:27 -06:00
|
|
|
import { sanitizeAndCheckVideoCommentObject } from '@server/helpers/custom-validators/activitypub/video-comments'
|
2021-03-09 07:01:44 -06:00
|
|
|
import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests'
|
2021-02-26 09:26:27 -06:00
|
|
|
import { AP_CLEANER_CONCURRENCY } from '@server/initializers/constants'
|
|
|
|
import { VideoModel } from '@server/models/video/video'
|
|
|
|
import { VideoCommentModel } from '@server/models/video/video-comment'
|
|
|
|
import { VideoShareModel } from '@server/models/video/video-share'
|
|
|
|
import { HttpStatusCode } from '@shared/core-utils'
|
|
|
|
import { logger } from '../../../helpers/logger'
|
|
|
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
|
|
|
|
|
|
|
|
// Job to clean remote interactions off local videos
|
|
|
|
|
|
|
|
async function processActivityPubCleaner (_job: Bull.Job) {
|
|
|
|
logger.info('Processing ActivityPub cleaner.')
|
|
|
|
|
|
|
|
{
|
|
|
|
const rateUrls = await AccountVideoRateModel.listRemoteRateUrlsOfLocalVideos()
|
|
|
|
const { bodyValidator, deleter, updater } = rateOptionsFactory()
|
|
|
|
|
|
|
|
await Bluebird.map(rateUrls, async rateUrl => {
|
|
|
|
try {
|
|
|
|
const result = await updateObjectIfNeeded(rateUrl, bodyValidator, updater, deleter)
|
|
|
|
|
|
|
|
if (result?.status === 'deleted') {
|
|
|
|
const { videoId, type } = result.data
|
|
|
|
|
|
|
|
await VideoModel.updateRatesOf(videoId, type, undefined)
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
logger.warn('Cannot update/delete remote AP rate %s.', rateUrl, { err })
|
|
|
|
}
|
|
|
|
}, { concurrency: AP_CLEANER_CONCURRENCY })
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const shareUrls = await VideoShareModel.listRemoteShareUrlsOfLocalVideos()
|
|
|
|
const { bodyValidator, deleter, updater } = shareOptionsFactory()
|
|
|
|
|
|
|
|
await Bluebird.map(shareUrls, async shareUrl => {
|
|
|
|
try {
|
|
|
|
await updateObjectIfNeeded(shareUrl, bodyValidator, updater, deleter)
|
|
|
|
} catch (err) {
|
|
|
|
logger.warn('Cannot update/delete remote AP share %s.', shareUrl, { err })
|
|
|
|
}
|
|
|
|
}, { concurrency: AP_CLEANER_CONCURRENCY })
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
const commentUrls = await VideoCommentModel.listRemoteCommentUrlsOfLocalVideos()
|
|
|
|
const { bodyValidator, deleter, updater } = commentOptionsFactory()
|
|
|
|
|
|
|
|
await Bluebird.map(commentUrls, async commentUrl => {
|
|
|
|
try {
|
|
|
|
await updateObjectIfNeeded(commentUrl, bodyValidator, updater, deleter)
|
|
|
|
} catch (err) {
|
|
|
|
logger.warn('Cannot update/delete remote AP comment %s.', commentUrl, { err })
|
|
|
|
}
|
|
|
|
}, { concurrency: AP_CLEANER_CONCURRENCY })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
export {
|
|
|
|
processActivityPubCleaner
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
async function updateObjectIfNeeded <T> (
|
|
|
|
url: string,
|
|
|
|
bodyValidator: (body: any) => boolean,
|
|
|
|
updater: (url: string, newUrl: string) => Promise<T>,
|
|
|
|
deleter: (url: string) => Promise<T>
|
|
|
|
): Promise<{ data: T, status: 'deleted' | 'updated' } | null> {
|
2021-03-09 07:01:44 -06:00
|
|
|
const on404OrTombstone = async () => {
|
2021-02-26 09:26:27 -06:00
|
|
|
logger.info('Removing remote AP object %s.', url)
|
|
|
|
const data = await deleter(url)
|
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
return { status: 'deleted' as 'deleted', data }
|
2021-02-26 09:26:27 -06:00
|
|
|
}
|
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
try {
|
|
|
|
const { body } = await doJSONRequest<any>(url, { activityPub: true })
|
2021-02-26 09:26:27 -06:00
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
// If not same id, check same host and update
|
|
|
|
if (!body || !body.id || !bodyValidator(body)) throw new Error(`Body or body id of ${url} is invalid`)
|
2021-02-26 09:26:27 -06:00
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
if (body.type === 'Tombstone') {
|
|
|
|
return on404OrTombstone()
|
|
|
|
}
|
2021-02-26 09:26:27 -06:00
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
const newUrl = body.id
|
|
|
|
if (newUrl !== url) {
|
|
|
|
if (checkUrlsSameHost(newUrl, url) !== true) {
|
|
|
|
throw new Error(`New url ${newUrl} has not the same host than old url ${url}`)
|
|
|
|
}
|
|
|
|
|
|
|
|
logger.info('Updating remote AP object %s.', url)
|
|
|
|
const data = await updater(url, newUrl)
|
|
|
|
|
|
|
|
return { status: 'updated', data }
|
2021-02-26 09:26:27 -06:00
|
|
|
}
|
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
return null
|
|
|
|
} catch (err) {
|
|
|
|
// Does not exist anymore, remove entry
|
|
|
|
if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
|
|
|
|
return on404OrTombstone()
|
|
|
|
}
|
2021-02-26 09:26:27 -06:00
|
|
|
|
2021-03-09 07:01:44 -06:00
|
|
|
throw err
|
2021-02-26 09:26:27 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function rateOptionsFactory () {
|
|
|
|
return {
|
|
|
|
bodyValidator: (body: any) => isLikeActivityValid(body) || isDislikeActivityValid(body),
|
|
|
|
|
|
|
|
updater: async (url: string, newUrl: string) => {
|
|
|
|
const rate = await AccountVideoRateModel.loadByUrl(url, undefined)
|
|
|
|
rate.url = newUrl
|
|
|
|
|
|
|
|
const videoId = rate.videoId
|
|
|
|
const type = rate.type
|
|
|
|
|
|
|
|
await rate.save()
|
|
|
|
|
|
|
|
return { videoId, type }
|
|
|
|
},
|
|
|
|
|
|
|
|
deleter: async (url) => {
|
|
|
|
const rate = await AccountVideoRateModel.loadByUrl(url, undefined)
|
|
|
|
|
|
|
|
const videoId = rate.videoId
|
|
|
|
const type = rate.type
|
|
|
|
|
|
|
|
await rate.destroy()
|
|
|
|
|
|
|
|
return { videoId, type }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function shareOptionsFactory () {
|
|
|
|
return {
|
2021-03-05 06:49:58 -06:00
|
|
|
bodyValidator: (body: any) => isAnnounceActivityValid(body),
|
2021-02-26 09:26:27 -06:00
|
|
|
|
|
|
|
updater: async (url: string, newUrl: string) => {
|
|
|
|
const share = await VideoShareModel.loadByUrl(url, undefined)
|
|
|
|
share.url = newUrl
|
|
|
|
|
|
|
|
await share.save()
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
},
|
|
|
|
|
|
|
|
deleter: async (url) => {
|
|
|
|
const share = await VideoShareModel.loadByUrl(url, undefined)
|
|
|
|
|
|
|
|
await share.destroy()
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function commentOptionsFactory () {
|
|
|
|
return {
|
|
|
|
bodyValidator: (body: any) => sanitizeAndCheckVideoCommentObject(body),
|
|
|
|
|
|
|
|
updater: async (url: string, newUrl: string) => {
|
|
|
|
const comment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(url)
|
|
|
|
comment.url = newUrl
|
|
|
|
|
|
|
|
await comment.save()
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
},
|
|
|
|
|
|
|
|
deleter: async (url) => {
|
|
|
|
const comment = await VideoCommentModel.loadByUrlAndPopulateAccountAndVideo(url)
|
|
|
|
|
|
|
|
await comment.destroy()
|
|
|
|
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|