import { sanitizeUrl } from '@server/helpers/core-utils'
import { doJSONRequest } from '@server/helpers/requests'
import { CONFIG } from '@server/initializers/config'
-import { getOrCreateVideoAndAccountAndChannel } from '@server/lib/activitypub/videos'
+import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
import { Hooks } from '@server/lib/plugins/hooks'
import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
import { getServerActor } from '@server/models/application/application'
refreshVideo: false
}
- const result = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam })
+ const result = await getOrCreateAPVideo({ videoObject: url, syncParam })
video = result ? result.video : undefined
} catch (err) {
logger.info('Cannot search remote video %s.', url, { err })
import * as express from 'express'
import toInt from 'validator/lib/toInt'
+import { doJSONRequest } from '@server/helpers/requests'
import { LiveManager } from '@server/lib/live-manager'
import { getServerActor } from '@server/models/application/application'
+import { MVideoAccountLight } from '@server/types/models'
import { VideosCommonQuery } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/core-utils/miscs'
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
import { logger } from '../../../helpers/logger'
import { getFormattedObjects } from '../../../helpers/utils'
-import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
+import { REMOTE_SCHEME, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
import { sequelizeTypescript } from '../../../initializers/database'
import { sendView } from '../../../lib/activitypub/send/send-view'
-import { fetchRemoteVideoDescription } from '../../../lib/activitypub/videos'
import { JobQueue } from '../../../lib/job-queue'
import { Hooks } from '../../../lib/plugins/hooks'
import { Redis } from '../../../lib/redis'
.status(HttpStatusCode.NO_CONTENT_204)
.end()
}
+
+// ---------------------------------------------------------------------------
+
+// FIXME: Should not exist, we rely on specific API
+async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
+ const host = video.VideoChannel.Account.Actor.Server.host
+ const path = video.getDescriptionAPIPath()
+ const url = REMOTE_SCHEME.HTTP + '://' + host + path
+
+ const { body } = await doJSONRequest<any>(url)
+ return body.description || ''
+}
import { createPlaylistMiniatureFromUrl } from '../thumbnail'
import { getOrCreateActorAndServerAndModel } from './actor'
import { crawlCollectionPage } from './crawl'
-import { getOrCreateVideoAndAccountAndChannel } from './videos'
+import { getOrCreateAPVideo } from './videos'
function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) {
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
throw new Error(`Playlist element url ${elementUrl} host is different from the AP object id ${body.id}`)
}
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: { id: body.url }, fetchType: 'only-video' })
+ const { video } = await getOrCreateAPVideo({ videoObject: { id: body.url }, fetchType: 'only-video' })
elementsToCreate.push(playlistElementObjectToDBAttributes(body, playlist, video))
} catch (err) {
import { sequelizeTypescript } from '../../../initializers/database'
import { VideoShareModel } from '../../../models/video/video-share'
import { forwardVideoRelatedActivity } from '../send/utils'
-import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { getOrCreateAPVideo } from '../videos'
import { Notifier } from '../../notifier'
import { logger } from '../../../helpers/logger'
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
let videoCreated: boolean
try {
- const result = await getOrCreateVideoAndAccountAndChannel({ videoObject: objectUri })
+ const result = await getOrCreateAPVideo({ videoObject: objectUri })
video = result.video
videoCreated = result.created
} catch (err) {
import { createOrUpdateVideoPlaylist } from '../playlist'
import { forwardVideoRelatedActivity } from '../send/utils'
import { resolveThread } from '../video-comments'
-import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { getOrCreateAPVideo } from '../videos'
import { isBlockedByServerOrAccount } from '@server/lib/blocklist'
async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) {
const videoToCreateData = activity.object as VideoObject
const syncParam = { likes: false, dislikes: false, shares: false, comments: false, thumbnail: true, refreshVideo: false }
- const { video, created } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoToCreateData, syncParam })
+ const { video, created } = await getOrCreateAPVideo({ videoObject: videoToCreateData, syncParam })
if (created && notify) Notifier.Instance.notifyOnNewVideoIfNeeded(video)
const cacheFile = activity.object as CacheFileObject
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object })
+ const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
await sequelizeTypescript.transaction(async t => {
return createOrUpdateCacheFile(cacheFile, video, byActor, t)
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorSignature } from '../../../types/models'
import { forwardVideoRelatedActivity } from '../send/utils'
-import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { getOrCreateAPVideo } from '../videos'
async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) {
const { activity, byActor } = options
if (!byAccount) throw new Error('Cannot create dislike with the non account actor ' + byActor.url)
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislikeObject })
+ const { video } = await getOrCreateAPVideo({ videoObject: dislikeObject })
return sequelizeTypescript.transaction(async t => {
const existingRate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byAccount.id, video.id, activity.id, t)
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorSignature } from '../../../types/models'
import { forwardVideoRelatedActivity } from '../send/utils'
-import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { getOrCreateAPVideo } from '../videos'
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
const { activity, byActor } = options
const byAccount = byActor.Account
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoUrl })
+ const { video } = await getOrCreateAPVideo({ videoObject: videoUrl })
return sequelizeTypescript.transaction(async t => {
const existingRate = await AccountVideoRateModel.loadByAccountAndVideoOrUrl(byAccount.id, video.id, activity.id, t)
import { APProcessorOptions } from '../../../types/activitypub-processor.model'
import { MActorSignature } from '../../../types/models'
import { forwardVideoRelatedActivity } from '../send/utils'
-import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { getOrCreateAPVideo } from '../videos'
async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) {
const { activity, byActor } = options
async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) {
const likeActivity = activity.object as ActivityLike
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object })
+ const { video } = await getOrCreateAPVideo({ videoObject: likeActivity.object })
return sequelizeTypescript.transaction(async t => {
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
? activity.object
: activity.object.object as DislikeObject
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: dislike.object })
+ const { video } = await getOrCreateAPVideo({ videoObject: dislike.object })
return sequelizeTypescript.transaction(async t => {
if (!byActor.Account) throw new Error('Unknown account ' + byActor.url)
async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) {
const cacheFileObject = activity.object.object as CacheFileObject
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
+ const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
return sequelizeTypescript.transaction(async t => {
const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id)
import { createOrUpdateCacheFile } from '../cache-file'
import { createOrUpdateVideoPlaylist } from '../playlist'
import { forwardVideoRelatedActivity } from '../send/utils'
-import { APVideoUpdater, getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { APVideoUpdater, getOrCreateAPVideo } from '../videos'
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) {
const { activity, byActor } = options
return undefined
}
- const { video, created } = await getOrCreateVideoAndAccountAndChannel({
+ const { video, created } = await getOrCreateAPVideo({
videoObject: videoObject.id,
allowRefresh: false,
fetchType: 'all'
return undefined
}
- const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object })
+ const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
await sequelizeTypescript.transaction(async t => {
await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
-import { getOrCreateVideoAndAccountAndChannel } from '../videos'
+import { getOrCreateAPVideo } from '../videos'
import { forwardVideoRelatedActivity } from '../send/utils'
import { Redis } from '../../redis'
import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub'
fetchType: 'only-video' as 'only-video',
allowRefresh: false as false
}
- const { video } = await getOrCreateVideoAndAccountAndChannel(options)
+ const { video } = await getOrCreateAPVideo(options)
if (!video.isLive) {
await Redis.Instance.addVideoView(video.id)
import { VideoCommentModel } from '../../models/video/video-comment'
import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../types/models/video'
import { getOrCreateActorAndServerAndModel } from './actor'
-import { getOrCreateVideoAndAccountAndChannel } from './videos'
+import { getOrCreateAPVideo } from './videos'
type ResolveThreadParams = {
url: string
// 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 { video } = await getOrCreateAPVideo({ videoObject: url, syncParam })
if (video.isOwned() && !video.hasPrivacyForFederation()) {
throw new Error('Cannot resolve thread of video with privacy that is not compatible with federation')
+++ /dev/null
-import { checkUrlsSameHost, getAPId } from '@server/helpers/activitypub'
-import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos'
-import { retryTransactionWrapper } from '@server/helpers/database-utils'
-import { logger } from '@server/helpers/logger'
-import { doJSONRequest, PeerTubeRequestError } from '@server/helpers/requests'
-import { fetchVideoByUrl, VideoFetchByUrlType } from '@server/helpers/video'
-import { REMOTE_SCHEME } from '@server/initializers/constants'
-import { ActorFollowScoreCache } from '@server/lib/files-cache'
-import { JobQueue } from '@server/lib/job-queue'
-import { VideoModel } from '@server/models/video/video'
-import { MVideoAccountLight, MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models'
-import { HttpStatusCode } from '@shared/core-utils'
-import { VideoObject } from '@shared/models'
-import { APVideoCreator, SyncParam, syncVideoExternalAttributes } from './shared'
-import { APVideoUpdater } from './updater'
-
-async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> {
- logger.info('Fetching remote video %s.', videoUrl)
-
- const { statusCode, body } = await doJSONRequest<any>(videoUrl, { activityPub: true })
-
- if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) {
- logger.debug('Remote video JSON is not valid.', { body })
- return { statusCode, videoObject: undefined }
- }
-
- return { statusCode, videoObject: body }
-}
-
-async function fetchRemoteVideoDescription (video: MVideoAccountLight) {
- const host = video.VideoChannel.Account.Actor.Server.host
- const path = video.getDescriptionAPIPath()
- const url = REMOTE_SCHEME.HTTP + '://' + host + path
-
- const { body } = await doJSONRequest<any>(url)
- return body.description || ''
-}
-
-type GetVideoResult <T> = Promise<{
- video: T
- created: boolean
- autoBlacklisted?: boolean
-}>
-
-type GetVideoParamAll = {
- videoObject: { id: string } | string
- syncParam?: SyncParam
- fetchType?: 'all'
- allowRefresh?: boolean
-}
-
-type GetVideoParamImmutable = {
- videoObject: { id: string } | string
- syncParam?: SyncParam
- fetchType: 'only-immutable-attributes'
- allowRefresh: false
-}
-
-type GetVideoParamOther = {
- videoObject: { id: string } | string
- syncParam?: SyncParam
- fetchType?: 'all' | 'only-video'
- allowRefresh?: boolean
-}
-
-function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
-function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
-function getOrCreateVideoAndAccountAndChannel (
- options: GetVideoParamOther
-): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
-async function getOrCreateVideoAndAccountAndChannel (
- options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
-): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
- // Default params
- const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
- const fetchType = options.fetchType || 'all'
- const allowRefresh = options.allowRefresh !== false
-
- // Get video url
- const videoUrl = getAPId(options.videoObject)
- let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType)
-
- if (videoFromDatabase) {
- // If allowRefresh is true, we could not call this function using 'only-immutable-attributes' fetch type
- if (allowRefresh === true && (videoFromDatabase as MVideoThumbnail).isOutdated()) {
- const refreshOptions = {
- video: videoFromDatabase as MVideoThumbnail,
- fetchedType: fetchType,
- syncParam
- }
-
- if (syncParam.refreshVideo === true) {
- videoFromDatabase = await refreshVideoIfNeeded(refreshOptions)
- } else {
- await JobQueue.Instance.createJobWithPromise({
- type: 'activitypub-refresher',
- payload: { type: 'video', url: videoFromDatabase.url }
- })
- }
- }
-
- return { video: videoFromDatabase, created: false }
- }
-
- const { videoObject } = await fetchRemoteVideo(videoUrl)
- if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
-
- try {
- const creator = new APVideoCreator(videoObject)
- const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail)
-
- await syncVideoExternalAttributes(videoCreated, videoObject, syncParam)
-
- return { video: videoCreated, created: true, autoBlacklisted }
- } catch (err) {
- // Maybe a concurrent getOrCreateVideoAndAccountAndChannel call created this video
- if (err.name === 'SequelizeUniqueConstraintError') {
- const fallbackVideo = await fetchVideoByUrl(videoUrl, fetchType)
- if (fallbackVideo) return { video: fallbackVideo, created: false }
- }
-
- throw err
- }
-}
-
-async function refreshVideoIfNeeded (options: {
- video: MVideoThumbnail
- fetchedType: VideoFetchByUrlType
- syncParam: SyncParam
-}): Promise<MVideoThumbnail> {
- if (!options.video.isOutdated()) return options.video
-
- // We need more attributes if the argument video was fetched with not enough joints
- const video = options.fetchedType === 'all'
- ? options.video as MVideoAccountLightBlacklistAllFiles
- : await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
-
- try {
- const { videoObject } = await fetchRemoteVideo(video.url)
-
- if (videoObject === undefined) {
- logger.warn('Cannot refresh remote video %s: invalid body.', video.url)
-
- await video.setAsRefreshed()
- return video
- }
-
- const videoUpdater = new APVideoUpdater(videoObject, video)
- await videoUpdater.update()
-
- await syncVideoExternalAttributes(video, videoObject, options.syncParam)
-
- ActorFollowScoreCache.Instance.addGoodServerId(video.VideoChannel.Actor.serverId)
-
- return video
- } catch (err) {
- if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
- logger.info('Cannot refresh remote video %s: video does not exist anymore. Deleting it.', video.url)
-
- // Video does not exist anymore
- await video.destroy()
- return undefined
- }
-
- logger.warn('Cannot refresh video %s.', options.video.url, { err })
-
- ActorFollowScoreCache.Instance.addBadServerId(video.VideoChannel.Actor.serverId)
-
- // Don't refresh in loop
- await video.setAsRefreshed()
- return video
- }
-}
-
-export {
- fetchRemoteVideo,
- fetchRemoteVideoDescription,
- refreshVideoIfNeeded,
- getOrCreateVideoAndAccountAndChannel
-}
--- /dev/null
+import { getAPId } from '@server/helpers/activitypub'
+import { retryTransactionWrapper } from '@server/helpers/database-utils'
+import { fetchVideoByUrl, VideoFetchByUrlType } from '@server/helpers/video'
+import { JobQueue } from '@server/lib/job-queue'
+import { MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models'
+import { refreshVideoIfNeeded } from './refresh'
+import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared'
+
+type GetVideoResult <T> = Promise<{
+ video: T
+ created: boolean
+ autoBlacklisted?: boolean
+}>
+
+type GetVideoParamAll = {
+ videoObject: { id: string } | string
+ syncParam?: SyncParam
+ fetchType?: 'all'
+ allowRefresh?: boolean
+}
+
+type GetVideoParamImmutable = {
+ videoObject: { id: string } | string
+ syncParam?: SyncParam
+ fetchType: 'only-immutable-attributes'
+ allowRefresh: false
+}
+
+type GetVideoParamOther = {
+ videoObject: { id: string } | string
+ syncParam?: SyncParam
+ fetchType?: 'all' | 'only-video'
+ allowRefresh?: boolean
+}
+
+function getOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
+function getOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
+function getOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
+
+async function getOrCreateAPVideo (
+ options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
+): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
+ // Default params
+ const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false }
+ const fetchType = options.fetchType || 'all'
+ const allowRefresh = options.allowRefresh !== false
+
+ // Get video url
+ const videoUrl = getAPId(options.videoObject)
+ let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType)
+
+ if (videoFromDatabase) {
+ if (allowRefresh === true) {
+ // Typings ensure allowRefresh === false in only-immutable-attributes fetch type
+ videoFromDatabase = await scheduleRefresh(videoFromDatabase as MVideoThumbnail, fetchType, syncParam)
+ }
+
+ return { video: videoFromDatabase, created: false }
+ }
+
+ const { videoObject } = await fetchRemoteVideo(videoUrl)
+ if (!videoObject) throw new Error('Cannot fetch remote video with url: ' + videoUrl)
+
+ try {
+ const creator = new APVideoCreator(videoObject)
+ const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(creator.create.bind(creator), syncParam.thumbnail)
+
+ await syncVideoExternalAttributes(videoCreated, videoObject, syncParam)
+
+ return { video: videoCreated, created: true, autoBlacklisted }
+ } catch (err) {
+ // Maybe a concurrent getOrCreateAPVideo call created this video
+ if (err.name === 'SequelizeUniqueConstraintError') {
+ const alreadyCreatedVideo = await fetchVideoByUrl(videoUrl, fetchType)
+ if (alreadyCreatedVideo) return { video: alreadyCreatedVideo, created: false }
+ }
+
+ throw err
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ getOrCreateAPVideo
+}
+
+// ---------------------------------------------------------------------------
+
+async function scheduleRefresh (video: MVideoThumbnail, fetchType: VideoFetchByUrlType, syncParam: SyncParam) {
+ if (!video.isOutdated()) return video
+
+ const refreshOptions = {
+ video,
+ fetchedType: fetchType,
+ syncParam
+ }
+
+ if (syncParam.refreshVideo === true) {
+ return refreshVideoIfNeeded(refreshOptions)
+ }
+
+ await JobQueue.Instance.createJobWithPromise({
+ type: 'activitypub-refresher',
+ payload: { type: 'video', url: video.url }
+ })
+
+ return video
+}
export * from './federate'
-export * from './fetch'
+export * from './get'
+export * from './refresh'
export * from './updater'
--- /dev/null
+import { logger } from '@server/helpers/logger'
+import { PeerTubeRequestError } from '@server/helpers/requests'
+import { VideoFetchByUrlType } from '@server/helpers/video'
+import { ActorFollowScoreCache } from '@server/lib/files-cache'
+import { VideoModel } from '@server/models/video/video'
+import { MVideoAccountLightBlacklistAllFiles, MVideoThumbnail } from '@server/types/models'
+import { HttpStatusCode } from '@shared/core-utils'
+import { fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared'
+import { APVideoUpdater } from './updater'
+
+async function refreshVideoIfNeeded (options: {
+ video: MVideoThumbnail
+ fetchedType: VideoFetchByUrlType
+ syncParam: SyncParam
+}): Promise<MVideoThumbnail> {
+ if (!options.video.isOutdated()) return options.video
+
+ // We need more attributes if the argument video was fetched with not enough joints
+ const video = options.fetchedType === 'all'
+ ? options.video as MVideoAccountLightBlacklistAllFiles
+ : await VideoModel.loadByUrlAndPopulateAccount(options.video.url)
+
+ try {
+ const { videoObject } = await fetchRemoteVideo(video.url)
+
+ if (videoObject === undefined) {
+ logger.warn('Cannot refresh remote video %s: invalid body.', video.url)
+
+ await video.setAsRefreshed()
+ return video
+ }
+
+ const videoUpdater = new APVideoUpdater(videoObject, video)
+ await videoUpdater.update()
+
+ await syncVideoExternalAttributes(video, videoObject, options.syncParam)
+
+ ActorFollowScoreCache.Instance.addGoodServerId(video.VideoChannel.Actor.serverId)
+
+ return video
+ } catch (err) {
+ if ((err as PeerTubeRequestError).statusCode === HttpStatusCode.NOT_FOUND_404) {
+ logger.info('Cannot refresh remote video %s: video does not exist anymore. Deleting it.', video.url)
+
+ // Video does not exist anymore
+ await video.destroy()
+ return undefined
+ }
+
+ logger.warn('Cannot refresh video %s.', options.video.url, { err })
+
+ ActorFollowScoreCache.Instance.addBadServerId(video.VideoChannel.Actor.serverId)
+
+ // Don't refresh in loop
+ await video.setAsRefreshed()
+ return video
+ }
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ refreshVideoIfNeeded
+}
export * from './creator'
export * from './object-to-model-attributes'
export * from './trackers'
+export * from './url-to-object'
export * from './video-sync-attributes'
--- /dev/null
+import { checkUrlsSameHost } from '@server/helpers/activitypub'
+import { sanitizeAndCheckVideoTorrentObject } from '@server/helpers/custom-validators/activitypub/videos'
+import { logger } from '@server/helpers/logger'
+import { doJSONRequest } from '@server/helpers/requests'
+import { VideoObject } from '@shared/models'
+
+async function fetchRemoteVideo (videoUrl: string): Promise<{ statusCode: number, videoObject: VideoObject }> {
+ logger.info('Fetching remote video %s.', videoUrl)
+
+ const { statusCode, body } = await doJSONRequest<any>(videoUrl, { activityPub: true })
+
+ if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) {
+ logger.debug('Remote video JSON is not valid.', { body })
+ return { statusCode, videoObject: undefined }
+ }
+
+ return { statusCode, videoObject: body }
+}
+
+export {
+ fetchRemoteVideo
+}
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy'
import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send'
import { getLocalVideoCacheFileActivityPubUrl, getLocalVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url'
-import { getOrCreateVideoAndAccountAndChannel } from '../activitypub/videos'
+import { getOrCreateAPVideo } from '../activitypub/videos'
import { downloadPlaylistSegments } from '../hls'
import { removeVideoRedundancy } from '../redundancy'
import { generateHLSRedundancyUrl, generateWebTorrentRedundancyUrl } from '../video-paths'
syncParam: { likes: false, dislikes: false, shares: false, comments: false, thumbnail: false, refreshVideo: true },
fetchType: 'all' as 'all'
}
- const { video } = await getOrCreateVideoAndAccountAndChannel(getVideoOptions)
+ const { video } = await getOrCreateAPVideo(getVideoOptions)
return video
}
}
function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) {
+ if (!model.createdAt || !model.updatedAt) {
+ throw new Error('Miss createdAt & updatedAt attribuets to model')
+ }
+
const now = Date.now()
const createdAtTime = model.createdAt.getTime()
const updatedAtTime = model.updatedAt.getTime()