Fetch remote AP objects if only id is specified

This commit is contained in:
Chocobozzz 2023-06-05 15:51:16 +02:00
parent f987425bd1
commit cefe22cf7c
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
19 changed files with 760 additions and 661 deletions

View File

@ -4,6 +4,7 @@ import { activityPubCollectionPagination } from '@server/lib/activitypub/collect
import { activityPubContextify } from '@server/lib/activitypub/context' import { activityPubContextify } from '@server/lib/activitypub/context'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models' import { MAccountId, MActorId, MChannelId, MVideoId } from '@server/types/models'
import { VideoCommentObject } from '@shared/models'
import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants'
@ -353,7 +354,7 @@ async function videoCommentController (req: express.Request, res: express.Respon
videoCommentObject = audiencify(videoCommentObject, audience) videoCommentObject = audiencify(videoCommentObject, audience)
if (req.path.endsWith('/activity')) { if (req.path.endsWith('/activity')) {
const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject, audience) const data = buildCreateActivity(videoComment.url, videoComment.Account.Actor, videoCommentObject as VideoCommentObject, audience)
return activityPubResponse(activityPubContextify(data, 'Comment'), res) return activityPubResponse(activityPubContextify(data, 'Comment'), res)
} }
} }

View File

@ -63,6 +63,7 @@ async function buildActivities (actor: MActorLight, start: number, count: number
activities.push(announceActivity) activities.push(announceActivity)
} else { } else {
// FIXME: only use the video URL to reduce load. Breaks compat with PeerTube < 6.0.0
const videoObject = await video.toActivityPubObject() const videoObject = await video.toActivityPubObject()
const createActivity = buildCreateActivity(video.url, byActor, videoObject, createActivityAudience) const createActivity = buildCreateActivity(video.url, byActor, videoObject, createActivityAudience)

View File

@ -1,4 +1,5 @@
import { ActivityType } from '@shared/models' import { doJSONRequest } from '@server/helpers/requests'
import { APObjectId, ActivityObject, ActivityPubActor, ActivityType } from '@shared/models'
function getAPId (object: string | { id: string }) { function getAPId (object: string | { id: string }) {
if (typeof object === 'string') return object if (typeof object === 'string') return object
@ -32,8 +33,19 @@ function buildAvailableActivities (): ActivityType[] {
] ]
} }
async function fetchAPObject <T extends (ActivityObject | ActivityPubActor)> (object: APObjectId) {
if (typeof object === 'string') {
const { body } = await doJSONRequest<Exclude<T, string>>(object, { activityPub: true })
return body
}
return object as Exclude<T, string>
}
export { export {
getAPId, getAPId,
fetchAPObject,
getActivityStreamDuration, getActivityStreamDuration,
buildAvailableActivities, buildAvailableActivities,
getDurationFromActivityStream getDurationFromActivityStream

View File

@ -1,12 +1,12 @@
import { VideoPlaylistModel } from '@server/models/video/video-playlist' import { VideoPlaylistModel } from '@server/models/video/video-playlist'
import { MVideoPlaylistFullSummary } from '@server/types/models' import { MVideoPlaylistFullSummary } from '@server/types/models'
import { APObject } from '@shared/models' import { APObjectId } from '@shared/models'
import { getAPId } from '../activity' import { getAPId } from '../activity'
import { createOrUpdateVideoPlaylist } from './create-update' import { createOrUpdateVideoPlaylist } from './create-update'
import { scheduleRefreshIfNeeded } from './refresh' import { scheduleRefreshIfNeeded } from './refresh'
import { fetchRemoteVideoPlaylist } from './shared' import { fetchRemoteVideoPlaylist } from './shared'
async function getOrCreateAPVideoPlaylist (playlistObjectArg: APObject): Promise<MVideoPlaylistFullSummary> { async function getOrCreateAPVideoPlaylist (playlistObjectArg: APObjectId): Promise<MVideoPlaylistFullSummary> {
const playlistUrl = getAPId(playlistObjectArg) const playlistUrl = getAPId(playlistObjectArg)
const playlistFromDatabase = await VideoPlaylistModel.loadByUrlWithAccountAndChannelSummary(playlistUrl) const playlistFromDatabase = await VideoPlaylistModel.loadByUrlWithAccountAndChannelSummary(playlistUrl)

View File

@ -1,13 +1,24 @@
import { isBlockedByServerOrAccount } from '@server/lib/blocklist' import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
import { isRedundancyAccepted } from '@server/lib/redundancy' import { isRedundancyAccepted } from '@server/lib/redundancy'
import { VideoModel } from '@server/models/video/video' import { VideoModel } from '@server/models/video/video'
import { ActivityCreate, CacheFileObject, PlaylistObject, VideoCommentObject, VideoObject, WatchActionObject } from '@shared/models' import {
AbuseObject,
ActivityCreate,
ActivityCreateObject,
ActivityObject,
CacheFileObject,
PlaylistObject,
VideoCommentObject,
VideoObject,
WatchActionObject
} from '@shared/models'
import { retryTransactionWrapper } from '../../../helpers/database-utils' import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers/database' import { sequelizeTypescript } from '../../../initializers/database'
import { APProcessorOptions } from '../../../types/activitypub-processor.model' import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models' import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../types/models'
import { Notifier } from '../../notifier' import { Notifier } from '../../notifier'
import { fetchAPObject } from '../activity'
import { createOrUpdateCacheFile } from '../cache-file' import { createOrUpdateCacheFile } from '../cache-file'
import { createOrUpdateLocalVideoViewer } from '../local-video-viewer' import { createOrUpdateLocalVideoViewer } from '../local-video-viewer'
import { createOrUpdateVideoPlaylist } from '../playlists' import { createOrUpdateVideoPlaylist } from '../playlists'
@ -15,35 +26,35 @@ import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
import { resolveThread } from '../video-comments' import { resolveThread } from '../video-comments'
import { getOrCreateAPVideo } from '../videos' import { getOrCreateAPVideo } from '../videos'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
const { activity, byActor } = options const { activity, byActor } = options
// Only notify if it is not from a fetcher job // Only notify if it is not from a fetcher job
const notify = options.fromFetch !== true const notify = options.fromFetch !== true
const activityObject = activity.object const activityObject = await fetchAPObject<Exclude<ActivityObject, AbuseObject>>(activity.object)
const activityType = activityObject.type const activityType = activityObject.type
if (activityType === 'Video') { if (activityType === 'Video') {
return processCreateVideo(activity, notify) return processCreateVideo(activityObject, notify)
} }
if (activityType === 'Note') { if (activityType === 'Note') {
// Comments will be fetched from videos // Comments will be fetched from videos
if (options.fromFetch) return if (options.fromFetch) return
return retryTransactionWrapper(processCreateVideoComment, activity, byActor, notify) return retryTransactionWrapper(processCreateVideoComment, activity, activityObject, byActor, notify)
} }
if (activityType === 'WatchAction') { if (activityType === 'WatchAction') {
return retryTransactionWrapper(processCreateWatchAction, activity) return retryTransactionWrapper(processCreateWatchAction, activityObject)
} }
if (activityType === 'CacheFile') { if (activityType === 'CacheFile') {
return retryTransactionWrapper(processCreateCacheFile, activity, byActor) return retryTransactionWrapper(processCreateCacheFile, activity, activityObject, byActor)
} }
if (activityType === 'Playlist') { if (activityType === 'Playlist') {
return retryTransactionWrapper(processCreatePlaylist, activity, byActor) return retryTransactionWrapper(processCreatePlaylist, activity, activityObject, byActor)
} }
logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id })
@ -58,9 +69,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processCreateVideo (activity: ActivityCreate, notify: boolean) { async function processCreateVideo (videoToCreateData: VideoObject, notify: boolean) {
const videoToCreateData = activity.object as VideoObject
const syncParam = { rates: false, shares: false, comments: false, thumbnail: true, refreshVideo: false } const syncParam = { rates: false, shares: false, comments: false, thumbnail: true, refreshVideo: false }
const { video, created } = await getOrCreateAPVideo({ videoObject: videoToCreateData, syncParam }) const { video, created } = await getOrCreateAPVideo({ videoObject: videoToCreateData, syncParam })
@ -69,11 +78,13 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) {
return video return video
} }
async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) { async function processCreateCacheFile (
activity: ActivityCreate<CacheFileObject | string>,
cacheFile: CacheFileObject,
byActor: MActorSignature
) {
if (await isRedundancyAccepted(activity, byActor) !== true) return if (await isRedundancyAccepted(activity, byActor) !== true) return
const cacheFile = activity.object as CacheFileObject
const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object }) const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
await sequelizeTypescript.transaction(async t => { await sequelizeTypescript.transaction(async t => {
@ -87,9 +98,7 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: MActor
} }
} }
async function processCreateWatchAction (activity: ActivityCreate) { async function processCreateWatchAction (watchAction: WatchActionObject) {
const watchAction = activity.object as WatchActionObject
if (watchAction.actionStatus !== 'CompletedActionStatus') return if (watchAction.actionStatus !== 'CompletedActionStatus') return
const video = await VideoModel.loadByUrl(watchAction.object) const video = await VideoModel.loadByUrl(watchAction.object)
@ -100,8 +109,12 @@ async function processCreateWatchAction (activity: ActivityCreate) {
}) })
} }
async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) { async function processCreateVideoComment (
const commentObject = activity.object as VideoCommentObject activity: ActivityCreate<VideoCommentObject | string>,
commentObject: VideoCommentObject,
byActor: MActorSignature,
notify: boolean
) {
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url)
@ -144,8 +157,11 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: MAc
if (created && notify) Notifier.Instance.notifyOnNewComment(comment) if (created && notify) Notifier.Instance.notifyOnNewComment(comment)
} }
async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) { async function processCreatePlaylist (
const playlistObject = activity.object as PlaylistObject activity: ActivityCreate<PlaylistObject | string>,
playlistObject: PlaylistObject,
byActor: MActorSignature
) {
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url)

View File

@ -1,5 +1,5 @@
import { VideoModel } from '@server/models/video/video' import { VideoModel } from '@server/models/video/video'
import { ActivityCreate, ActivityDislike, DislikeObject } from '@shared/models' import { ActivityDislike } from '@shared/models'
import { retryTransactionWrapper } from '../../../helpers/database-utils' import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { sequelizeTypescript } from '../../../initializers/database' import { sequelizeTypescript } from '../../../initializers/database'
import { AccountVideoRateModel } from '../../../models/account/account-video-rate' import { AccountVideoRateModel } from '../../../models/account/account-video-rate'
@ -7,7 +7,7 @@ import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorSignature } from '../../../types/models' import { MActorSignature } from '../../../types/models'
import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos'
async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
const { activity, byActor } = options const { activity, byActor } = options
return retryTransactionWrapper(processDislike, activity, byActor) return retryTransactionWrapper(processDislike, activity, byActor)
} }
@ -20,11 +20,8 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) { async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
const dislikeObject = activity.type === 'Dislike' const dislikeObject = activity.object
? activity.object
: (activity.object as DislikeObject).object
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)

