mirror of https://github.com/Chocobozzz/PeerTube
Compare commits
5 Commits
ac7e2f832b
...
8e2d1e5090
Author | SHA1 | Date |
---|---|---|
kontrollanten | 8e2d1e5090 | |
kontrollanten | 7e57daa971 | |
Chocobozzz | d72ef2a2b9 | |
Chocobozzz | afb28272f5 | |
Chocobozzz | b8635c2606 |
|
@ -250,6 +250,10 @@ export class PluginService implements ClientHook {
|
||||||
return firstValueFrom(obs)
|
return firstValueFrom(obs)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUser: () => {
|
||||||
|
return this.authService.getUser()
|
||||||
|
},
|
||||||
|
|
||||||
getServerConfig: () => {
|
getServerConfig: () => {
|
||||||
const obs = this.server.getConfig()
|
const obs = this.server.getConfig()
|
||||||
.pipe(catchError(res => this.restExtractor.handleError(res)))
|
.pipe(catchError(res => this.restExtractor.handleError(res)))
|
||||||
|
|
|
@ -74,54 +74,12 @@ export class User implements UserServerModel {
|
||||||
createdAt: Date
|
createdAt: Date
|
||||||
|
|
||||||
constructor (hash: Partial<UserServerModel>) {
|
constructor (hash: Partial<UserServerModel>) {
|
||||||
this.id = hash.id
|
const { account, ...mergeProps }: Partial<UserServerModel> = hash
|
||||||
this.username = hash.username
|
|
||||||
this.email = hash.email
|
|
||||||
|
|
||||||
this.role = hash.role
|
Object.assign(this, mergeProps)
|
||||||
|
|
||||||
this.videoChannels = hash.videoChannels
|
if (account !== undefined) {
|
||||||
|
this.account = new Account(account)
|
||||||
this.videoQuota = hash.videoQuota
|
|
||||||
this.videoQuotaDaily = hash.videoQuotaDaily
|
|
||||||
this.videoQuotaUsed = hash.videoQuotaUsed
|
|
||||||
this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily
|
|
||||||
this.videosCount = hash.videosCount
|
|
||||||
this.abusesCount = hash.abusesCount
|
|
||||||
this.abusesAcceptedCount = hash.abusesAcceptedCount
|
|
||||||
this.abusesCreatedCount = hash.abusesCreatedCount
|
|
||||||
this.videoCommentsCount = hash.videoCommentsCount
|
|
||||||
|
|
||||||
this.nsfwPolicy = hash.nsfwPolicy
|
|
||||||
this.p2pEnabled = hash.p2pEnabled
|
|
||||||
this.autoPlayVideo = hash.autoPlayVideo
|
|
||||||
this.autoPlayNextVideo = hash.autoPlayNextVideo
|
|
||||||
this.autoPlayNextVideoPlaylist = hash.autoPlayNextVideoPlaylist
|
|
||||||
this.videosHistoryEnabled = hash.videosHistoryEnabled
|
|
||||||
this.videoLanguages = hash.videoLanguages
|
|
||||||
|
|
||||||
this.theme = hash.theme
|
|
||||||
|
|
||||||
this.adminFlags = hash.adminFlags
|
|
||||||
|
|
||||||
this.blocked = hash.blocked
|
|
||||||
this.blockedReason = hash.blockedReason
|
|
||||||
|
|
||||||
this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal
|
|
||||||
this.noWelcomeModal = hash.noWelcomeModal
|
|
||||||
this.noAccountSetupWarningModal = hash.noAccountSetupWarningModal
|
|
||||||
|
|
||||||
this.notificationSettings = hash.notificationSettings
|
|
||||||
|
|
||||||
this.twoFactorEnabled = hash.twoFactorEnabled
|
|
||||||
|
|
||||||
this.createdAt = hash.createdAt
|
|
||||||
|
|
||||||
this.pluginAuth = hash.pluginAuth
|
|
||||||
this.lastLoginDate = hash.lastLoginDate
|
|
||||||
|
|
||||||
if (hash.account !== undefined) {
|
|
||||||
this.account = new Account(hash.account)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,8 @@ export class PeerTubePlugin {
|
||||||
.then((obj: PublicServerSetting) => obj.publicSettings)
|
.then((obj: PublicServerSetting) => obj.publicSettings)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getUser: unimplemented,
|
||||||
|
|
||||||
isLoggedIn: () => this.http.isLoggedIn(),
|
isLoggedIn: () => this.http.isLoggedIn(),
|
||||||
getAuthHeader: () => {
|
getAuthHeader: () => {
|
||||||
if (!this.http.isLoggedIn()) return undefined
|
if (!this.http.isLoggedIn()) return undefined
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
MyUser,
|
||||||
RegisterClientFormFieldOptions,
|
RegisterClientFormFieldOptions,
|
||||||
RegisterClientHookOptions,
|
RegisterClientHookOptions,
|
||||||
RegisterClientRouteOptions,
|
RegisterClientRouteOptions,
|
||||||
|
@ -35,6 +36,8 @@ export type RegisterClientHelpers = {
|
||||||
|
|
||||||
getSettings: () => Promise<SettingEntries>
|
getSettings: () => Promise<SettingEntries>
|
||||||
|
|
||||||
|
getUser: () => MyUser
|
||||||
|
|
||||||
getServerConfig: () => Promise<ServerConfig>
|
getServerConfig: () => Promise<ServerConfig>
|
||||||
|
|
||||||
notifier: {
|
notifier: {
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
|
import { FIXTURE_URLS } from '@tests/shared/fixture-urls.js'
|
||||||
import { sortObjectComparator } from '@peertube/peertube-core-utils'
|
import { sortObjectComparator } from '@peertube/peertube-core-utils'
|
||||||
import { UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
|
import { HttpStatusCode, UserAdminFlag, UserRole, VideoBlacklist, VideoBlacklistType } from '@peertube/peertube-models'
|
||||||
import {
|
import {
|
||||||
BlacklistCommand,
|
BlacklistCommand,
|
||||||
cleanupTests,
|
cleanupTests,
|
||||||
createMultipleServers,
|
createMultipleServers,
|
||||||
doubleFollow, PeerTubeServer,
|
doubleFollow, makeActivityPubGetRequest, PeerTubeServer,
|
||||||
setAccessTokensToServers,
|
setAccessTokensToServers,
|
||||||
setDefaultChannelAvatar,
|
setDefaultChannelAvatar,
|
||||||
waitJobs
|
waitJobs
|
||||||
|
@ -298,6 +298,13 @@ describe('Test video blacklist', function () {
|
||||||
expect(video4Blacklisted.unfederated).to.be.true
|
expect(video4Blacklisted.unfederated).to.be.true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Should not have AP comments/announces/likes/dislikes', async function () {
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/comments`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/announces`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/likes`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
await makeActivityPubGetRequest(servers[0].url, `/videos/watch/${video3UUID}/dislikes`, HttpStatusCode.UNAUTHORIZED_401)
|
||||||
|
})
|
||||||
|
|
||||||
it('Should remove the video from blacklist and refederate the video', async function () {
|
it('Should remove the video from blacklist and refederate the video', async function () {
|
||||||
await command.remove({ videoId: video4UUID })
|
await command.remove({ videoId: video4UUID })
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
import { buildAbsoluteFixturePath } from '@peertube/peertube-node-utils'
|
||||||
import { signAndContextify } from '@peertube/peertube-server/core/helpers/activity-pub-utils.js'
|
import { signAndContextify } from '@peertube/peertube-server/core/helpers/activity-pub-utils.js'
|
||||||
import { isHTTPSignatureVerified, parseHTTPSignature } from '@peertube/peertube-server/core/helpers/peertube-crypto.js'
|
import { isHTTPSignatureVerified, parseHTTPSignature } from '@peertube/peertube-server/core/helpers/peertube-crypto.js'
|
||||||
import { isJsonLDSignatureVerified, signJsonLDObject } from '@peertube/peertube-server/core/helpers/peertube-jsonld.js'
|
import { compactJSONLDAndCheckSignature, signJsonLDObject } from '@peertube/peertube-server/core/helpers/peertube-jsonld.js'
|
||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
import { readJsonSync } from 'fs-extra/esm'
|
import { readJsonSync } from 'fs-extra/esm'
|
||||||
import cloneDeep from 'lodash-es/cloneDeep.js'
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
||||||
|
@ -24,6 +24,10 @@ function fakeFilter () {
|
||||||
return (data: any) => Promise.resolve(data)
|
return (data: any) => Promise.resolve(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fakeExpressReq (body: any) {
|
||||||
|
return { body }
|
||||||
|
}
|
||||||
|
|
||||||
describe('Test activity pub helpers', function () {
|
describe('Test activity pub helpers', function () {
|
||||||
|
|
||||||
describe('When checking the Linked Signature', function () {
|
describe('When checking the Linked Signature', function () {
|
||||||
|
@ -33,7 +37,7 @@ describe('Test activity pub helpers', function () {
|
||||||
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
||||||
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
|
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, body)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(body))
|
||||||
|
|
||||||
expect(result).to.be.false
|
expect(result).to.be.false
|
||||||
})
|
})
|
||||||
|
@ -43,7 +47,7 @@ describe('Test activity pub helpers', function () {
|
||||||
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/bad-public-key.json')).publicKey
|
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/bad-public-key.json')).publicKey
|
||||||
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
|
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, body)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(body))
|
||||||
|
|
||||||
expect(result).to.be.false
|
expect(result).to.be.false
|
||||||
})
|
})
|
||||||
|
@ -53,7 +57,7 @@ describe('Test activity pub helpers', function () {
|
||||||
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
const publicKey = readJsonSync(buildAbsoluteFixturePath('./ap-json/mastodon/public-key.json')).publicKey
|
||||||
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
|
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, body)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(body))
|
||||||
|
|
||||||
expect(result).to.be.true
|
expect(result).to.be.true
|
||||||
})
|
})
|
||||||
|
@ -72,7 +76,7 @@ describe('Test activity pub helpers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(signedBody))
|
||||||
|
|
||||||
expect(result).to.be.false
|
expect(result).to.be.false
|
||||||
})
|
})
|
||||||
|
@ -91,7 +95,7 @@ describe('Test activity pub helpers', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9002/accounts/peertube' }
|
||||||
const result = await isJsonLDSignatureVerified(fromActor as any, signedBody)
|
const result = await compactJSONLDAndCheckSignature(fromActor as any, fakeExpressReq(signedBody))
|
||||||
|
|
||||||
expect(result).to.be.true
|
expect(result).to.be.true
|
||||||
})
|
})
|
||||||
|
|
|
@ -120,7 +120,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity',
|
||||||
activityPubClientRouter.get('/videos/watch/:id/announces',
|
activityPubClientRouter.get('/videos/watch/:id/announces',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoAnnouncesController)
|
asyncMiddleware(videoAnnouncesController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||||
|
@ -132,19 +132,19 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
|
||||||
activityPubClientRouter.get('/videos/watch/:id/likes',
|
activityPubClientRouter.get('/videos/watch/:id/likes',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoLikesController)
|
asyncMiddleware(videoLikesController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/dislikes',
|
activityPubClientRouter.get('/videos/watch/:id/dislikes',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoDislikesController)
|
asyncMiddleware(videoDislikesController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:id/comments',
|
activityPubClientRouter.get('/videos/watch/:id/comments',
|
||||||
executeIfActivityPub,
|
executeIfActivityPub,
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoCommentsController)
|
asyncMiddleware(videoCommentsController)
|
||||||
)
|
)
|
||||||
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
|
activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
|
||||||
|
@ -175,7 +175,7 @@ activityPubClientRouter.get('/videos/watch/:id/chapters',
|
||||||
activityPubRateLimiter,
|
activityPubRateLimiter,
|
||||||
apVideoChaptersSetCacheKey,
|
apVideoChaptersSetCacheKey,
|
||||||
chaptersCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
chaptersCacheRouteMiddleware(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS),
|
||||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(videoChaptersController)
|
asyncMiddleware(videoChaptersController)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoAnnouncesController (req: express.Request, res: express.Response) {
|
async function videoAnnouncesController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
@ -347,7 +347,7 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoLikesController (req: express.Request, res: express.Response) {
|
async function videoLikesController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
@ -357,7 +357,7 @@ async function videoLikesController (req: express.Request, res: express.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoDislikesController (req: express.Request, res: express.Response) {
|
async function videoDislikesController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ async function videoDislikesController (req: express.Request, res: express.Respo
|
||||||
}
|
}
|
||||||
|
|
||||||
async function videoCommentsController (req: express.Request, res: express.Response) {
|
async function videoCommentsController (req: express.Request, res: express.Response) {
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyVideo
|
||||||
|
|
||||||
if (redirectIfNotOwned(video.url, res)) return
|
if (redirectIfNotOwned(video.url, res)) return
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { replaceChapters } from '@server/lib/video-chapters.js'
|
||||||
const videoChaptersRouter = express.Router()
|
const videoChaptersRouter = express.Router()
|
||||||
|
|
||||||
videoChaptersRouter.get('/:id/chapters',
|
videoChaptersRouter.get('/:id/chapters',
|
||||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
asyncMiddleware(listVideoChapters)
|
asyncMiddleware(listVideoChapters)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import express from 'express'
|
import { HttpStatusCode, VideoChangeOwnershipStatus } from '@peertube/peertube-models'
|
||||||
import { HttpStatusCode, VideoChangeOwnershipStatus, VideoState } from '@peertube/peertube-models'
|
import { canVideoBeFederated } from '@server/lib/activitypub/videos/federate.js'
|
||||||
import { MVideoFullLight } from '@server/types/models/index.js'
|
import { MVideoFullLight } from '@server/types/models/index.js'
|
||||||
|
import express from 'express'
|
||||||
import { logger } from '../../../helpers/logger.js'
|
import { logger } from '../../../helpers/logger.js'
|
||||||
import { getFormattedObjects } from '../../../helpers/utils.js'
|
import { getFormattedObjects } from '../../../helpers/utils.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
|
@ -113,7 +114,7 @@ function acceptOwnership (req: express.Request, res: express.Response) {
|
||||||
const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
|
const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight
|
||||||
targetVideoUpdated.VideoChannel = channel
|
targetVideoUpdated.VideoChannel = channel
|
||||||
|
|
||||||
if (targetVideoUpdated.hasPrivacyForFederation() && targetVideoUpdated.state === VideoState.PUBLISHED) {
|
if (canVideoBeFederated(targetVideoUpdated)) {
|
||||||
await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
|
await changeVideoChannelShare(targetVideoUpdated, oldVideoChannel, t)
|
||||||
await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
|
await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ const tokenRouter = express.Router()
|
||||||
|
|
||||||
tokenRouter.post('/:id/token',
|
tokenRouter.post('/:id/token',
|
||||||
optionalAuthenticate,
|
optionalAuthenticate,
|
||||||
asyncMiddleware(videosCustomGetValidator('only-video')),
|
asyncMiddleware(videosCustomGetValidator('only-video-and-blacklist')),
|
||||||
videoFileTokenValidator,
|
videoFileTokenValidator,
|
||||||
generateToken
|
generateToken
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import express, { UploadFiles } from 'express'
|
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||||
import { HttpStatusCode, ThumbnailType, VideoPrivacy, VideoPrivacyType, VideoUpdate } from '@peertube/peertube-models'
|
import { HttpStatusCode, ThumbnailType, VideoPrivacy, VideoPrivacyType, VideoUpdate } from '@peertube/peertube-models'
|
||||||
import { exists } from '@server/helpers/custom-validators/misc.js'
|
import { exists } from '@server/helpers/custom-validators/misc.js'
|
||||||
import { changeVideoChannelShare } from '@server/lib/activitypub/share.js'
|
import { changeVideoChannelShare } from '@server/lib/activitypub/share.js'
|
||||||
|
import { isNewVideoPrivacyForFederation, isPrivacyForFederation } from '@server/lib/activitypub/videos/federate.js'
|
||||||
|
import { updateLocalVideoMiniatureFromExisting } from '@server/lib/thumbnail.js'
|
||||||
|
import { replaceChaptersFromDescriptionIfNeeded } from '@server/lib/video-chapters.js'
|
||||||
|
import { addVideoJobsAfterUpdate } from '@server/lib/video-jobs.js'
|
||||||
import { VideoPathManager } from '@server/lib/video-path-manager.js'
|
import { VideoPathManager } from '@server/lib/video-path-manager.js'
|
||||||
import { setVideoPrivacy } from '@server/lib/video-privacy.js'
|
import { setVideoPrivacy } from '@server/lib/video-privacy.js'
|
||||||
import { setVideoTags } from '@server/lib/video.js'
|
import { setVideoTags } from '@server/lib/video.js'
|
||||||
|
@ -11,7 +13,9 @@ import { openapiOperationDoc } from '@server/middlewares/doc.js'
|
||||||
import { VideoPasswordModel } from '@server/models/video/video-password.js'
|
import { VideoPasswordModel } from '@server/models/video/video-password.js'
|
||||||
import { FilteredModelAttributes } from '@server/types/index.js'
|
import { FilteredModelAttributes } from '@server/types/index.js'
|
||||||
import { MVideoFullLight, MVideoThumbnail } from '@server/types/models/index.js'
|
import { MVideoFullLight, MVideoThumbnail } from '@server/types/models/index.js'
|
||||||
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger.js'
|
import express, { UploadFiles } from 'express'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
|
import { VideoAuditView, auditLoggerFactory, getAuditIdFromRes } from '../../../helpers/audit-logger.js'
|
||||||
import { resetSequelizeInstance } from '../../../helpers/database-utils.js'
|
import { resetSequelizeInstance } from '../../../helpers/database-utils.js'
|
||||||
import { createReqFiles } from '../../../helpers/express-utils.js'
|
import { createReqFiles } from '../../../helpers/express-utils.js'
|
||||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||||
|
@ -22,9 +26,6 @@ import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist.js'
|
||||||
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares/index.js'
|
import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videosUpdateValidator } from '../../../middlewares/index.js'
|
||||||
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update.js'
|
import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update.js'
|
||||||
import { VideoModel } from '../../../models/video/video.js'
|
import { VideoModel } from '../../../models/video/video.js'
|
||||||
import { replaceChaptersFromDescriptionIfNeeded } from '@server/lib/video-chapters.js'
|
|
||||||
import { addVideoJobsAfterUpdate } from '@server/lib/video-jobs.js'
|
|
||||||
import { updateLocalVideoMiniatureFromExisting } from '@server/lib/thumbnail.js'
|
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('api', 'video')
|
const lTags = loggerTagsFactory('api', 'video')
|
||||||
const auditLogger = auditLoggerFactory('videos')
|
const auditLogger = auditLoggerFactory('videos')
|
||||||
|
@ -53,7 +54,7 @@ async function updateVideo (req: express.Request, res: express.Response) {
|
||||||
const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON())
|
const oldVideoAuditView = new VideoAuditView(videoFromReq.toFormattedDetailsJSON())
|
||||||
const videoInfoToUpdate: VideoUpdate = req.body
|
const videoInfoToUpdate: VideoUpdate = req.body
|
||||||
|
|
||||||
const hadPrivacyForFederation = videoFromReq.hasPrivacyForFederation()
|
const hadPrivacyForFederation = isPrivacyForFederation(videoFromReq.privacy)
|
||||||
const oldPrivacy = videoFromReq.privacy
|
const oldPrivacy = videoFromReq.privacy
|
||||||
|
|
||||||
const thumbnails = await buildVideoThumbnailsFromReq(videoFromReq, req.files)
|
const thumbnails = await buildVideoThumbnailsFromReq(videoFromReq, req.files)
|
||||||
|
@ -191,7 +192,7 @@ async function updateVideoPrivacy (options: {
|
||||||
transaction: Transaction
|
transaction: Transaction
|
||||||
}) {
|
}) {
|
||||||
const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
|
const { videoInstance, videoInfoToUpdate, hadPrivacyForFederation, transaction } = options
|
||||||
const isNewVideoForFederation = videoInstance.isNewVideoForFederation(videoInfoToUpdate.privacy)
|
const isNewVideoForFederation = isNewVideoPrivacyForFederation(videoInstance.privacy, videoInfoToUpdate.privacy)
|
||||||
|
|
||||||
const newPrivacy = forceNumber(videoInfoToUpdate.privacy) as VideoPrivacyType
|
const newPrivacy = forceNumber(videoInfoToUpdate.privacy) as VideoPrivacyType
|
||||||
setVideoPrivacy(videoInstance, newPrivacy)
|
setVideoPrivacy(videoInstance, newPrivacy)
|
||||||
|
@ -207,7 +208,7 @@ async function updateVideoPrivacy (options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfederate the video if the new privacy is not compatible with federation
|
// Unfederate the video if the new privacy is not compatible with federation
|
||||||
if (hadPrivacyForFederation && !videoInstance.hasPrivacyForFederation()) {
|
if (hadPrivacyForFederation && !isPrivacyForFederation(videoInstance.privacy)) {
|
||||||
await VideoModel.sendDelete(videoInstance, { transaction })
|
await VideoModel.sendDelete(videoInstance, { transaction })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { ContextType } from '@peertube/peertube-models'
|
import { ContextType } from '@peertube/peertube-models'
|
||||||
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
|
import { ACTIVITY_PUB, REMOTE_SCHEME } from '@server/initializers/constants.js'
|
||||||
|
import { isArray } from './custom-validators/misc.js'
|
||||||
import { buildDigest } from './peertube-crypto.js'
|
import { buildDigest } from './peertube-crypto.js'
|
||||||
import type { signJsonLDObject } from './peertube-jsonld.js'
|
import type { signJsonLDObject } from './peertube-jsonld.js'
|
||||||
import { doJSONRequest } from './requests.js'
|
import { doJSONRequest } from './requests.js'
|
||||||
import { isArray } from './custom-validators/misc.js'
|
|
||||||
|
|
||||||
export type ContextFilter = <T> (arg: T) => Promise<T>
|
export type ContextFilter = <T> (arg: T) => Promise<T>
|
||||||
|
|
||||||
|
@ -49,6 +49,18 @@ export async function getApplicationActorOfHost (host: string) {
|
||||||
return found?.href || undefined
|
return found?.href || undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAPPublicValue () {
|
||||||
|
return 'https://www.w3.org/ns/activitystreams#Public'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasAPPublic (toOrCC: string[]) {
|
||||||
|
if (!isArray(toOrCC)) return false
|
||||||
|
|
||||||
|
const publicValue = getAPPublicValue()
|
||||||
|
|
||||||
|
return toOrCC.some(f => f === 'as:Public' || publicValue)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Private
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -58,7 +70,6 @@ type ContextValue = { [ id: string ]: (string | { '@type': string, '@id': string
|
||||||
const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string })[] } = {
|
const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string })[] } = {
|
||||||
Video: buildContext({
|
Video: buildContext({
|
||||||
Hashtag: 'as:Hashtag',
|
Hashtag: 'as:Hashtag',
|
||||||
uuid: 'sc:identifier',
|
|
||||||
category: 'sc:category',
|
category: 'sc:category',
|
||||||
licence: 'sc:license',
|
licence: 'sc:license',
|
||||||
subtitleLanguage: 'sc:subtitleLanguage',
|
subtitleLanguage: 'sc:subtitleLanguage',
|
||||||
|
@ -99,6 +110,11 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
'@id': 'pt:aspectRatio'
|
'@id': 'pt:aspectRatio'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
uuid: {
|
||||||
|
'@type': 'sc:identifier',
|
||||||
|
'@id': 'pt:uuid'
|
||||||
|
},
|
||||||
|
|
||||||
originallyPublishedAt: 'sc:datePublished',
|
originallyPublishedAt: 'sc:datePublished',
|
||||||
|
|
||||||
uploadDate: 'sc:uploadDate',
|
uploadDate: 'sc:uploadDate',
|
||||||
|
@ -170,12 +186,23 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:Number',
|
||||||
'@id': 'pt:stopTimestamp'
|
'@id': 'pt:stopTimestamp'
|
||||||
},
|
},
|
||||||
uuid: 'sc:identifier'
|
uuid: {
|
||||||
|
'@type': 'sc:identifier',
|
||||||
|
'@id': 'pt:uuid'
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
CacheFile: buildContext({
|
CacheFile: buildContext({
|
||||||
expires: 'sc:expires',
|
expires: 'sc:expires',
|
||||||
CacheFile: 'pt:CacheFile'
|
CacheFile: 'pt:CacheFile',
|
||||||
|
size: {
|
||||||
|
'@type': 'sc:Number',
|
||||||
|
'@id': 'pt:size'
|
||||||
|
},
|
||||||
|
fps: {
|
||||||
|
'@type': 'sc:Number',
|
||||||
|
'@id': 'pt:fps'
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
Flag: buildContext({
|
Flag: buildContext({
|
||||||
|
@ -205,15 +232,21 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:Number',
|
||||||
'@id': 'pt:startTimestamp'
|
'@id': 'pt:startTimestamp'
|
||||||
},
|
},
|
||||||
stopTimestamp: {
|
endTimestamp: {
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:Number',
|
||||||
'@id': 'pt:stopTimestamp'
|
'@id': 'pt:endTimestamp'
|
||||||
},
|
},
|
||||||
watchSection: {
|
uuid: {
|
||||||
'@type': 'sc:Number',
|
'@type': 'sc:identifier',
|
||||||
'@id': 'pt:stopTimestamp'
|
'@id': 'pt:uuid'
|
||||||
},
|
},
|
||||||
uuid: 'sc:identifier'
|
actionStatus: 'sc:actionStatus',
|
||||||
|
watchSections: {
|
||||||
|
'@type': '@id',
|
||||||
|
'@id': 'pt:watchSections'
|
||||||
|
},
|
||||||
|
addressRegion: 'sc:addressRegion',
|
||||||
|
addressCountry: 'sc:addressCountry'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
View: buildContext({
|
View: buildContext({
|
||||||
|
@ -233,13 +266,46 @@ const contextStore: { [ id in ContextType ]: (string | { [ id: string ]: string
|
||||||
Rate: buildContext(),
|
Rate: buildContext(),
|
||||||
|
|
||||||
Chapters: buildContext({
|
Chapters: buildContext({
|
||||||
name: 'sc:name',
|
|
||||||
hasPart: 'sc:hasPart',
|
hasPart: 'sc:hasPart',
|
||||||
endOffset: 'sc:endOffset',
|
endOffset: 'sc:endOffset',
|
||||||
startOffset: 'sc:startOffset'
|
startOffset: 'sc:startOffset'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let allContext: (string | ContextValue)[]
|
||||||
|
export function getAllContext () {
|
||||||
|
if (allContext) return allContext
|
||||||
|
|
||||||
|
const processed = new Set<string>()
|
||||||
|
allContext = []
|
||||||
|
|
||||||
|
let staticContext: ContextValue = {}
|
||||||
|
|
||||||
|
for (const v of Object.values(contextStore)) {
|
||||||
|
for (const item of v) {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
if (!processed.has(item)) {
|
||||||
|
allContext.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
processed.add(item)
|
||||||
|
} else {
|
||||||
|
for (const subKey of Object.keys(item)) {
|
||||||
|
if (!processed.has(subKey)) {
|
||||||
|
staticContext = { ...staticContext, [subKey]: item[subKey] }
|
||||||
|
}
|
||||||
|
|
||||||
|
processed.add(subKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allContext = [ ...allContext, staticContext ]
|
||||||
|
|
||||||
|
return allContext
|
||||||
|
}
|
||||||
|
|
||||||
async function getContextData (type: ContextType, contextFilter: ContextFilter) {
|
async function getContextData (type: ContextType, contextFilter: ContextFilter) {
|
||||||
const contextData = contextFilter
|
const contextData = contextFilter
|
||||||
? await contextFilter(contextStore[type])
|
? await contextFilter(contextStore[type])
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import jsonld from 'jsonld'
|
import jsonld from 'jsonld'
|
||||||
|
|
||||||
const CACHE = {
|
const STATIC_CACHE = {
|
||||||
'https://w3id.org/security/v1': {
|
'https://w3id.org/security/v1': {
|
||||||
'@context': {
|
'@context': {
|
||||||
id: '@id',
|
id: '@id',
|
||||||
|
@ -53,19 +53,29 @@ const CACHE = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localCache = new Map<string, any>()
|
||||||
|
|
||||||
const nodeDocumentLoader = (jsonld as any).documentLoaders.node();
|
const nodeDocumentLoader = (jsonld as any).documentLoaders.node();
|
||||||
|
|
||||||
/* eslint-disable no-import-assign */
|
/* eslint-disable no-import-assign */
|
||||||
(jsonld as any).documentLoader = (url) => {
|
(jsonld as any).documentLoader = async (url: string) => {
|
||||||
if (url in CACHE) {
|
if (url in STATIC_CACHE) {
|
||||||
return Promise.resolve({
|
return {
|
||||||
contextUrl: null,
|
contextUrl: null,
|
||||||
document: CACHE[url],
|
document: STATIC_CACHE[url],
|
||||||
documentUrl: url
|
documentUrl: url
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nodeDocumentLoader(url)
|
if (localCache.has(url)) return localCache.get(url)
|
||||||
|
|
||||||
|
const remoteDoc = await nodeDocumentLoader(url)
|
||||||
|
|
||||||
|
if (localCache.size < 100) {
|
||||||
|
localCache.set(url, remoteDoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteDoc
|
||||||
}
|
}
|
||||||
|
|
||||||
export { jsonld }
|
export { jsonld }
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
import { CacheFileObject } from '@peertube/peertube-models'
|
import { CacheFileObject } from '@peertube/peertube-models'
|
||||||
import { exists, isDateValid } from '../misc.js'
|
import { MIMETYPES } from '@server/initializers/constants.js'
|
||||||
|
import validator from 'validator'
|
||||||
|
import { isDateValid } from '../misc.js'
|
||||||
import { isActivityPubUrlValid } from './misc.js'
|
import { isActivityPubUrlValid } from './misc.js'
|
||||||
import { isRemoteVideoUrlValid } from './videos.js'
|
|
||||||
|
|
||||||
function isCacheFileObjectValid (object: CacheFileObject) {
|
export function isCacheFileObjectValid (object: CacheFileObject) {
|
||||||
return exists(object) &&
|
if (!object || object.type !== 'CacheFile') return false
|
||||||
object.type === 'CacheFile' &&
|
|
||||||
(object.expires === null || isDateValid(object.expires)) &&
|
return (!object.expires || isDateValid(object.expires)) &&
|
||||||
isActivityPubUrlValid(object.object) &&
|
isActivityPubUrlValid(object.object) &&
|
||||||
(isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
|
(isRedundancyUrlVideoValid(object.url) || isPlaylistRedundancyUrlValid(object.url))
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
isCacheFileObjectValid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -24,3 +19,15 @@ function isPlaylistRedundancyUrlValid (url: any) {
|
||||||
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
|
(url.mediaType || url.mimeType) === 'application/x-mpegURL' &&
|
||||||
isActivityPubUrlValid(url.href)
|
isActivityPubUrlValid(url.href)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, use isRemoteVideoUrlValid instead in 7.0
|
||||||
|
function isRedundancyUrlVideoValid (url: any) {
|
||||||
|
const size = url.size || url['_:size']
|
||||||
|
const fps = url.fps || url['_fps']
|
||||||
|
|
||||||
|
return MIMETYPES.AP_VIDEO.MIMETYPE_EXT[url.mediaType] &&
|
||||||
|
isActivityPubUrlValid(url.href) &&
|
||||||
|
validator.default.isInt(url.height + '', { min: 0 }) &&
|
||||||
|
validator.default.isInt(size + '', { min: 0 }) &&
|
||||||
|
(!fps || validator.default.isInt(fps + '', { min: -1 }))
|
||||||
|
}
|
||||||
|
|
|
@ -1,29 +1,25 @@
|
||||||
import validator from 'validator'
|
|
||||||
import { PlaylistElementObject, PlaylistObject } from '@peertube/peertube-models'
|
import { PlaylistElementObject, PlaylistObject } from '@peertube/peertube-models'
|
||||||
|
import validator from 'validator'
|
||||||
import { exists, isDateValid, isUUIDValid } from '../misc.js'
|
import { exists, isDateValid, isUUIDValid } from '../misc.js'
|
||||||
import { isVideoPlaylistNameValid } from '../video-playlists.js'
|
import { isVideoPlaylistNameValid } from '../video-playlists.js'
|
||||||
import { isActivityPubUrlValid } from './misc.js'
|
import { isActivityPubUrlValid } from './misc.js'
|
||||||
|
|
||||||
function isPlaylistObjectValid (object: PlaylistObject) {
|
export function isPlaylistObjectValid (object: PlaylistObject) {
|
||||||
return exists(object) &&
|
if (!object || object.type !== 'Playlist') return false
|
||||||
object.type === 'Playlist' &&
|
|
||||||
validator.default.isInt(object.totalItems + '') &&
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (!object.uuid && object['identifier']) object.uuid = object['identifier']
|
||||||
|
|
||||||
|
return validator.default.isInt(object.totalItems + '') &&
|
||||||
isVideoPlaylistNameValid(object.name) &&
|
isVideoPlaylistNameValid(object.name) &&
|
||||||
isUUIDValid(object.uuid) &&
|
isUUIDValid(object.uuid) &&
|
||||||
isDateValid(object.published) &&
|
isDateValid(object.published) &&
|
||||||
isDateValid(object.updated)
|
isDateValid(object.updated)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPlaylistElementObjectValid (object: PlaylistElementObject) {
|
export function isPlaylistElementObjectValid (object: PlaylistElementObject) {
|
||||||
return exists(object) &&
|
return exists(object) &&
|
||||||
object.type === 'PlaylistElement' &&
|
object.type === 'PlaylistElement' &&
|
||||||
validator.default.isInt(object.position + '') &&
|
validator.default.isInt(object.position + '') &&
|
||||||
isActivityPubUrlValid(object.url)
|
isActivityPubUrlValid(object.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
isPlaylistObjectValid,
|
|
||||||
isPlaylistElementObjectValid
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
|
||||||
import validator from 'validator'
|
import validator from 'validator'
|
||||||
import { ACTIVITY_PUB } from '../../../initializers/constants.js'
|
|
||||||
import { exists, isArray, isDateValid } from '../misc.js'
|
import { exists, isArray, isDateValid } from '../misc.js'
|
||||||
import { isActivityPubUrlValid } from './misc.js'
|
import { isActivityPubUrlValid } from './misc.js'
|
||||||
|
|
||||||
|
@ -23,10 +23,7 @@ function sanitizeAndCheckVideoCommentObject (comment: any) {
|
||||||
isDateValid(comment.published) &&
|
isDateValid(comment.published) &&
|
||||||
isActivityPubUrlValid(comment.url) &&
|
isActivityPubUrlValid(comment.url) &&
|
||||||
isArray(comment.to) &&
|
isArray(comment.to) &&
|
||||||
(
|
(hasAPPublic(comment.to) || hasAPPublic(comment.cc)) // Only accept public comments
|
||||||
comment.to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ||
|
|
||||||
comment.cc.indexOf(ACTIVITY_PUB.PUBLIC) !== -1
|
|
||||||
) // Only accept public comments
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
|
@ -27,7 +27,7 @@ function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) {
|
||||||
sanitizeAndCheckVideoTorrentObject(activity.object)
|
sanitizeAndCheckVideoTorrentObject(activity.object)
|
||||||
}
|
}
|
||||||
|
|
||||||
function sanitizeAndCheckVideoTorrentObject (video: any) {
|
function sanitizeAndCheckVideoTorrentObject (video: VideoObject) {
|
||||||
if (!video || video.type !== 'Video') return false
|
if (!video || video.type !== 'Video') return false
|
||||||
|
|
||||||
if (!setValidRemoteTags(video)) {
|
if (!setValidRemoteTags(video)) {
|
||||||
|
@ -59,6 +59,9 @@ function sanitizeAndCheckVideoTorrentObject (video: any) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (!video.uuid && video['identifier']) video.uuid = video['identifier']
|
||||||
|
|
||||||
// Default attributes
|
// Default attributes
|
||||||
if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
|
if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED
|
||||||
if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
|
if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
|
import { arrayify } from '@peertube/peertube-core-utils'
|
||||||
import { WatchActionObject } from '@peertube/peertube-models'
|
import { WatchActionObject } from '@peertube/peertube-models'
|
||||||
import { exists, isDateValid, isUUIDValid } from '../misc.js'
|
import { isDateValid, isUUIDValid } from '../misc.js'
|
||||||
import { isVideoTimeValid } from '../video-view.js'
|
import { isVideoTimeValid } from '../video-view.js'
|
||||||
import { isActivityPubVideoDurationValid, isObjectValid } from './misc.js'
|
import { isActivityPubVideoDurationValid, isObjectValid } from './misc.js'
|
||||||
|
|
||||||
function isWatchActionObjectValid (action: WatchActionObject) {
|
function isWatchActionObjectValid (action: WatchActionObject) {
|
||||||
return exists(action) &&
|
if (!action || action.type !== 'WatchAction') return false
|
||||||
action.type === 'WatchAction' &&
|
|
||||||
isObjectValid(action.id) &&
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (!action.uuid && action['identifier']) action.uuid = action['identifier']
|
||||||
|
|
||||||
|
if (action['_:actionStatus'] && !action.actionStatus) action.actionStatus = action['_:actionStatus']
|
||||||
|
if (action['_:watchSections'] && !action.watchSections) action.watchSections = arrayify(action['_:watchSections'])
|
||||||
|
|
||||||
|
return isObjectValid(action.id) &&
|
||||||
isActivityPubVideoDurationValid(action.duration) &&
|
isActivityPubVideoDurationValid(action.duration) &&
|
||||||
isDateValid(action.startTime) &&
|
isDateValid(action.startTime) &&
|
||||||
isDateValid(action.endTime) &&
|
isDateValid(action.endTime) &&
|
||||||
isLocationValid(action.location) &&
|
isLocationValid(action.location) &&
|
||||||
isUUIDValid(action.uuid) &&
|
isUUIDValid(action.uuid) &&
|
||||||
isObjectValid(action.object) &&
|
isObjectValid(action.object) &&
|
||||||
isWatchSectionsValid(action.watchSections)
|
areWatchSectionsValid(action.watchSections)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
@ -34,8 +41,11 @@ function isLocationValid (location: any) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
function isWatchSectionsValid (sections: WatchActionObject['watchSections']) {
|
function areWatchSectionsValid (sections: WatchActionObject['watchSections']) {
|
||||||
return Array.isArray(sections) && sections.every(s => {
|
return Array.isArray(sections) && sections.every(s => {
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
if (s['_:endTimestamp'] && !s.endTimestamp) s.endTimestamp = s['_:endTimestamp']
|
||||||
|
|
||||||
return isVideoTimeValid(s.startTimestamp) && isVideoTimeValid(s.endTimestamp)
|
return isVideoTimeValid(s.startTimestamp) && isVideoTimeValid(s.endTimestamp)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ export function areVideoTagsValid (tags: string[]) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isVideoViewsValid (value: string) {
|
export function isVideoViewsValid (value: string | number) {
|
||||||
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
|
return exists(value) && validator.default.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.VIEWS)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,51 @@
|
||||||
|
import { omit } from '@peertube/peertube-core-utils'
|
||||||
import { sha256 } from '@peertube/peertube-node-utils'
|
import { sha256 } from '@peertube/peertube-node-utils'
|
||||||
import { createSign, createVerify } from 'crypto'
|
import { createSign, createVerify } from 'crypto'
|
||||||
import cloneDeep from 'lodash-es/cloneDeep.js'
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
||||||
import { MActor } from '../types/models/index.js'
|
import { MActor } from '../types/models/index.js'
|
||||||
|
import { getAllContext } from './activity-pub-utils.js'
|
||||||
|
import { jsonld } from './custom-jsonld-signature.js'
|
||||||
|
import { isArray } from './custom-validators/misc.js'
|
||||||
import { logger } from './logger.js'
|
import { logger } from './logger.js'
|
||||||
import { assertIsInWorkerThread } from './threads.js'
|
import { assertIsInWorkerThread } from './threads.js'
|
||||||
import { jsonld } from './custom-jsonld-signature.js'
|
|
||||||
|
|
||||||
export function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
|
type ExpressRequest = { body: any }
|
||||||
if (signedDocument.signature.type === 'RsaSignature2017') {
|
|
||||||
return isJsonLDRSA2017Verified(fromActor, signedDocument)
|
export function compactJSONLDAndCheckSignature (fromActor: MActor, req: ExpressRequest): Promise<boolean> {
|
||||||
|
if (req.body.signature.type === 'RsaSignature2017') {
|
||||||
|
return compactJSONLDAndCheckRSA2017Signature(fromActor, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
|
logger.warn('Unknown JSON LD signature %s.', req.body.signature.type, req.body)
|
||||||
|
|
||||||
return Promise.resolve(false)
|
return Promise.resolve(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backward compatibility with "other" implementations
|
// Backward compatibility with "other" implementations
|
||||||
export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
|
export async function compactJSONLDAndCheckRSA2017Signature (fromActor: MActor, req: ExpressRequest) {
|
||||||
|
const compacted = await jsonldCompact(omit(req.body, [ 'signature' ]))
|
||||||
|
|
||||||
|
fixCompacted(req.body, compacted)
|
||||||
|
|
||||||
|
req.body = { ...compacted, signature: req.body.signature }
|
||||||
|
|
||||||
|
if (compacted['@include']) {
|
||||||
|
logger.warn('JSON-LD @include is not supported')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
let safe = true
|
||||||
|
if (
|
||||||
|
(compacted.type === 'Create' && (compacted?.object?.type === 'WatchAction' || compacted?.object?.type === 'CacheFile')) ||
|
||||||
|
(compacted.type === 'Undo' && compacted?.object?.type === 'Create' && compacted?.object?.object.type === 'CacheFile')
|
||||||
|
) {
|
||||||
|
safe = false
|
||||||
|
}
|
||||||
|
|
||||||
const [ documentHash, optionsHash ] = await Promise.all([
|
const [ documentHash, optionsHash ] = await Promise.all([
|
||||||
createDocWithoutSignatureHash(signedDocument),
|
hashObject(compacted, safe),
|
||||||
createSignatureHash(signedDocument.signature)
|
createSignatureHash(req.body.signature, safe)
|
||||||
])
|
])
|
||||||
|
|
||||||
const toVerify = optionsHash + documentHash
|
const toVerify = optionsHash + documentHash
|
||||||
|
@ -28,7 +53,39 @@ export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument
|
||||||
const verify = createVerify('RSA-SHA256')
|
const verify = createVerify('RSA-SHA256')
|
||||||
verify.update(toVerify, 'utf8')
|
verify.update(toVerify, 'utf8')
|
||||||
|
|
||||||
return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
|
return verify.verify(fromActor.publicKey, req.body.signature.signatureValue, 'base64')
|
||||||
|
}
|
||||||
|
|
||||||
|
function fixCompacted (original: any, compacted: any) {
|
||||||
|
if (!original || !compacted) return
|
||||||
|
|
||||||
|
for (const [ k, v ] of Object.entries(original)) {
|
||||||
|
if (k === '@context' || k === 'signature') continue
|
||||||
|
if (v === undefined || v === null) continue
|
||||||
|
|
||||||
|
const cv = compacted[k]
|
||||||
|
if (cv === undefined || cv === null) continue
|
||||||
|
|
||||||
|
if (typeof v === 'string') {
|
||||||
|
if (v === 'https://www.w3.org/ns/activitystreams#Public' && cv === 'as:Public') {
|
||||||
|
compacted[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isArray(v) && !isArray(cv)) {
|
||||||
|
compacted[k] = [ cv ]
|
||||||
|
|
||||||
|
for (let i = 0; i < v.length; i++) {
|
||||||
|
if (v[i] === 'https://www.w3.org/ns/activitystreams#Public' && cv[i] === 'as:Public') {
|
||||||
|
compacted[k][i] = v[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === 'object') {
|
||||||
|
fixCompacted(original[k], compacted[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function signJsonLDObject <T> (options: {
|
export async function signJsonLDObject <T> (options: {
|
||||||
|
@ -66,35 +123,40 @@ export async function signJsonLDObject <T> (options: {
|
||||||
// Private
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function hashObject (obj: any): Promise<any> {
|
async function hashObject (obj: any, safe: boolean): Promise<any> {
|
||||||
const res = await (jsonld as any).promises.normalize(obj, {
|
const res = await jsonldNormalize(obj, safe)
|
||||||
safe: false,
|
|
||||||
algorithm: 'URDNA2015',
|
|
||||||
format: 'application/n-quads'
|
|
||||||
})
|
|
||||||
|
|
||||||
return sha256(res)
|
return sha256(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSignatureHash (signature: any) {
|
function jsonldCompact (obj: any) {
|
||||||
const signatureCopy = cloneDeep(signature)
|
return (jsonld as any).promises.compact(obj, getAllContext())
|
||||||
Object.assign(signatureCopy, {
|
}
|
||||||
|
|
||||||
|
function jsonldNormalize (obj: any, safe: boolean) {
|
||||||
|
return (jsonld as any).promises.normalize(obj, {
|
||||||
|
safe,
|
||||||
|
algorithm: 'URDNA2015',
|
||||||
|
format: 'application/n-quads'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function createSignatureHash (signature: any, safe = true) {
|
||||||
|
return hashObject({
|
||||||
'@context': [
|
'@context': [
|
||||||
'https://w3id.org/security/v1',
|
'https://w3id.org/security/v1',
|
||||||
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
|
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
|
||||||
]
|
],
|
||||||
})
|
|
||||||
|
|
||||||
delete signatureCopy.type
|
...omit(signature, [ 'type', 'id', 'signatureValue' ])
|
||||||
delete signatureCopy.id
|
}, safe)
|
||||||
delete signatureCopy.signatureValue
|
|
||||||
|
|
||||||
return hashObject(signatureCopy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDocWithoutSignatureHash (doc: any) {
|
function createDocWithoutSignatureHash (doc: any) {
|
||||||
const docWithoutSignature = cloneDeep(doc)
|
const docWithoutSignature = cloneDeep(doc)
|
||||||
delete docWithoutSignature.signature
|
delete docWithoutSignature.signature
|
||||||
|
|
||||||
return hashObject(docWithoutSignature)
|
return hashObject(docWithoutSignature, true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +1,28 @@
|
||||||
import { Response } from 'express'
|
import { VideoPrivacy } from '@peertube/peertube-models'
|
||||||
import { forceNumber } from '@peertube/peertube-core-utils'
|
|
||||||
import { VideoPrivacy, VideoPrivacyType, VideoState, VideoStateType } from '@peertube/peertube-models'
|
|
||||||
import { CONFIG } from '@server/initializers/config.js'
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models/index.js'
|
import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/types/models/index.js'
|
||||||
|
import { Response } from 'express'
|
||||||
|
|
||||||
function getVideoWithAttributes (res: Response) {
|
export function getVideoWithAttributes (res: Response) {
|
||||||
return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
|
return res.locals.videoAPI || res.locals.videoAll || res.locals.onlyVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
|
export function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) {
|
||||||
return isStreamingPlaylist(videoOrPlaylist)
|
return isStreamingPlaylist(videoOrPlaylist)
|
||||||
? videoOrPlaylist.Video
|
? videoOrPlaylist.Video
|
||||||
: videoOrPlaylist
|
: videoOrPlaylist
|
||||||
}
|
}
|
||||||
|
|
||||||
function isPrivacyForFederation (privacy: VideoPrivacyType) {
|
export function getPrivaciesForFederation () {
|
||||||
const castedPrivacy = forceNumber(privacy)
|
|
||||||
|
|
||||||
return castedPrivacy === VideoPrivacy.PUBLIC ||
|
|
||||||
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isStateForFederation (state: VideoStateType) {
|
|
||||||
const castedState = forceNumber(state)
|
|
||||||
|
|
||||||
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrivaciesForFederation () {
|
|
||||||
return (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true)
|
return (CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true)
|
||||||
? [ { privacy: VideoPrivacy.PUBLIC }, { privacy: VideoPrivacy.UNLISTED } ]
|
? [ { privacy: VideoPrivacy.PUBLIC }, { privacy: VideoPrivacy.UNLISTED } ]
|
||||||
: [ { privacy: VideoPrivacy.PUBLIC } ]
|
: [ { privacy: VideoPrivacy.PUBLIC } ]
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExtFromMimetype (mimeTypes: { [id: string]: string | string[] }, mimeType: string) {
|
export function getExtFromMimetype (mimeTypes: { [id: string]: string | string[] }, mimeType: string) {
|
||||||
const value = mimeTypes[mimeType]
|
const value = mimeTypes[mimeType]
|
||||||
|
|
||||||
if (Array.isArray(value)) return value[0]
|
if (Array.isArray(value)) return value[0]
|
||||||
|
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
getVideoWithAttributes,
|
|
||||||
extractVideo,
|
|
||||||
getExtFromMimetype,
|
|
||||||
isStateForFederation,
|
|
||||||
isPrivacyForFederation,
|
|
||||||
getPrivaciesForFederation
|
|
||||||
}
|
|
||||||
|
|
|
@ -774,7 +774,6 @@ const ACTIVITY_PUB = {
|
||||||
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
|
||||||
],
|
],
|
||||||
ACCEPT_HEADER: 'application/activity+json, application/ld+json',
|
ACCEPT_HEADER: 'application/activity+json, application/ld+json',
|
||||||
PUBLIC: 'https://www.w3.org/ns/activitystreams#Public',
|
|
||||||
COLLECTION_ITEMS_PER_PAGE: 10,
|
COLLECTION_ITEMS_PER_PAGE: 10,
|
||||||
FETCH_PAGE_LIMIT: 2000,
|
FETCH_PAGE_LIMIT: 2000,
|
||||||
MAX_RECURSION_COMMENTS: 100,
|
MAX_RECURSION_COMMENTS: 100,
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import { ActivityAudience } from '@peertube/peertube-models'
|
import { ActivityAudience } from '@peertube/peertube-models'
|
||||||
import { ACTIVITY_PUB } from '../../initializers/constants.js'
|
import { getAPPublicValue } from '@server/helpers/activity-pub-utils.js'
|
||||||
import { MActorFollowersUrl } from '../../types/models/index.js'
|
import { MActorFollowersUrl } from '../../types/models/index.js'
|
||||||
|
|
||||||
function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
|
export function getAudience (actorSender: MActorFollowersUrl, isPublic = true) {
|
||||||
return buildAudience([ actorSender.followersUrl ], isPublic)
|
return buildAudience([ actorSender.followersUrl ], isPublic)
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAudience (followerUrls: string[], isPublic = true) {
|
export function buildAudience (followerUrls: string[], isPublic = true) {
|
||||||
let to: string[] = []
|
let to: string[] = []
|
||||||
let cc: string[] = []
|
let cc: string[] = []
|
||||||
|
|
||||||
if (isPublic) {
|
if (isPublic) {
|
||||||
to = [ ACTIVITY_PUB.PUBLIC ]
|
to = [ getAPPublicValue() ]
|
||||||
cc = followerUrls
|
cc = followerUrls
|
||||||
} else { // Unlisted
|
} else { // Unlisted
|
||||||
to = []
|
to = []
|
||||||
|
@ -21,14 +21,6 @@ function buildAudience (followerUrls: string[], isPublic = true) {
|
||||||
return { to, cc }
|
return { to, cc }
|
||||||
}
|
}
|
||||||
|
|
||||||
function audiencify<T> (object: T, audience: ActivityAudience) {
|
export function audiencify<T> (object: T, audience: ActivityAudience) {
|
||||||
return { ...audience, ...object }
|
return { ...audience, ...object }
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
buildAudience,
|
|
||||||
getAudience,
|
|
||||||
audiencify
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Transaction } from 'sequelize'
|
||||||
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
|
import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/types/models/index.js'
|
||||||
import { CacheFileObject, VideoStreamingPlaylistType } from '@peertube/peertube-models'
|
import { CacheFileObject, VideoStreamingPlaylistType } from '@peertube/peertube-models'
|
||||||
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
|
import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy.js'
|
||||||
|
import { exists } from '@server/helpers/custom-validators/misc.js'
|
||||||
|
|
||||||
async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
|
async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) {
|
||||||
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
|
const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t)
|
||||||
|
@ -65,11 +66,15 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = cacheFileObject.url
|
const url = cacheFileObject.url
|
||||||
|
const urlFPS = exists(url.fps) // TODO: compat with < 6.1, remove in 7.0
|
||||||
|
? url.fps
|
||||||
|
: url['_:fps']
|
||||||
|
|
||||||
const videoFile = video.VideoFiles.find(f => {
|
const videoFile = video.VideoFiles.find(f => {
|
||||||
return f.resolution === url.height && f.fps === url.fps
|
return f.resolution === url.height && f.fps === urlFPS
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`)
|
if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${urlFPS} of video ${video.url}`)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
|
expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Activity } from '@peertube/peertube-models'
|
||||||
import { StatsManager } from '../stat-manager.js'
|
import { StatsManager } from '../stat-manager.js'
|
||||||
import { processActivities } from './process/index.js'
|
import { processActivities } from './process/index.js'
|
||||||
|
|
||||||
class InboxManager {
|
export class InboxManager {
|
||||||
|
|
||||||
private static instance: InboxManager
|
private static instance: InboxManager
|
||||||
private readonly inboxQueue: PQueue
|
private readonly inboxQueue: PQueue
|
||||||
|
@ -39,9 +39,3 @@ class InboxManager {
|
||||||
return this.instance || (this.instance = new this())
|
return this.instance || (this.instance = new this())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
InboxManager
|
|
||||||
}
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ async function buildElementsDBAttributes (elementUrls: string[], playlist: MVide
|
||||||
try {
|
try {
|
||||||
const { elementObject } = await fetchRemotePlaylistElement(elementUrl)
|
const { elementObject } = await fetchRemotePlaylistElement(elementUrl)
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: { id: elementObject.url }, fetchType: 'only-video' })
|
const { video } = await getOrCreateAPVideo({ videoObject: { id: elementObject.url }, fetchType: 'only-video-and-blacklist' })
|
||||||
|
|
||||||
elementsToCreate.push(playlistElementObjectToDBAttributes(elementObject, playlist, video))
|
elementsToCreate.push(playlistElementObjectToDBAttributes(elementObject, playlist, video))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { ACTIVITY_PUB } from '@server/initializers/constants.js'
|
|
||||||
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
|
|
||||||
import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element.js'
|
|
||||||
import { MVideoId, MVideoPlaylistId } from '@server/types/models/index.js'
|
|
||||||
import { AttributesOnly } from '@peertube/peertube-typescript-utils'
|
|
||||||
import { PlaylistElementObject, PlaylistObject, VideoPlaylistPrivacy } from '@peertube/peertube-models'
|
import { PlaylistElementObject, PlaylistObject, VideoPlaylistPrivacy } from '@peertube/peertube-models'
|
||||||
|
import { AttributesOnly } from '@peertube/peertube-typescript-utils'
|
||||||
|
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
|
||||||
|
import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element.js'
|
||||||
|
import { VideoPlaylistModel } from '@server/models/video/video-playlist.js'
|
||||||
|
import { MVideoId, MVideoPlaylistId } from '@server/types/models/index.js'
|
||||||
|
|
||||||
function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) {
|
export function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: string[]) {
|
||||||
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
|
const privacy = hasAPPublic(to)
|
||||||
? VideoPlaylistPrivacy.PUBLIC
|
? VideoPlaylistPrivacy.PUBLIC
|
||||||
: VideoPlaylistPrivacy.UNLISTED
|
: VideoPlaylistPrivacy.UNLISTED
|
||||||
|
|
||||||
|
@ -23,7 +23,11 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, to: strin
|
||||||
} as AttributesOnly<VideoPlaylistModel>
|
} as AttributesOnly<VideoPlaylistModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) {
|
export function playlistElementObjectToDBAttributes (
|
||||||
|
elementObject: PlaylistElementObject,
|
||||||
|
videoPlaylist: MVideoPlaylistId,
|
||||||
|
video: MVideoId
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
position: elementObject.position,
|
position: elementObject.position,
|
||||||
url: elementObject.id,
|
url: elementObject.id,
|
||||||
|
@ -33,8 +37,3 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje
|
||||||
videoId: video.id
|
videoId: video.id
|
||||||
} as AttributesOnly<VideoPlaylistElementModel>
|
} as AttributesOnly<VideoPlaylistElementModel>
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
playlistObjectToDBAttributes,
|
|
||||||
playlistElementObjectToDBAttributes
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { createOrUpdateLocalVideoViewer } from '../local-video-viewer.js'
|
||||||
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
||||||
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
||||||
import { resolveThread } from '../video-comments.js'
|
import { resolveThread } from '../video-comments.js'
|
||||||
import { getOrCreateAPVideo } from '../videos/index.js'
|
import { canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
|
async function processCreateActivity (options: APProcessorOptions<ActivityCreate<ActivityCreateObject>>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -87,6 +87,11 @@ async function processCreateCacheFile (
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
|
const { video } = await getOrCreateAPVideo({ videoObject: cacheFile.object })
|
||||||
|
|
||||||
|
if (video.isOwned() && !canVideoBeFederated(video)) {
|
||||||
|
logger.warn(`Do not process create cache file ${cacheFile.object} on a video that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
return createOrUpdateCacheFile(cacheFile, video, byActor, t)
|
return createOrUpdateCacheFile(cacheFile, video, byActor, t)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
|
||||||
import { ActivityDislike } from '@peertube/peertube-models'
|
import { ActivityDislike } from '@peertube/peertube-models'
|
||||||
|
import { logger } from '@server/helpers/logger.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
||||||
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
||||||
import { MActorSignature } from '../../../types/models/index.js'
|
import { MActorSignature } from '../../../types/models/index.js'
|
||||||
import { federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
import { canVideoBeFederated, federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
|
async function processDislikeActivity (options: APProcessorOptions<ActivityDislike>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -21,14 +22,19 @@ export {
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
|
async function processDislike (activity: ActivityDislike, byActor: MActorSignature) {
|
||||||
const dislikeObject = activity.object
|
const videoUrl = activity.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)
|
||||||
|
|
||||||
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: dislikeObject, fetchType: 'only-video' })
|
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video-and-blacklist' })
|
||||||
if (!onlyVideo?.isOwned()) return
|
if (!onlyVideo?.isOwned()) return
|
||||||
|
|
||||||
|
if (!canVideoBeFederated(onlyVideo)) {
|
||||||
|
logger.warn(`Do not process dislike on video ${videoUrl} that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { ActivityLike } from '@peertube/peertube-models'
|
import { ActivityLike } from '@peertube/peertube-models'
|
||||||
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
import { retryTransactionWrapper } from '../../../helpers/database-utils.js'
|
||||||
import { sequelizeTypescript } from '../../../initializers/database.js'
|
import { sequelizeTypescript } from '../../../initializers/database.js'
|
||||||
|
@ -6,7 +7,7 @@ import { getAPId } from '../../../lib/activitypub/activity.js'
|
||||||
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
import { AccountVideoRateModel } from '../../../models/account/account-video-rate.js'
|
||||||
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
import { APProcessorOptions } from '../../../types/activitypub-processor.model.js'
|
||||||
import { MActorSignature } from '../../../types/models/index.js'
|
import { MActorSignature } from '../../../types/models/index.js'
|
||||||
import { federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
import { canVideoBeFederated, federateVideoIfNeeded, maybeGetOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
|
async function processLikeActivity (options: APProcessorOptions<ActivityLike>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -28,9 +29,14 @@ async function processLikeVideo (byActor: MActorSignature, activity: ActivityLik
|
||||||
const byAccount = byActor.Account
|
const byAccount = byActor.Account
|
||||||
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
|
if (!byAccount) throw new Error('Cannot create like with the non account actor ' + byActor.url)
|
||||||
|
|
||||||
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video' })
|
const { video: onlyVideo } = await maybeGetOrCreateAPVideo({ videoObject: videoUrl, fetchType: 'only-video-and-blacklist' })
|
||||||
if (!onlyVideo?.isOwned()) return
|
if (!onlyVideo?.isOwned()) return
|
||||||
|
|
||||||
|
if (!canVideoBeFederated(onlyVideo)) {
|
||||||
|
logger.warn(`Do not process like on video ${videoUrl} that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
return sequelizeTypescript.transaction(async t => {
|
return sequelizeTypescript.transaction(async t => {
|
||||||
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
const video = await VideoModel.loadFull(onlyVideo.id, t)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { APActorUpdater } from '../actors/updater.js'
|
||||||
import { createOrUpdateCacheFile } from '../cache-file.js'
|
import { createOrUpdateCacheFile } from '../cache-file.js'
|
||||||
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
import { createOrUpdateVideoPlaylist } from '../playlists/index.js'
|
||||||
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
import { forwardVideoRelatedActivity } from '../send/shared/send-utils.js'
|
||||||
import { APVideoUpdater, getOrCreateAPVideo } from '../videos/index.js'
|
import { APVideoUpdater, canVideoBeFederated, getOrCreateAPVideo } from '../videos/index.js'
|
||||||
|
|
||||||
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
|
async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate<ActivityUpdateObject>>) {
|
||||||
const { activity, byActor } = options
|
const { activity, byActor } = options
|
||||||
|
@ -93,6 +93,11 @@ async function processUpdateCacheFile (
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
|
const { video } = await getOrCreateAPVideo({ videoObject: cacheFileObject.object })
|
||||||
|
|
||||||
|
if (video.isOwned() && !canVideoBeFederated(video)) {
|
||||||
|
logger.warn(`Do not process update cache file on video ${activity.object} that cannot be federated`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
await sequelizeTypescript.transaction(async t => {
|
await sequelizeTypescript.transaction(async t => {
|
||||||
await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
|
await createOrUpdateCacheFile(cacheFileObject, video, byActor, t)
|
||||||
})
|
})
|
||||||
|
|
|
@ -24,7 +24,7 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
|
||||||
|
|
||||||
const { video } = await getOrCreateAPVideo({
|
const { video } = await getOrCreateAPVideo({
|
||||||
videoObject,
|
videoObject,
|
||||||
fetchType: 'only-video',
|
fetchType: 'only-video-and-blacklist',
|
||||||
allowRefresh: false
|
allowRefresh: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -32,8 +32,8 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
|
||||||
video,
|
video,
|
||||||
viewerId: activity.id,
|
viewerId: activity.id,
|
||||||
|
|
||||||
viewerExpires: activity.expires
|
viewerExpires: getExpires(activity)
|
||||||
? new Date(activity.expires)
|
? new Date(getExpires(activity))
|
||||||
: undefined,
|
: undefined,
|
||||||
viewerResultCounter: getViewerResultCounter(activity)
|
viewerResultCounter: getViewerResultCounter(activity)
|
||||||
})
|
})
|
||||||
|
@ -49,10 +49,15 @@ async function processCreateView (activity: ActivityView, byActor: MActorSignatu
|
||||||
function getViewerResultCounter (activity: ActivityView) {
|
function getViewerResultCounter (activity: ActivityView) {
|
||||||
const result = activity.result
|
const result = activity.result
|
||||||
|
|
||||||
if (!activity.expires || result?.interactionType !== 'WatchAction' || result?.type !== 'InteractionCounter') return undefined
|
if (!getExpires(activity) || result?.interactionType !== 'WatchAction' || result?.type !== 'InteractionCounter') return undefined
|
||||||
|
|
||||||
const counter = parseInt(result.userInteractionCount + '')
|
const counter = parseInt(result.userInteractionCount + '')
|
||||||
if (isNaN(counter)) return undefined
|
if (isNaN(counter)) return undefined
|
||||||
|
|
||||||
return counter
|
return counter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: compat with < 6.1, remove in 7.0
|
||||||
|
function getExpires (activity: ActivityView) {
|
||||||
|
return activity.expires || activity['expiration'] as string
|
||||||
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act
|
||||||
View: processViewActivity
|
View: processViewActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processActivities (
|
export async function processActivities (
|
||||||
activities: Activity[],
|
activities: Activity[],
|
||||||
options: {
|
options: {
|
||||||
signatureActor?: MActorSignature
|
signatureActor?: MActorSignature
|
||||||
|
@ -86,7 +86,3 @@ async function processActivities (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
|
||||||
processActivities
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { getServerActor } from '@server/models/application/application.js'
|
|
||||||
import {
|
import {
|
||||||
ActivityAudience,
|
ActivityAudience,
|
||||||
ActivityCreate,
|
ActivityCreate,
|
||||||
|
@ -9,19 +7,24 @@ import {
|
||||||
VideoPlaylistPrivacy,
|
VideoPlaylistPrivacy,
|
||||||
VideoPrivacy
|
VideoPrivacy
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
|
import { AccountModel } from '@server/models/account/account.js'
|
||||||
|
import { getServerActor } from '@server/models/application/application.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../../helpers/logger.js'
|
||||||
import { VideoCommentModel } from '../../../models/video/video-comment.js'
|
import { VideoCommentModel } from '../../../models/video/video-comment.js'
|
||||||
import {
|
import {
|
||||||
MActorLight,
|
MActorLight,
|
||||||
MCommentOwnerVideo,
|
MCommentOwnerVideo,
|
||||||
MLocalVideoViewerWithWatchSections,
|
MLocalVideoViewerWithWatchSections,
|
||||||
MVideoAccountLight,
|
|
||||||
MVideoAP,
|
MVideoAP,
|
||||||
|
MVideoAccountLight,
|
||||||
MVideoPlaylistFull,
|
MVideoPlaylistFull,
|
||||||
MVideoRedundancyFileVideo,
|
MVideoRedundancyFileVideo,
|
||||||
MVideoRedundancyStreamingPlaylistVideo
|
MVideoRedundancyStreamingPlaylistVideo
|
||||||
} from '../../../types/models/index.js'
|
} from '../../../types/models/index.js'
|
||||||
import { audiencify, getAudience } from '../audience.js'
|
import { audiencify, getAudience } from '../audience.js'
|
||||||
|
import { canVideoBeFederated } from '../videos/federate.js'
|
||||||
import {
|
import {
|
||||||
broadcastToActors,
|
broadcastToActors,
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
|
@ -32,12 +35,11 @@ import {
|
||||||
sendVideoRelatedActivity,
|
sendVideoRelatedActivity,
|
||||||
unicastTo
|
unicastTo
|
||||||
} from './shared/index.js'
|
} from './shared/index.js'
|
||||||
import { AccountModel } from '@server/models/account/account.js'
|
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('ap', 'create')
|
const lTags = loggerTagsFactory('ap', 'create')
|
||||||
|
|
||||||
async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
|
export async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
|
||||||
if (!video.hasPrivacyForFederation()) return undefined
|
if (!canVideoBeFederated(video)) return undefined
|
||||||
|
|
||||||
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
|
logger.info('Creating job to send video creation of %s.', video.url, lTags(video.uuid))
|
||||||
|
|
||||||
|
@ -56,7 +58,7 @@ async function sendCreateVideo (video: MVideoAP, transaction: Transaction) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateCacheFile (
|
export async function sendCreateCacheFile (
|
||||||
byActor: MActorLight,
|
byActor: MActorLight,
|
||||||
video: MVideoAccountLight,
|
video: MVideoAccountLight,
|
||||||
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
|
fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo
|
||||||
|
@ -72,7 +74,7 @@ async function sendCreateCacheFile (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) {
|
export async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections, transaction: Transaction) {
|
||||||
logger.info('Creating job to send create watch action %s.', stats.url, lTags(stats.uuid))
|
logger.info('Creating job to send create watch action %s.', stats.url, lTags(stats.uuid))
|
||||||
|
|
||||||
const byActor = await getServerActor()
|
const byActor = await getServerActor()
|
||||||
|
@ -84,7 +86,7 @@ async function sendCreateWatchAction (stats: MLocalVideoViewerWithWatchSections,
|
||||||
return sendVideoActivityToOrigin(activityBuilder, { byActor, video: stats.Video, transaction, contextType: 'WatchAction' })
|
return sendVideoActivityToOrigin(activityBuilder, { byActor, video: stats.Video, transaction, contextType: 'WatchAction' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
|
export async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transaction: Transaction) {
|
||||||
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
||||||
|
|
||||||
logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
|
logger.info('Creating job to send create video playlist of %s.', playlist.url, lTags(playlist.uuid))
|
||||||
|
@ -109,11 +111,20 @@ async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, transactio
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
|
export async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction: Transaction) {
|
||||||
logger.info('Creating job to send comment %s.', comment.url)
|
|
||||||
|
|
||||||
const isOrigin = comment.Video.isOwned()
|
const isOrigin = comment.Video.isOwned()
|
||||||
|
|
||||||
|
if (isOrigin) {
|
||||||
|
const videoWithBlacklist = await VideoModel.loadWithBlacklist(comment.Video.id)
|
||||||
|
|
||||||
|
if (!canVideoBeFederated(videoWithBlacklist)) {
|
||||||
|
logger.debug(`Do not send comment ${comment.url} on a video that cannot be federated`)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('Creating job to send comment %s.', comment.url)
|
||||||
|
|
||||||
const byActor = comment.Account.Actor
|
const byActor = comment.Account.Actor
|
||||||
const videoAccount = await AccountModel.load(comment.Video.VideoChannel.Account.id, transaction)
|
const videoAccount = await AccountModel.load(comment.Video.VideoChannel.Account.id, transaction)
|
||||||
|
|
||||||
|
@ -179,7 +190,7 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, transaction:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCreateActivity <T extends ActivityCreateObject> (
|
export function buildCreateActivity <T extends ActivityCreateObject> (
|
||||||
url: string,
|
url: string,
|
||||||
byActor: MActorLight,
|
byActor: MActorLight,
|
||||||
object: T,
|
object: T,
|
||||||
|
@ -201,16 +212,7 @@ function buildCreateActivity <T extends ActivityCreateObject> (
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
sendCreateVideo,
|
|
||||||
buildCreateActivity,
|
|
||||||
sendCreateVideoComment,
|
|
||||||
sendCreateVideoPlaylist,
|
|
||||||
sendCreateCacheFile,
|
|
||||||
sendCreateWatchAction
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function sendVideoRelatedCreateActivity (options: {
|
async function sendVideoRelatedCreateActivity (options: {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { getServerActor } from '@server/models/application/application.js'
|
|
||||||
import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models'
|
import { ActivityAudience, ActivityUpdate, ActivityUpdateObject, VideoPlaylistPrivacy, VideoPrivacy } from '@peertube/peertube-models'
|
||||||
|
import { getServerActor } from '@server/models/application/application.js'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
import { logger } from '../../../helpers/logger.js'
|
import { logger } from '../../../helpers/logger.js'
|
||||||
import { AccountModel } from '../../../models/account/account.js'
|
import { AccountModel } from '../../../models/account/account.js'
|
||||||
import { VideoModel } from '../../../models/video/video.js'
|
|
||||||
import { VideoShareModel } from '../../../models/video/video-share.js'
|
import { VideoShareModel } from '../../../models/video/video-share.js'
|
||||||
|
import { VideoModel } from '../../../models/video/video.js'
|
||||||
import {
|
import {
|
||||||
MAccountDefault,
|
MAccountDefault,
|
||||||
MActor,
|
MActor,
|
||||||
|
@ -16,11 +16,12 @@ import {
|
||||||
} from '../../../types/models/index.js'
|
} from '../../../types/models/index.js'
|
||||||
import { audiencify, getAudience } from '../audience.js'
|
import { audiencify, getAudience } from '../audience.js'
|
||||||
import { getUpdateActivityPubUrl } from '../url.js'
|
import { getUpdateActivityPubUrl } from '../url.js'
|
||||||
|
import { canVideoBeFederated } from '../videos/federate.js'
|
||||||
import { getActorsInvolvedInVideo } from './shared/index.js'
|
import { getActorsInvolvedInVideo } from './shared/index.js'
|
||||||
import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils.js'
|
import { broadcastToFollowers, sendVideoRelatedActivity } from './shared/send-utils.js'
|
||||||
|
|
||||||
async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) {
|
export async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transaction, overriddenByActor?: MActor) {
|
||||||
if (!videoArg.hasPrivacyForFederation()) return undefined
|
if (!canVideoBeFederated(videoArg)) return undefined
|
||||||
|
|
||||||
const video = await videoArg.lightAPToFullAP(transaction)
|
const video = await videoArg.lightAPToFullAP(transaction)
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ async function sendUpdateVideo (videoArg: MVideoAPLight, transaction: Transactio
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
|
export async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, transaction: Transaction) {
|
||||||
const byActor = accountOrChannel.Actor
|
const byActor = accountOrChannel.Actor
|
||||||
|
|
||||||
logger.info('Creating job to update actor %s.', byActor.url)
|
logger.info('Creating job to update actor %s.', byActor.url)
|
||||||
|
@ -77,7 +78,7 @@ async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefa
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
|
export async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) {
|
||||||
logger.info('Creating job to update cache file %s.', redundancyModel.url)
|
logger.info('Creating job to update cache file %s.', redundancyModel.url)
|
||||||
|
|
||||||
const associatedVideo = redundancyModel.getVideo()
|
const associatedVideo = redundancyModel.getVideo()
|
||||||
|
@ -98,7 +99,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide
|
||||||
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
|
return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
|
export async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, transaction: Transaction) {
|
||||||
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined
|
||||||
|
|
||||||
const byActor = videoPlaylist.OwnerAccount.Actor
|
const byActor = videoPlaylist.OwnerAccount.Actor
|
||||||
|
@ -127,14 +128,7 @@ async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, trans
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
sendUpdateActor,
|
|
||||||
sendUpdateVideo,
|
|
||||||
sendUpdateCacheFile,
|
|
||||||
sendUpdateVideoPlaylist
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function buildUpdateActivity (
|
function buildUpdateActivity (
|
||||||
|
|
|
@ -1,25 +1,25 @@
|
||||||
import { Transaction } from 'sequelize'
|
|
||||||
import { ACTIVITY_PUB } from '@server/initializers/constants.js'
|
|
||||||
import { ActorModel } from '@server/models/actor/actor.js'
|
|
||||||
import { VideoModel } from '@server/models/video/video.js'
|
|
||||||
import { VideoShareModel } from '@server/models/video/video-share.js'
|
|
||||||
import { MActorFollowersUrl, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models/index.js'
|
|
||||||
import { ActivityAudience } from '@peertube/peertube-models'
|
import { ActivityAudience } from '@peertube/peertube-models'
|
||||||
|
import { getAPPublicValue } from '@server/helpers/activity-pub-utils.js'
|
||||||
|
import { ActorModel } from '@server/models/actor/actor.js'
|
||||||
|
import { VideoShareModel } from '@server/models/video/video-share.js'
|
||||||
|
import { VideoModel } from '@server/models/video/video.js'
|
||||||
|
import { MActorFollowersUrl, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '@server/types/models/index.js'
|
||||||
|
import { Transaction } from 'sequelize'
|
||||||
|
|
||||||
function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience {
|
export function getOriginVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[] = []): ActivityAudience {
|
||||||
return {
|
return {
|
||||||
to: [ accountActor.url ],
|
to: [ accountActor.url ],
|
||||||
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
|
cc: actorsInvolvedInVideo.map(a => a.followersUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getVideoCommentAudience (
|
export function getVideoCommentAudience (
|
||||||
videoComment: MCommentOwnerVideo,
|
videoComment: MCommentOwnerVideo,
|
||||||
threadParentComments: MCommentOwner[],
|
threadParentComments: MCommentOwner[],
|
||||||
actorsInvolvedInVideo: MActorFollowersUrl[],
|
actorsInvolvedInVideo: MActorFollowersUrl[],
|
||||||
isOrigin = false
|
isOrigin = false
|
||||||
): ActivityAudience {
|
): ActivityAudience {
|
||||||
const to = [ ACTIVITY_PUB.PUBLIC ]
|
const to = [ getAPPublicValue() ]
|
||||||
const cc: string[] = []
|
const cc: string[] = []
|
||||||
|
|
||||||
// Owner of the video we comment
|
// Owner of the video we comment
|
||||||
|
@ -43,14 +43,14 @@ function getVideoCommentAudience (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
|
export function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience {
|
||||||
return {
|
return {
|
||||||
to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
|
to: [ getAPPublicValue() ].concat(actorsInvolvedInObject.map(a => a.followersUrl)),
|
||||||
cc: []
|
cc: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
|
export async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
|
||||||
const actors = await VideoShareModel.listActorIdsAndFollowerUrlsByShare(video.id, t)
|
const actors = await VideoShareModel.listActorIdsAndFollowerUrlsByShare(video.id, t)
|
||||||
|
|
||||||
const alreadyLoadedActor = (video as VideoModel).VideoChannel?.Account?.Actor
|
const alreadyLoadedActor = (video as VideoModel).VideoChannel?.Account?.Actor
|
||||||
|
@ -63,12 +63,3 @@ async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) {
|
||||||
|
|
||||||
return actors
|
return actors
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
export {
|
|
||||||
getOriginVideoAudience,
|
|
||||||
getActorsInvolvedInVideo,
|
|
||||||
getAudienceFromFollowersOf,
|
|
||||||
getVideoCommentAudience
|
|
||||||
}
|
|
||||||
|
|
|
@ -258,7 +258,6 @@ function unicastTo (options: {
|
||||||
export {
|
export {
|
||||||
broadcastToFollowers,
|
broadcastToFollowers,
|
||||||
unicastTo,
|
unicastTo,
|
||||||
forwardActivity,
|
|
||||||
broadcastToActors,
|
broadcastToActors,
|
||||||
sendVideoActivityToOrigin,
|
sendVideoActivityToOrigin,
|
||||||
forwardVideoRelatedActivity,
|
forwardVideoRelatedActivity,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { getServerActor } from '@server/models/application/application.js'
|
||||||
import Bluebird from 'bluebird'
|
import Bluebird from 'bluebird'
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { getServerActor } from '@server/models/application/application.js'
|
|
||||||
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
|
import { logger, loggerTagsFactory } from '../../helpers/logger.js'
|
||||||
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants.js'
|
import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants.js'
|
||||||
import { VideoShareModel } from '../../models/video/video-share.js'
|
import { VideoShareModel } from '../../models/video/video-share.js'
|
||||||
|
@ -12,16 +12,7 @@ import { checkUrlsSameHost, getLocalVideoAnnounceActivityPubUrl } from './url.js
|
||||||
|
|
||||||
const lTags = loggerTagsFactory('share')
|
const lTags = loggerTagsFactory('share')
|
||||||
|
|
||||||
async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) {
|
export async function changeVideoChannelShare (
|
||||||
if (!video.hasPrivacyForFederation()) return undefined
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
shareByServer(video, t),
|
|
||||||
shareByVideoChannel(video, t)
|
|
||||||
])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function changeVideoChannelShare (
|
|
||||||
video: MVideoAccountLight,
|
video: MVideoAccountLight,
|
||||||
oldVideoChannel: MChannelActorLight,
|
oldVideoChannel: MChannelActorLight,
|
||||||
t: Transaction
|
t: Transaction
|
||||||
|
@ -36,7 +27,7 @@ async function changeVideoChannelShare (
|
||||||
await shareByVideoChannel(video, t)
|
await shareByVideoChannel(video, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addVideoShares (shareUrls: string[], video: MVideoId) {
|
export async function addVideoShares (shareUrls: string[], video: MVideoId) {
|
||||||
await Bluebird.map(shareUrls, async shareUrl => {
|
await Bluebird.map(shareUrls, async shareUrl => {
|
||||||
try {
|
try {
|
||||||
await addVideoShare(shareUrl, video)
|
await addVideoShare(shareUrl, video)
|
||||||
|
@ -46,12 +37,44 @@ async function addVideoShares (shareUrls: string[], video: MVideoId) {
|
||||||
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
}, { concurrency: CRAWL_REQUEST_CONCURRENCY })
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export async function shareByServer (video: MVideo, t: Transaction) {
|
||||||
changeVideoChannelShare,
|
const serverActor = await getServerActor()
|
||||||
addVideoShares,
|
|
||||||
shareVideoByServerAndChannel
|
const serverShareUrl = getLocalVideoAnnounceActivityPubUrl(serverActor, video)
|
||||||
|
const [ serverShare ] = await VideoShareModel.findOrCreate({
|
||||||
|
defaults: {
|
||||||
|
actorId: serverActor.id,
|
||||||
|
videoId: video.id,
|
||||||
|
url: serverShareUrl
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
url: serverShareUrl
|
||||||
|
},
|
||||||
|
transaction: t
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendVideoAnnounce(serverActor, serverShare, video, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
|
||||||
|
const videoChannelShareUrl = getLocalVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
|
||||||
|
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
|
||||||
|
defaults: {
|
||||||
|
actorId: video.VideoChannel.actorId,
|
||||||
|
videoId: video.id,
|
||||||
|
url: videoChannelShareUrl
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
url: videoChannelShareUrl
|
||||||
|
},
|
||||||
|
transaction: t
|
||||||
|
})
|
||||||
|
|
||||||
|
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function addVideoShare (shareUrl: string, video: MVideoId) {
|
async function addVideoShare (shareUrl: string, video: MVideoId) {
|
||||||
|
@ -74,42 +97,6 @@ async function addVideoShare (shareUrl: string, video: MVideoId) {
|
||||||
await VideoShareModel.upsert(entry)
|
await VideoShareModel.upsert(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shareByServer (video: MVideo, t: Transaction) {
|
|
||||||
const serverActor = await getServerActor()
|
|
||||||
|
|
||||||
const serverShareUrl = getLocalVideoAnnounceActivityPubUrl(serverActor, video)
|
|
||||||
const [ serverShare ] = await VideoShareModel.findOrCreate({
|
|
||||||
defaults: {
|
|
||||||
actorId: serverActor.id,
|
|
||||||
videoId: video.id,
|
|
||||||
url: serverShareUrl
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
url: serverShareUrl
|
|
||||||
},
|
|
||||||
transaction: t
|
|
||||||
})
|
|
||||||
|
|
||||||
return sendVideoAnnounce(serverActor, serverShare, video, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) {
|
|
||||||
const videoChannelShareUrl = getLocalVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video)
|
|
||||||
const [ videoChannelShare ] = await VideoShareModel.findOrCreate({
|
|
||||||
defaults: {
|
|
||||||
actorId: video.VideoChannel.actorId,
|
|
||||||
videoId: video.id,
|
|
||||||
url: videoChannelShareUrl
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
url: videoChannelShareUrl
|
|
||||||
},
|
|
||||||
transaction: t
|
|
||||||
})
|
|
||||||
|
|
||||||
return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
|
async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) {
|
||||||
// Load old share
|
// Load old share
|
||||||
const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
|
const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Hooks } from '../plugins/hooks.js'
|
||||||
import { fetchAP } from './activity.js'
|
import { fetchAP } from './activity.js'
|
||||||
import { getOrCreateAPActor } from './actors/index.js'
|
import { getOrCreateAPActor } from './actors/index.js'
|
||||||
import { checkUrlsSameHost } from './url.js'
|
import { checkUrlsSameHost } from './url.js'
|
||||||
import { getOrCreateAPVideo } from './videos/index.js'
|
import { canVideoBeFederated, getOrCreateAPVideo } from './videos/index.js'
|
||||||
|
|
||||||
type ResolveThreadParams = {
|
type ResolveThreadParams = {
|
||||||
url: string
|
url: string
|
||||||
|
@ -92,8 +92,8 @@ async function tryToResolveThreadFromVideo (params: ResolveThreadParams) {
|
||||||
const syncParam = { rates: true, shares: true, comments: false, refreshVideo: false }
|
const syncParam = { rates: true, shares: true, comments: false, refreshVideo: false }
|
||||||
const { video } = await getOrCreateAPVideo({ videoObject: url, syncParam })
|
const { video } = await getOrCreateAPVideo({ videoObject: url, syncParam })
|
||||||
|
|
||||||
if (video.isOwned() && !video.hasPrivacyForFederation()) {
|
if (video.isOwned() && !canVideoBeFederated(video)) {
|
||||||
throw new Error('Cannot resolve thread of video with privacy that is not compatible with federation')
|
throw new Error('Cannot resolve thread of video that is not compatible with federation')
|
||||||
}
|
}
|
||||||
|
|
||||||
let resultComment: MCommentOwnerVideo
|
let resultComment: MCommentOwnerVideo
|
||||||
|
|
|
@ -1,29 +1,53 @@
|
||||||
|
import { forceNumber } from '@peertube/peertube-core-utils'
|
||||||
|
import { VideoPrivacy, VideoPrivacyType, VideoState, VideoStateType } from '@peertube/peertube-models'
|
||||||
|
import { CONFIG } from '@server/initializers/config.js'
|
||||||
|
import { MVideoAPLight, MVideoWithBlacklistRights } from '@server/types/models/index.js'
|
||||||
import { Transaction } from 'sequelize'
|
import { Transaction } from 'sequelize'
|
||||||
import { MVideoAP, MVideoAPLight } from '@server/types/models/index.js'
|
|
||||||
import { sendCreateVideo, sendUpdateVideo } from '../send/index.js'
|
import { sendCreateVideo, sendUpdateVideo } from '../send/index.js'
|
||||||
import { shareVideoByServerAndChannel } from '../share.js'
|
import { shareByServer, shareByVideoChannel } from '../share.js'
|
||||||
|
|
||||||
async function federateVideoIfNeeded (videoArg: MVideoAPLight, isNewVideo: boolean, transaction?: Transaction) {
|
export async function federateVideoIfNeeded (videoArg: MVideoAPLight, isNewVideo: boolean, transaction?: Transaction) {
|
||||||
const video = videoArg as MVideoAP
|
if (!canVideoBeFederated(videoArg, isNewVideo)) return
|
||||||
|
|
||||||
if (
|
const video = await videoArg.lightAPToFullAP(transaction)
|
||||||
// Check this is not a blacklisted video, or unfederated blacklisted video
|
|
||||||
(video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) &&
|
|
||||||
// Check the video is public/unlisted and published
|
|
||||||
video.hasPrivacyForFederation() && video.hasStateForFederation()
|
|
||||||
) {
|
|
||||||
const video = await videoArg.lightAPToFullAP(transaction)
|
|
||||||
|
|
||||||
if (isNewVideo) {
|
if (isNewVideo) {
|
||||||
// Now we'll add the video's meta data to our followers
|
// Now we'll add the video's meta data to our followers
|
||||||
await sendCreateVideo(video, transaction)
|
await sendCreateVideo(video, transaction)
|
||||||
await shareVideoByServerAndChannel(video, transaction)
|
|
||||||
} else {
|
await Promise.all([
|
||||||
await sendUpdateVideo(video, transaction)
|
shareByServer(video, transaction),
|
||||||
}
|
shareByVideoChannel(video, transaction)
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
await sendUpdateVideo(video, transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export function canVideoBeFederated (video: MVideoWithBlacklistRights, isNewVideo = false) {
|
||||||
federateVideoIfNeeded
|
// Check this is not a blacklisted video
|
||||||
|
if (video.isBlacklisted() === true) {
|
||||||
|
if (isNewVideo === false) return false
|
||||||
|
if (video.VideoBlacklist.unfederated === true) return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the video is public/unlisted and published
|
||||||
|
return isPrivacyForFederation(video.privacy) && isStateForFederation(video.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNewVideoPrivacyForFederation (currentPrivacy: VideoPrivacyType, newPrivacy: VideoPrivacyType) {
|
||||||
|
return !isPrivacyForFederation(currentPrivacy) && isPrivacyForFederation(newPrivacy)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPrivacyForFederation (privacy: VideoPrivacyType) {
|
||||||
|
const castedPrivacy = forceNumber(privacy)
|
||||||
|
|
||||||
|
return castedPrivacy === VideoPrivacy.PUBLIC ||
|
||||||
|
(CONFIG.FEDERATION.VIDEOS.FEDERATE_UNLISTED === true && castedPrivacy === VideoPrivacy.UNLISTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isStateForFederation (state: VideoStateType) {
|
||||||
|
const castedState = forceNumber(state)
|
||||||
|
|
||||||
|
return castedState === VideoState.PUBLISHED || castedState === VideoState.WAITING_FOR_LIVE || castedState === VideoState.LIVE_ENDED
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
|
import { APObjectId } from '@peertube/peertube-models'
|
||||||
import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
|
import { retryTransactionWrapper } from '@server/helpers/database-utils.js'
|
||||||
import { logger } from '@server/helpers/logger.js'
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { JobQueue } from '@server/lib/job-queue/index.js'
|
import { JobQueue } from '@server/lib/job-queue/index.js'
|
||||||
import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders/index.js'
|
import { loadVideoByUrl, VideoLoadByUrlType } from '@server/lib/model-loaders/index.js'
|
||||||
import { MVideoAccountLightBlacklistAllFiles, MVideoImmutable, MVideoThumbnail } from '@server/types/models/index.js'
|
import {
|
||||||
import { APObjectId } from '@peertube/peertube-models'
|
MVideoAccountLightBlacklistAllFiles,
|
||||||
|
MVideoImmutable,
|
||||||
|
MVideoThumbnail,
|
||||||
|
MVideoThumbnailBlacklist
|
||||||
|
} from '@server/types/models/index.js'
|
||||||
import { getAPId } from '../activity.js'
|
import { getAPId } from '../activity.js'
|
||||||
import { refreshVideoIfNeeded } from './refresh.js'
|
import { refreshVideoIfNeeded } from './refresh.js'
|
||||||
import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared/index.js'
|
import { APVideoCreator, fetchRemoteVideo, SyncParam, syncVideoExternalAttributes } from './shared/index.js'
|
||||||
|
@ -24,23 +29,25 @@ type GetVideoParamAll = {
|
||||||
type GetVideoParamImmutable = {
|
type GetVideoParamImmutable = {
|
||||||
videoObject: APObjectId
|
videoObject: APObjectId
|
||||||
syncParam?: SyncParam
|
syncParam?: SyncParam
|
||||||
fetchType: 'only-immutable-attributes'
|
fetchType: 'unsafe-only-immutable-attributes'
|
||||||
allowRefresh: false
|
allowRefresh: false
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetVideoParamOther = {
|
type GetVideoParamOther = {
|
||||||
videoObject: APObjectId
|
videoObject: APObjectId
|
||||||
syncParam?: SyncParam
|
syncParam?: SyncParam
|
||||||
fetchType?: 'all' | 'only-video'
|
fetchType?: 'all' | 'only-video-and-blacklist'
|
||||||
allowRefresh?: boolean
|
allowRefresh?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
export function getOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
||||||
export function getOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
export function getOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
||||||
export function getOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
|
export function getOrCreateAPVideo (
|
||||||
|
options: GetVideoParamOther
|
||||||
|
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist>
|
||||||
export async function getOrCreateAPVideo (
|
export async function getOrCreateAPVideo (
|
||||||
options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
|
options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther
|
||||||
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
|
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable> {
|
||||||
// Default params
|
// Default params
|
||||||
const syncParam = options.syncParam || { rates: true, shares: true, comments: true, refreshVideo: false }
|
const syncParam = options.syncParam || { rates: true, shares: true, comments: true, refreshVideo: false }
|
||||||
const fetchType = options.fetchType || 'all'
|
const fetchType = options.fetchType || 'all'
|
||||||
|
@ -52,7 +59,7 @@ export async function getOrCreateAPVideo (
|
||||||
|
|
||||||
if (videoFromDatabase) {
|
if (videoFromDatabase) {
|
||||||
if (allowRefresh === true) {
|
if (allowRefresh === true) {
|
||||||
// Typings ensure allowRefresh === false in only-immutable-attributes fetch type
|
// Typings ensure allowRefresh === false in unsafe-only-immutable-attributes fetch type
|
||||||
videoFromDatabase = await scheduleRefresh(videoFromDatabase as MVideoThumbnail, fetchType, syncParam)
|
videoFromDatabase = await scheduleRefresh(videoFromDatabase as MVideoThumbnail, fetchType, syncParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +94,9 @@ export async function getOrCreateAPVideo (
|
||||||
|
|
||||||
export function maybeGetOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
export function maybeGetOrCreateAPVideo (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles>
|
||||||
export function maybeGetOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
export function maybeGetOrCreateAPVideo (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable>
|
||||||
export function maybeGetOrCreateAPVideo (options: GetVideoParamOther): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail>
|
export function maybeGetOrCreateAPVideo (
|
||||||
|
options: GetVideoParamOther
|
||||||
|
): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist>
|
||||||
export async function maybeGetOrCreateAPVideo (options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther) {
|
export async function maybeGetOrCreateAPVideo (options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther) {
|
||||||
try {
|
try {
|
||||||
const result = await getOrCreateAPVideo(options as any)
|
const result = await getOrCreateAPVideo(options as any)
|
||||||
|
|
|
@ -11,13 +11,14 @@ import {
|
||||||
VideoPrivacy,
|
VideoPrivacy,
|
||||||
VideoStreamingPlaylistType
|
VideoStreamingPlaylistType
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
|
import { hasAPPublic } from '@server/helpers/activity-pub-utils.js'
|
||||||
import { isAPVideoFileUrlMetadataObject } from '@server/helpers/custom-validators/activitypub/videos.js'
|
import { isAPVideoFileUrlMetadataObject } from '@server/helpers/custom-validators/activitypub/videos.js'
|
||||||
import { isArray } from '@server/helpers/custom-validators/misc.js'
|
import { isArray } from '@server/helpers/custom-validators/misc.js'
|
||||||
import { isVideoFileInfoHashValid } from '@server/helpers/custom-validators/videos.js'
|
import { isVideoFileInfoHashValid } from '@server/helpers/custom-validators/videos.js'
|
||||||
import { generateImageFilename } from '@server/helpers/image-utils.js'
|
import { generateImageFilename } from '@server/helpers/image-utils.js'
|
||||||
import { logger } from '@server/helpers/logger.js'
|
import { logger } from '@server/helpers/logger.js'
|
||||||
import { getExtFromMimetype } from '@server/helpers/video.js'
|
import { getExtFromMimetype } from '@server/helpers/video.js'
|
||||||
import { ACTIVITY_PUB, MIMETYPES, P2P_MEDIA_LOADER_PEER_VERSION, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '@server/initializers/constants.js'
|
import { MIMETYPES, P2P_MEDIA_LOADER_PEER_VERSION, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '@server/initializers/constants.js'
|
||||||
import { generateTorrentFileName } from '@server/lib/paths.js'
|
import { generateTorrentFileName } from '@server/lib/paths.js'
|
||||||
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
|
import { VideoCaptionModel } from '@server/models/video/video-caption.js'
|
||||||
import { VideoFileModel } from '@server/models/video/video-file.js'
|
import { VideoFileModel } from '@server/models/video/video-file.js'
|
||||||
|
@ -191,7 +192,7 @@ export function getStoryboardAttributeFromObject (video: MVideoId, videoObject:
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: VideoObject, to: string[] = []) {
|
export function getVideoAttributesFromObject (videoChannel: MChannelId, videoObject: VideoObject, to: string[] = []) {
|
||||||
const privacy = to.includes(ACTIVITY_PUB.PUBLIC)
|
const privacy = hasAPPublic(to)
|
||||||
? VideoPrivacy.PUBLIC
|
? VideoPrivacy.PUBLIC
|
||||||
: VideoPrivacy.UNLISTED
|
: VideoPrivacy.UNLISTED
|
||||||
|
|
||||||
|
|
|
@ -6,57 +6,57 @@ import {
|
||||||
MVideoFullLight,
|
MVideoFullLight,
|
||||||
MVideoId,
|
MVideoId,
|
||||||
MVideoImmutable,
|
MVideoImmutable,
|
||||||
MVideoThumbnail
|
MVideoThumbnailBlacklist
|
||||||
} from '@server/types/models/index.js'
|
} from '@server/types/models/index.js'
|
||||||
import { getOrCreateAPVideo } from '../activitypub/videos/get.js'
|
import { getOrCreateAPVideo } from '../activitypub/videos/get.js'
|
||||||
|
|
||||||
type VideoLoadType = 'for-api' | 'all' | 'only-video' | 'id' | 'none' | 'only-immutable-attributes'
|
type VideoLoadType = 'for-api' | 'all' | 'only-video-and-blacklist' | 'id' | 'none' | 'unsafe-only-immutable-attributes'
|
||||||
|
|
||||||
function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails>
|
function loadVideo (id: number | string, fetchType: 'for-api', userId?: number): Promise<MVideoFormattableDetails>
|
||||||
function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight>
|
function loadVideo (id: number | string, fetchType: 'all', userId?: number): Promise<MVideoFullLight>
|
||||||
function loadVideo (id: number | string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
|
function loadVideo (id: number | string, fetchType: 'unsafe-only-immutable-attributes'): Promise<MVideoImmutable>
|
||||||
function loadVideo (id: number | string, fetchType: 'only-video', userId?: number): Promise<MVideoThumbnail>
|
function loadVideo (id: number | string, fetchType: 'only-video-and-blacklist', userId?: number): Promise<MVideoThumbnailBlacklist>
|
||||||
function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoId>
|
function loadVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Promise<MVideoId>
|
||||||
function loadVideo (
|
function loadVideo (
|
||||||
id: number | string,
|
id: number | string,
|
||||||
fetchType: VideoLoadType,
|
fetchType: VideoLoadType,
|
||||||
userId?: number
|
userId?: number
|
||||||
): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable>
|
): Promise<MVideoFullLight | MVideoThumbnailBlacklist | MVideoId | MVideoImmutable>
|
||||||
function loadVideo (
|
function loadVideo (
|
||||||
id: number | string,
|
id: number | string,
|
||||||
fetchType: VideoLoadType,
|
fetchType: VideoLoadType,
|
||||||
userId?: number
|
userId?: number
|
||||||
): Promise<MVideoFullLight | MVideoThumbnail | MVideoId | MVideoImmutable> {
|
): Promise<MVideoFullLight | MVideoThumbnailBlacklist | MVideoId | MVideoImmutable> {
|
||||||
|
|
||||||
if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId })
|
if (fetchType === 'for-api') return VideoModel.loadForGetAPI({ id, userId })
|
||||||
|
|
||||||
if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId)
|
if (fetchType === 'all') return VideoModel.loadFull(id, undefined, userId)
|
||||||
|
|
||||||
if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
|
if (fetchType === 'unsafe-only-immutable-attributes') return VideoModel.loadImmutableAttributes(id)
|
||||||
|
|
||||||
if (fetchType === 'only-video') return VideoModel.load(id)
|
if (fetchType === 'only-video-and-blacklist') return VideoModel.loadWithBlacklist(id)
|
||||||
|
|
||||||
if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
|
if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
type VideoLoadByUrlType = 'all' | 'only-video' | 'only-immutable-attributes'
|
type VideoLoadByUrlType = 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes'
|
||||||
|
|
||||||
function loadVideoByUrl (url: string, fetchType: 'all'): Promise<MVideoAccountLightBlacklistAllFiles>
|
function loadVideoByUrl (url: string, fetchType: 'all'): Promise<MVideoAccountLightBlacklistAllFiles>
|
||||||
function loadVideoByUrl (url: string, fetchType: 'only-immutable-attributes'): Promise<MVideoImmutable>
|
function loadVideoByUrl (url: string, fetchType: 'unsafe-only-immutable-attributes'): Promise<MVideoImmutable>
|
||||||
function loadVideoByUrl (url: string, fetchType: 'only-video'): Promise<MVideoThumbnail>
|
function loadVideoByUrl (url: string, fetchType: 'only-video-and-blacklist'): Promise<MVideoThumbnailBlacklist>
|
||||||
function loadVideoByUrl (
|
function loadVideoByUrl (
|
||||||
url: string,
|
url: string,
|
||||||
fetchType: VideoLoadByUrlType
|
fetchType: VideoLoadByUrlType
|
||||||
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable>
|
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable>
|
||||||
function loadVideoByUrl (
|
function loadVideoByUrl (
|
||||||
url: string,
|
url: string,
|
||||||
fetchType: VideoLoadByUrlType
|
fetchType: VideoLoadByUrlType
|
||||||
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> {
|
): Promise<MVideoAccountLightBlacklistAllFiles | MVideoThumbnailBlacklist | MVideoImmutable> {
|
||||||
if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccountAndFiles(url)
|
if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccountAndFiles(url)
|
||||||
|
|
||||||
if (fetchType === 'only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
|
if (fetchType === 'unsafe-only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url)
|
||||||
|
|
||||||
if (fetchType === 'only-video') return VideoModel.loadByUrl(url)
|
if (fetchType === 'only-video-and-blacklist') return VideoModel.loadByUrlWithBlacklist(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
||||||
|
@ -64,7 +64,7 @@ async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
||||||
try {
|
try {
|
||||||
const res = await getOrCreateAPVideo({
|
const res = await getOrCreateAPVideo({
|
||||||
videoObject: videoUrl,
|
videoObject: videoUrl,
|
||||||
fetchType: 'only-immutable-attributes',
|
fetchType: 'unsafe-only-immutable-attributes',
|
||||||
allowRefresh: false
|
allowRefresh: false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -78,10 +78,8 @@ async function loadOrCreateVideoIfAllowedForUser (videoUrl: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type VideoLoadType,
|
loadOrCreateVideoIfAllowedForUser, loadVideo,
|
||||||
type VideoLoadByUrlType,
|
|
||||||
|
|
||||||
loadVideo,
|
|
||||||
loadVideoByUrl,
|
loadVideoByUrl,
|
||||||
loadOrCreateVideoIfAllowedForUser
|
type VideoLoadByUrlType,
|
||||||
|
type VideoLoadType
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { logger } from '../../helpers/logger.js'
|
||||||
import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants.js'
|
import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants.js'
|
||||||
import { sequelizeTypescript } from '../../initializers/database.js'
|
import { sequelizeTypescript } from '../../initializers/database.js'
|
||||||
import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update.js'
|
import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update.js'
|
||||||
|
import { isNewVideoPrivacyForFederation } from '../activitypub/videos/federate.js'
|
||||||
import { Notifier } from '../notifier/index.js'
|
import { Notifier } from '../notifier/index.js'
|
||||||
|
import { addVideoJobsAfterUpdate } from '../video-jobs.js'
|
||||||
import { VideoPathManager } from '../video-path-manager.js'
|
import { VideoPathManager } from '../video-path-manager.js'
|
||||||
import { setVideoPrivacy } from '../video-privacy.js'
|
import { setVideoPrivacy } from '../video-privacy.js'
|
||||||
import { addVideoJobsAfterUpdate } from '../video-jobs.js'
|
|
||||||
import { AbstractScheduler } from './abstract-scheduler.js'
|
import { AbstractScheduler } from './abstract-scheduler.js'
|
||||||
|
|
||||||
export class UpdateVideosScheduler extends AbstractScheduler {
|
export class UpdateVideosScheduler extends AbstractScheduler {
|
||||||
|
@ -58,7 +59,7 @@ export class UpdateVideosScheduler extends AbstractScheduler {
|
||||||
logger.info('Executing scheduled video update on %s.', video.uuid)
|
logger.info('Executing scheduled video update on %s.', video.uuid)
|
||||||
|
|
||||||
if (schedule.privacy) {
|
if (schedule.privacy) {
|
||||||
isNewVideoForFederation = video.isNewVideoForFederation(schedule.privacy)
|
isNewVideoForFederation = isNewVideoPrivacyForFederation(video.privacy, schedule.privacy)
|
||||||
oldPrivacy = video.privacy
|
oldPrivacy = video.privacy
|
||||||
|
|
||||||
setVideoPrivacy(video, schedule.privacy)
|
setVideoPrivacy(video, schedule.privacy)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { NextFunction, Request, Response } from 'express'
|
import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@peertube/peertube-models'
|
||||||
import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor.js'
|
import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor.js'
|
||||||
import { getAPId } from '@server/lib/activitypub/activity.js'
|
import { getAPId } from '@server/lib/activitypub/activity.js'
|
||||||
import { wrapWithSpanAndContext } from '@server/lib/opentelemetry/tracing.js'
|
import { wrapWithSpanAndContext } from '@server/lib/opentelemetry/tracing.js'
|
||||||
import { ActivityDelete, ActivityPubSignature, HttpStatusCode } from '@peertube/peertube-models'
|
import { NextFunction, Request, Response } from 'express'
|
||||||
import { logger } from '../helpers/logger.js'
|
import { logger } from '../helpers/logger.js'
|
||||||
import { isHTTPSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto.js'
|
import { isHTTPSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto.js'
|
||||||
import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants.js'
|
import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants.js'
|
||||||
import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../lib/activitypub/actors/index.js'
|
import { getOrCreateAPActor, loadActorUrlOrGetFromWebfinger } from '../lib/activitypub/actors/index.js'
|
||||||
|
|
||||||
async function checkSignature (req: Request, res: Response, next: NextFunction) {
|
export async function checkSignature (req: Request, res: Response, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
const httpSignatureChecked = await checkHttpSignature(req, res)
|
const httpSignatureChecked = await checkHttpSignature(req, res)
|
||||||
if (httpSignatureChecked !== true) return
|
if (httpSignatureChecked !== true) return
|
||||||
|
@ -39,7 +39,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function executeIfActivityPub (req: Request, res: Response, next: NextFunction) {
|
export function executeIfActivityPub (req: Request, res: Response, next: NextFunction) {
|
||||||
const accepted = req.accepts(ACCEPT_HEADERS)
|
const accepted = req.accepts(ACCEPT_HEADERS)
|
||||||
if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.includes(accepted) === false) {
|
if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.includes(accepted) === false) {
|
||||||
// Bypass this route
|
// Bypass this route
|
||||||
|
@ -52,13 +52,7 @@ function executeIfActivityPub (req: Request, res: Response, next: NextFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
// Private
|
||||||
export {
|
|
||||||
checkSignature,
|
|
||||||
executeIfActivityPub,
|
|
||||||
checkHttpSignature
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
async function checkHttpSignature (req: Request, res: Response) {
|
async function checkHttpSignature (req: Request, res: Response) {
|
||||||
|
@ -123,7 +117,7 @@ async function checkHttpSignature (req: Request, res: Response) {
|
||||||
|
|
||||||
async function checkJsonLDSignature (req: Request, res: Response) {
|
async function checkJsonLDSignature (req: Request, res: Response) {
|
||||||
// Lazy load the module as it's quite big with json.ld dependency
|
// Lazy load the module as it's quite big with json.ld dependency
|
||||||
const { isJsonLDSignatureVerified } = await import('../helpers/peertube-jsonld.js')
|
const { compactJSONLDAndCheckSignature } = await import('../helpers/peertube-jsonld.js')
|
||||||
|
|
||||||
return wrapWithSpanAndContext('peertube.activitypub.JSONLDSignature', async () => {
|
return wrapWithSpanAndContext('peertube.activitypub.JSONLDSignature', async () => {
|
||||||
const signatureObject: ActivityPubSignature = req.body.signature
|
const signatureObject: ActivityPubSignature = req.body.signature
|
||||||
|
@ -141,7 +135,7 @@ async function checkJsonLDSignature (req: Request, res: Response) {
|
||||||
logger.debug('Checking JsonLD signature of actor %s...', creator)
|
logger.debug('Checking JsonLD signature of actor %s...', creator)
|
||||||
|
|
||||||
const actor = await getOrCreateAPActor(creator)
|
const actor = await getOrCreateAPActor(creator)
|
||||||
const verified = await isJsonLDSignatureVerified(actor, req.body)
|
const verified = await compactJSONLDAndCheckSignature(actor, req)
|
||||||
|
|
||||||
if (verified !== true) {
|
if (verified !== true) {
|
||||||
logger.warn('Signature not verified.', req.body)
|
logger.warn('Signature not verified.', req.body)
|
||||||
|
|
|
@ -47,7 +47,7 @@ const addPlaybackMetricValidator = [
|
||||||
const body: PlaybackMetricCreate = req.body
|
const body: PlaybackMetricCreate = req.body
|
||||||
|
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(body.videoId, res, 'only-immutable-attributes')) return
|
if (!await doesVideoExist(body.videoId, res, 'unsafe-only-immutable-attributes')) return
|
||||||
|
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ const addVideoRedundancyValidator = [
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
|
if (!await doesVideoExist(req.body.videoId, res, 'only-video-and-blacklist')) return
|
||||||
|
|
||||||
if (res.locals.onlyVideo.remote === false) {
|
if (res.locals.onlyVideo.remote === false) {
|
||||||
return res.fail({ message: 'Cannot create a redundancy on a local video' })
|
return res.fail({ message: 'Cannot create a redundancy on a local video' })
|
||||||
|
|
|
@ -47,7 +47,7 @@ export async function doesVideoExist (id: number | string, res: Response, fetchT
|
||||||
res.locals.videoAll = video as MVideoFullLight
|
res.locals.videoAll = video as MVideoFullLight
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'only-immutable-attributes':
|
case 'unsafe-only-immutable-attributes':
|
||||||
res.locals.onlyImmutableVideo = video as MVideoImmutable
|
res.locals.onlyImmutableVideo = video as MVideoImmutable
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ export async function doesVideoExist (id: number | string, res: Response, fetchT
|
||||||
res.locals.videoId = video as MVideoId
|
res.locals.videoId = video as MVideoId
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'only-video':
|
case 'only-video-and-blacklist':
|
||||||
res.locals.onlyVideo = video as MVideoThumbnail
|
res.locals.onlyVideo = video as MVideoThumbnail
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ const listVideoCaptionsValidator = [
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
|
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
|
||||||
|
|
||||||
const video = res.locals.onlyVideo
|
const video = res.locals.onlyVideo
|
||||||
if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.videoId })) return
|
if (!await checkCanSeeVideo({ req, res, video, paramId: req.params.videoId })) return
|
||||||
|
|
|
@ -56,7 +56,7 @@ const listVideoCommentThreadsValidator = [
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
|
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
|
||||||
|
|
||||||
if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
|
if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ const listVideoThreadCommentsValidator = [
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return
|
if (!await doesVideoExist(req.params.videoId, res, 'only-video-and-blacklist')) return
|
||||||
if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
|
if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return
|
||||||
|
|
||||||
if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
|
if (!await checkCanSeeVideo({ req, res, paramId: req.params.videoId, video: res.locals.onlyVideo })) return
|
||||||
|
|
|
@ -205,7 +205,7 @@ const videoPlaylistsAddVideoValidator = [
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
|
|
||||||
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
|
if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return
|
||||||
if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return
|
if (!await doesVideoExist(req.body.videoId, res, 'only-video-and-blacklist')) return
|
||||||
|
|
||||||
const videoPlaylist = getPlaylist(res)
|
const videoPlaylist = getPlaylist(res)
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const videoViewValidator = [
|
||||||
|
|
||||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||||
if (areValidationErrors(req, res)) return
|
if (areValidationErrors(req, res)) return
|
||||||
if (!await doesVideoExist(req.params.videoId, res, 'only-immutable-attributes')) return
|
if (!await doesVideoExist(req.params.videoId, res, 'unsafe-only-immutable-attributes')) return
|
||||||
|
|
||||||
const video = res.locals.onlyImmutableVideo
|
const video = res.locals.onlyImmutableVideo
|
||||||
const { duration } = await getCachedVideoDuration(video.id)
|
const { duration } = await getCachedVideoDuration(video.id)
|
||||||
|
|
|
@ -243,7 +243,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' | 'only-immutable-attributes') => {
|
const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video-and-blacklist' | 'unsafe-only-immutable-attributes') => {
|
||||||
return [
|
return [
|
||||||
isValidVideoIdParam('id'),
|
isValidVideoIdParam('id'),
|
||||||
|
|
||||||
|
@ -254,7 +254,7 @@ const videosCustomGetValidator = (fetchType: 'for-api' | 'all' | 'only-video' |
|
||||||
if (!await doesVideoExist(req.params.id, res, fetchType)) return
|
if (!await doesVideoExist(req.params.id, res, fetchType)) return
|
||||||
|
|
||||||
// Controllers does not need to check video rights
|
// Controllers does not need to check video rights
|
||||||
if (fetchType === 'only-immutable-attributes') return next()
|
if (fetchType === 'unsafe-only-immutable-attributes') return next()
|
||||||
|
|
||||||
const video = getVideoWithAttributes(res) as MVideoFullLight
|
const video = getVideoWithAttributes(res) as MVideoFullLight
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
type VideoStateType
|
type VideoStateType
|
||||||
} from '@peertube/peertube-models'
|
} from '@peertube/peertube-models'
|
||||||
import { uuidToShort } from '@peertube/peertube-node-utils'
|
import { uuidToShort } from '@peertube/peertube-node-utils'
|
||||||
import { getPrivaciesForFederation, isPrivacyForFederation, isStateForFederation } from '@server/helpers/video.js'
|
import { getPrivaciesForFederation } from '@server/helpers/video.js'
|
||||||
import { InternalEventEmitter } from '@server/lib/internal-event-emitter.js'
|
import { InternalEventEmitter } from '@server/lib/internal-event-emitter.js'
|
||||||
import { LiveManager } from '@server/lib/live/live-manager.js'
|
import { LiveManager } from '@server/lib/live/live-manager.js'
|
||||||
import {
|
import {
|
||||||
|
@ -1448,6 +1448,12 @@ export class VideoModel extends SequelizeModel<VideoModel> {
|
||||||
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails' })
|
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static loadByUrlWithBlacklist (url: string, transaction?: Transaction): Promise<MVideoThumbnailBlacklist> {
|
||||||
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
|
return queryBuilder.queryVideo({ url, transaction, type: 'thumbnails-blacklist' })
|
||||||
|
}
|
||||||
|
|
||||||
static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLight> {
|
static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Promise<MVideoAccountLight> {
|
||||||
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
const queryBuilder = new VideoModelGetQueryBuilder(VideoModel.sequelize)
|
||||||
|
|
||||||
|
@ -2045,18 +2051,6 @@ export class VideoModel extends SequelizeModel<VideoModel> {
|
||||||
return isOutdated(this, ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL)
|
return isOutdated(this, ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL)
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPrivacyForFederation () {
|
|
||||||
return isPrivacyForFederation(this.privacy)
|
|
||||||
}
|
|
||||||
|
|
||||||
hasStateForFederation () {
|
|
||||||
return isStateForFederation(this.state)
|
|
||||||
}
|
|
||||||
|
|
||||||
isNewVideoForFederation (newPrivacy: VideoPrivacyType) {
|
|
||||||
return this.hasPrivacyForFederation() === false && isPrivacyForFederation(newPrivacy) === true
|
|
||||||
}
|
|
||||||
|
|
||||||
setAsRefreshed (transaction?: Transaction) {
|
setAsRefreshed (transaction?: Transaction) {
|
||||||
return setAsUpdated({ sequelize: this.sequelize, table: 'video', id: this.id, transaction })
|
return setAsUpdated({ sequelize: this.sequelize, table: 'video', id: this.id, transaction })
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,10 @@ export type MVideoWithBlacklistLight =
|
||||||
MVideo &
|
MVideo &
|
||||||
Use<'VideoBlacklist', MVideoBlacklistLight>
|
Use<'VideoBlacklist', MVideoBlacklistLight>
|
||||||
|
|
||||||
|
export type MVideoWithBlacklistRights =
|
||||||
|
MVideo &
|
||||||
|
Use<'VideoBlacklist', MVideoBlacklistUnfederated>
|
||||||
|
|
||||||
export type MVideoAccountLight =
|
export type MVideoAccountLight =
|
||||||
MVideo &
|
MVideo &
|
||||||
Use<'VideoChannel', MChannelAccountLight>
|
Use<'VideoChannel', MChannelAccountLight>
|
||||||
|
|
Loading…
Reference in New Issue