View File

@ -3,7 +3,7 @@ import { AccountModel } from '@server/models/account/account'
import { VideoModel } from '@server/models/video/video' import { VideoModel } from '@server/models/video/video'
import { VideoCommentModel } from '@server/models/video/video-comment' import { VideoCommentModel } from '@server/models/video/video-comment'
import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse' import { abusePredefinedReasonsMap } from '@shared/core-utils/abuse'
import { AbuseObject, AbuseState, ActivityCreate, ActivityFlag } from '@shared/models' import { AbuseState, ActivityFlag } from '@shared/models'
import { retryTransactionWrapper } from '../../../helpers/database-utils' import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers/database' import { sequelizeTypescript } from '../../../initializers/database'
@ -11,7 +11,7 @@ import { getAPId } from '../../../lib/activitypub/activity'
import { APProcessorOptions } from '../../../types/activitypub-processor.model' import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MAccountDefault, MActorSignature, MCommentOwnerVideo } from '../../../types/models' import { MAccountDefault, MActorSignature, MCommentOwnerVideo } from '../../../types/models'
async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { async function processFlagActivity (options: APProcessorOptions<ActivityFlag>) {
const { activity, byActor } = options const { activity, byActor } = options
return retryTransactionWrapper(processCreateAbuse, activity, byActor) return retryTransactionWrapper(processCreateAbuse, activity, byActor)
@ -25,9 +25,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processCreateAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { async function processCreateAbuse (flag: ActivityFlag, byActor: MActorSignature) {
const flag = activity.type === 'Flag' ? activity : (activity.object as AbuseObject)
const account = byActor.Account const account = byActor.Account
if (!account) throw new Error('Cannot create abuse with the non account actor ' + byActor.url) if (!account) throw new Error('Cannot create abuse with the non account actor ' + byActor.url)

View File

@ -1,6 +1,14 @@
import { VideoModel } from '@server/models/video/video' import { VideoModel } from '@server/models/video/video'
import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo, CacheFileObject } from '../../../../shared/models/activitypub' import {
import { DislikeObject } from '../../../../shared/models/activitypub/objects' ActivityAnnounce,
ActivityCreate,
ActivityDislike,
ActivityFollow,
ActivityLike,
ActivityUndo,
ActivityUndoObject,
CacheFileObject
} from '../../../../shared/models/activitypub'
import { retryTransactionWrapper } from '../../../helpers/database-utils' import { retryTransactionWrapper } from '../../../helpers/database-utils'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { sequelizeTypescript } from '../../../initializers/database' import { sequelizeTypescript } from '../../../initializers/database'
@ -11,10 +19,11 @@ import { VideoRedundancyModel } from '../../../models/redundancy/video-redundanc
import { VideoShareModel } from '../../../models/video/video-share' import { VideoShareModel } from '../../../models/video/video-share'
import { APProcessorOptions } from '../../../types/activitypub-processor.model' import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorSignature } from '../../../types/models' import { MActorSignature } from '../../../types/models'
import { fetchAPObject } from '../activity'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils' import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos' import { federateVideoIfNeeded, getOrCreateAPVideo } from '../videos'
async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { async function processUndoActivity (options: APProcessorOptions<ActivityUndo<ActivityUndoObject>>) {
const { activity, byActor } = options const { activity, byActor } = options
const activityToUndo = activity.object const activityToUndo = activity.object
@ -23,8 +32,10 @@ async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
} }
if (activityToUndo.type === 'Create') { if (activityToUndo.type === 'Create') {
if (activityToUndo.object.type === 'CacheFile') { const objectToUndo = await fetchAPObject<CacheFileObject>(activityToUndo.object)
return retryTransactionWrapper(processUndoCacheFile, byActor, activity)
if (objectToUndo.type === 'CacheFile') {
return retryTransactionWrapper(processUndoCacheFile, byActor, activity, objectToUndo)
} }
} }
@ -53,8 +64,8 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) { async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo<ActivityLike>) {
const likeActivity = activity.object as ActivityLike const likeActivity = activity.object
const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: likeActivity.object }) const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: likeActivity.object })
// We don't care about likes of remote videos // We don't care about likes of remote videos
@ -78,12 +89,10 @@ async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo
}) })
} }
async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) { async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo<ActivityDislike>) {
const dislike = activity.object.type === 'Dislike' const dislikeActivity = activity.object
? activity.object
: activity.object.object as DislikeObject
const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: dislike.object }) const { video: onlyVideo } = await getOrCreateAPVideo({ videoObject: dislikeActivity.object })
// We don't care about likes of remote videos // We don't care about likes of remote videos
if (!onlyVideo.isOwned()) return if (!onlyVideo.isOwned()) return
@ -91,7 +100,7 @@ async function processUndoDislike (byActor: MActorSignature, activity: ActivityU
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url) if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
const video = await VideoModel.loadFull(onlyVideo.id, t) const video = await VideoModel.loadFull(onlyVideo.id, t)
const rate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byActor.Account.id, video.id, dislike.id, t) const rate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byActor.Account.id, video.id, dislikeActivity.id, t)
if (!rate || rate.type !== 'dislike') { if (!rate || rate.type !== 'dislike') {
logger.warn(`Unknown dislike by account %d for video %d.`, byActor.Account.id, video.id) logger.warn(`Unknown dislike by account %d for video %d.`, byActor.Account.id, video.id)
return return
@ -107,9 +116,11 @@ async function processUndoDislike (byActor: MActorSignature, activity: ActivityU
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) { async function processUndoCacheFile (
const cacheFileObject = activity.object.object as CacheFileObject byActor: MActorSignature,
activity: ActivityUndo<ActivityCreate<CacheFileObject>>,
cacheFileObject: CacheFileObject
) {
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object }) const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
return sequelizeTypescript.transaction(async t => { return sequelizeTypescript.transaction(async t => {

View File

@ -1,5 +1,5 @@
import { isRedundancyAccepted } from '@server/lib/redundancy' import { isRedundancyAccepted } from '@server/lib/redundancy'
import { ActivityUpdate, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub' import { ActivityUpdate, ActivityUpdateObject, CacheFileObject, VideoObject } from '../../../../shared/models/activitypub'
import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor'
import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object'
import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file'
@ -10,16 +10,18 @@ import { sequelizeTypescript } from '../../../initializers/database'
import { ActorModel } from '../../../models/actor/actor' import { ActorModel } from '../../../models/actor/actor'
import { APProcessorOptions } from '../../../types/activitypub-processor.model' import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorFull, MActorSignature } from '../../../types/models' import { MActorFull, MActorSignature } from '../../../types/models'
import { fetchAPObject } from '../activity'
import { APActorUpdater } from '../actors/updater' import { APActorUpdater } from '../actors/updater'
import { createOrUpdateCacheFile } from '../cache-file' import { createOrUpdateCacheFile } from '../cache-file'
import { createOrUpdateVideoPlaylist } from '../playlists' import { createOrUpdateVideoPlaylist } from '../playlists'
import { forwardVideoRelatedActivity } from '../send/shared/send-utils' import { forwardVideoRelatedActivity } from '../send/shared/send-utils'
import { APVideoUpdater, getOrCreateAPVideo } from '../videos' import { APVideoUpdater, getOrCreateAPVideo } from '../videos'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
const { activity, byActor } = options const { activity, byActor } = options
const objectType = activity.object.type const object = await fetchAPObject(activity.object)
const objectType = object.type
if (objectType === 'Video') { if (objectType === 'Video') {
return retryTransactionWrapper(processUpdateVideo, activity) return retryTransactionWrapper(processUpdateVideo, activity)
@ -28,17 +30,17 @@ async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate
if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') { if (objectType === 'Person' || objectType === 'Application' || objectType === 'Group') {
// We need more attributes // We need more attributes
const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url) const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url)
return retryTransactionWrapper(processUpdateActor, byActorFull, activity) return retryTransactionWrapper(processUpdateActor, byActorFull, object)
} }
if (objectType === 'CacheFile') { if (objectType === 'CacheFile') {
// We need more attributes // We need more attributes
const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url) const byActorFull = await ActorModel.loadByUrlAndPopulateAccountAndChannel(byActor.url)
return retryTransactionWrapper(processUpdateCacheFile, byActorFull, activity) return retryTransactionWrapper(processUpdateCacheFile, byActorFull, activity, object)
} }
if (objectType === 'Playlist') { if (objectType === 'Playlist') {
return retryTransactionWrapper(processUpdatePlaylist, byActor, activity) return retryTransactionWrapper(processUpdatePlaylist, byActor, activity, object)
} }
return undefined return undefined
@ -52,7 +54,7 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
async function processUpdateVideo (activity: ActivityUpdate) { async function processUpdateVideo (activity: ActivityUpdate<VideoObject | string>) {
const videoObject = activity.object as VideoObject const videoObject = activity.object as VideoObject
if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) {
@ -72,11 +74,13 @@ async function processUpdateVideo (activity: ActivityUpdate) {
return updater.update(activity.to) return updater.update(activity.to)
} }
async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { async function processUpdateCacheFile (
byActor: MActorSignature,
activity: ActivityUpdate<CacheFileObject | string>,
cacheFileObject: CacheFileObject
) {
if (await isRedundancyAccepted(activity, byActor) !== true) return if (await isRedundancyAccepted(activity, byActor) !== true) return
const cacheFileObject = activity.object as CacheFileObject
if (!isCacheFileObjectValid(cacheFileObject)) { if (!isCacheFileObjectValid(cacheFileObject)) {
logger.debug('Cache file object sent by update is not valid.', { cacheFileObject }) logger.debug('Cache file object sent by update is not valid.', { cacheFileObject })
return undefined return undefined
@ -96,19 +100,19 @@ async function processUpdateCacheFile (byActor: MActorSignature, activity: Activ
} }
} }
async function processUpdateActor (actor: MActorFull, activity: ActivityUpdate) { async function processUpdateActor (actor: MActorFull, actorObject: ActivityPubActor) {
const actorObject = activity.object as ActivityPubActor
logger.debug('Updating remote account "%s".', actorObject.url) logger.debug('Updating remote account "%s".', actorObject.url)
const updater = new APActorUpdater(actorObject, actor) const updater = new APActorUpdater(actorObject, actor)
return updater.update() return updater.update()
} }
async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) { async function processUpdatePlaylist (
const playlistObject = activity.object as PlaylistObject byActor: MActorSignature,
activity: ActivityUpdate<PlaylistObject | string>,
playlistObject: PlaylistObject
) {
const byAccount = byActor.Account const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url) if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url)
await createOrUpdateVideoPlaylist(playlistObject, activity.to) await createOrUpdateVideoPlaylist(playlistObject, activity.to)

View File

@ -1,6 +1,14 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { ActivityAudience, ActivityCreate, ContextType, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' import {
ActivityAudience,
ActivityCreate,
ActivityCreateObject,
ContextType,
VideoCommentObject,
VideoPlaylistPrivacy,
VideoPrivacy
} from '@shared/models'
import { logger, loggerTagsFactory } from '../../../helpers/logger' import { logger, loggerTagsFactory } from '../../../helpers/logger'
import { VideoCommentModel } from '../../../models/video/video-comment' import { VideoCommentModel } from '../../../models/video/video-comment'
import { import {
@ -107,7 +115,7 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction:
const byActor = comment.Account.Actor const byActor = comment.Account.Actor
const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction) const threadParentComments = await VideoCommentModel.listThreadParentComments(comment, transaction)
const commentObject = comment.toActivityPubObject(threadParentComments) const commentObject = comment.toActivityPubObject(threadParentComments) as VideoCommentObject
const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction) const actorsInvolvedInComment = await getActorsInvolvedInVideo(comment.Video, transaction)
// Add the actor that commented too // Add the actor that commented too
@ -168,7 +176,12 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction:
}) })
} }
function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { function buildCreateActivity <T extends ActivityCreateObject> (
url: string,
byActor: MActorLight,
object: T,
audience?: ActivityAudience
): ActivityCreate<T> {
if (!audience) audience = getAudience(byActor) if (!audience) audience = getAudience(byActor)
return audiencify( return audiencify(
@ -176,7 +189,9 @@ function buildCreateActivity (url: string, byActor: MActorLight, object: any, au
type: 'Create' as 'Create', type: 'Create' as 'Create',
id: url + '/activity', id: url + '/activity',
actor: byActor.url, actor: byActor.url,
object: audiencify(object, audience) object: typeof object === 'string'
? object
: audiencify(object, audience)
}, },
audience audience
) )

View File

@ -1,14 +1,5 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { import { ActivityAudience, ActivityDislike, ActivityLike, ActivityUndo, ActivityUndoObject, ContextType } from '@shared/models'
ActivityAnnounce,
ActivityAudience,
ActivityCreate,
ActivityDislike,
ActivityFollow,
ActivityLike,
ActivityUndo,
ContextType
} from '@shared/models'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
import { import {
@ -128,12 +119,12 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function undoActivityData ( function undoActivityData <T extends ActivityUndoObject> (
url: string, url: string,
byActor: MActorAudience, byActor: MActorAudience,
object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, object: T,
audience?: ActivityAudience audience?: ActivityAudience
): ActivityUndo { ): ActivityUndo<T> {
if (!audience) audience = getAudience(byActor) if (!audience) audience = getAudience(byActor)
return audiencify( return audiencify(
@ -151,7 +142,7 @@ async function sendUndoVideoRelatedActivity (options: {
byActor: MActor byActor: MActor
video: MVideoAccountLight video: MVideoAccountLight
url: string url: string
activity: ActivityFollow | ActivityCreate | ActivityAnnounce activity: ActivityUndoObject
contextType: ContextType contextType: ContextType
transaction: Transaction transaction: Transaction
}) { }) {

View File

@ -1,6 +1,6 @@
import { Transaction } from 'sequelize' import { Transaction } from 'sequelize'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { ActivityAudience, ActivityUpdate, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models' import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@shared/models'
import { logger } from '../../../helpers/logger' import { logger } from '../../../helpers/logger'
import { AccountModel } from '../../../models/account/account' import { AccountModel } from '../../../models/account/account'
import { VideoModel } from '../../../models/video/video' import { VideoModel } from '../../../models/video/video'
@ -137,7 +137,12 @@ export {
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate { function buildUpdateActivity (
url: string,
byActor: MActorLight,
object: ActivityUpdateObject,
audience?: ActivityAudience
): ActivityUpdate<ActivityUpdateObject> {
if (!audience) audience = getAudience(byActor) if (!audience) audience = getAudience(byActor)
return audiencify( return audiencify(

View File

@ -3,7 +3,7 @@ import { logger } from '@server/helpers/logger'
import { JobQueue } from '@server/lib/job-queue' import { JobQueue } from '@server/lib/job-queue'
import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders' import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders'
import { MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models' import { MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models'
import { APObject } from '@shared/models' import { APObjectId } from '@shared/models'
import { getAPId } from '../activity' import { getAPId } from '../activity'
import { refreshVideoIfNeeded } from './refresh' import { refreshVideoIfNeeded } from './refresh'
import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared' import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared'
@ -15,21 +15,21 @@ type GetVideoResult <T> = Promise<{
}> }>
type GetVideoParamAll = { type GetVideoParamAll = {
videoObject: APObject videoObject: APObjectId
syncParam?: SyncParam syncParam?: SyncParam
fetchType?: 'all' fetchType?: 'all'
allowRefresh?: boolean allowRefresh?: boolean
} }
type GetVideoParamImmutable = { type GetVideoParamImmutable = {
videoObject: APObject videoObject: APObjectId
syncParam?: SyncParam syncParam?: SyncParam
fetchType: 'only-immutable-attributes' fetchType: 'only-immutable-attributes'
allowRefresh: false allowRefresh: false
} }
type GetVideoParamOther = { type GetVideoParamOther = {
videoObject: APObject videoObject: APObjectId
syncParam?: SyncParam syncParam?: SyncParam
fetchType?: 'all' | 'only-video' fetchType?: 'all' | 'only-video'
allowRefresh?: boolean allowRefresh?: boolean

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,34 @@
import { ActivityPubActor } from './activitypub-actor' import { ActivityPubActor } from './activitypub-actor'
import { ActivityPubSignature } from './activitypub-signature' import { ActivityPubSignature } from './activitypub-signature'
import { ActivityFlagReasonObject, CacheFileObject, VideoObject, WatchActionObject } from './objects' import {
import { AbuseObject } from './objects/abuse-object' ActivityFlagReasonObject,
import { DislikeObject } from './objects/dislike-object' ActivityObject,
import { APObject } from './objects/object.model' APObjectId,
import { PlaylistObject } from './objects/playlist-object' CacheFileObject,
import { VideoCommentObject } from './objects/video-comment-object' PlaylistObject,
VideoCommentObject,
VideoObject,
WatchActionObject
} from './objects'
export type ActivityUpdateObject =
Extract<ActivityObject, VideoObject | CacheFileObject | PlaylistObject | ActivityPubActor | string> | ActivityPubActor
// Cannot Extract from Activity because of circular reference
export type ActivityUndoObject =
ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate<CacheFileObject | string> | ActivityAnnounce
export type ActivityCreateObject =
Extract<ActivityObject, VideoObject | CacheFileObject | WatchActionObject | VideoCommentObject | PlaylistObject | string>
export type Activity = export type Activity =
ActivityCreate | ActivityCreate<ActivityCreateObject> |
ActivityUpdate | ActivityUpdate<ActivityUpdateObject> |
ActivityDelete | ActivityDelete |
ActivityFollow | ActivityFollow |
ActivityAccept | ActivityAccept |
ActivityAnnounce | ActivityAnnounce |
ActivityUndo | ActivityUndo<ActivityUndoObject> |
ActivityLike | ActivityLike |
ActivityReject | ActivityReject |
ActivityView | ActivityView |
@ -50,19 +64,19 @@ export interface BaseActivity {
signature?: ActivityPubSignature signature?: ActivityPubSignature
} }
export interface ActivityCreate extends BaseActivity { export interface ActivityCreate <T extends ActivityCreateObject> extends BaseActivity {
type: 'Create' type: 'Create'
object: VideoObject | AbuseObject | DislikeObject | VideoCommentObject | CacheFileObject | PlaylistObject | WatchActionObject object: T
} }
export interface ActivityUpdate extends BaseActivity { export interface ActivityUpdate <T extends ActivityUpdateObject> extends BaseActivity {
type: 'Update' type: 'Update'
object: VideoObject | ActivityPubActor | CacheFileObject | PlaylistObject object: T
} }
export interface ActivityDelete extends BaseActivity { export interface ActivityDelete extends BaseActivity {
type: 'Delete' type: 'Delete'
object: string | { id: string } object: APObjectId
} }
export interface ActivityFollow extends BaseActivity { export interface ActivityFollow extends BaseActivity {
@ -82,23 +96,23 @@ export interface ActivityReject extends BaseActivity {
export interface ActivityAnnounce extends BaseActivity { export interface ActivityAnnounce extends BaseActivity {
type: 'Announce' type: 'Announce'
object: APObject object: APObjectId
} }
export interface ActivityUndo extends BaseActivity { export interface ActivityUndo <T extends ActivityUndoObject> extends BaseActivity {
type: 'Undo' type: 'Undo'
object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce object: T
} }
export interface ActivityLike extends BaseActivity { export interface ActivityLike extends BaseActivity {
type: 'Like' type: 'Like'
object: APObject object: APObjectId
} }
export interface ActivityView extends BaseActivity { export interface ActivityView extends BaseActivity {
type: 'View' type: 'View'
actor: string actor: string
object: APObject object: APObjectId
// If sending a "viewer" event // If sending a "viewer" event
expires?: string expires?: string
@ -108,13 +122,13 @@ export interface ActivityDislike extends BaseActivity {
id: string id: string
type: 'Dislike' type: 'Dislike'
actor: string actor: string
object: APObject object: APObjectId
} }
export interface ActivityFlag extends BaseActivity { export interface ActivityFlag extends BaseActivity {
type: 'Flag' type: 'Flag'
content: string content: string
object: APObject | APObject[] object: APObjectId | APObjectId[]
tag?: ActivityFlagReasonObject[] tag?: ActivityFlagReasonObject[]
startAt?: number startAt?: number
endAt?: number endAt?: number

View File

@ -0,0 +1,17 @@
import { AbuseObject } from './abuse-object'
import { CacheFileObject } from './cache-file-object'
import { PlaylistObject } from './playlist-object'
import { VideoCommentObject } from './video-comment-object'
import { VideoObject } from './video-object'
import { WatchActionObject } from './watch-action-object'
export type ActivityObject =
VideoObject |
AbuseObject |
VideoCommentObject |
CacheFileObject |
PlaylistObject |
WatchActionObject |
string
export type APObjectId = string | { id: string }

View File

@ -1,6 +0,0 @@
export interface DislikeObject {
id: string
type: 'Dislike'
actor: string
object: string
}

View File

@ -1,8 +1,7 @@
export * from './abuse-object' export * from './abuse-object'
export * from './activitypub-object'
export * from './cache-file-object' export * from './cache-file-object'
export * from './common-objects' export * from './common-objects'
export * from './dislike-object'
export * from './object.model'
export * from './playlist-element-object' export * from './playlist-element-object'
export * from './playlist-object' export * from './playlist-object'
export * from './video-comment-object' export * from './video-comment-object'

View File

@ -1 +0,0 @@
export type APObject = string | { id: string }