1
0
Fork 0

Add ability to filter out public videos from admin

This commit is contained in:
Chocobozzz 2021-11-12 14:19:56 +01:00
parent 8f2608e9a9
commit 527a52ac42
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
15 changed files with 125 additions and 30 deletions

View File

@ -5,7 +5,8 @@ import { Injectable } from '@angular/core'
import { RestExtractor, RestPagination, RestService } from '@app/core' import { RestExtractor, RestPagination, RestService } from '@app/core'
import { AdvancedInputFilter } from '@app/shared/shared-forms' import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main' import { CommonVideoParams, Video, VideoService } from '@app/shared/shared-main'
import { ResultList, VideoInclude } from '@shared/models' import { ResultList, VideoInclude, VideoPrivacy } from '@shared/models'
import { getAllPrivacies } from '@shared/core-utils'
@Injectable() @Injectable()
export class VideoAdminService { export class VideoAdminService {
@ -96,6 +97,10 @@ export class VideoAdminService {
{ {
value: 'excludeMuted', value: 'excludeMuted',
label: $localize`Exclude muted accounts` label: $localize`Exclude muted accounts`
},
{
value: 'excludePublic',
label: $localize`Exclude public videos`
} }
] ]
} }
@ -105,11 +110,12 @@ export class VideoAdminService {
private buildAdminParamsFromSearch (search: string, params: HttpParams) { private buildAdminParamsFromSearch (search: string, params: HttpParams) {
let include = VideoInclude.BLACKLISTED | let include = VideoInclude.BLACKLISTED |
VideoInclude.BLOCKED_OWNER | VideoInclude.BLOCKED_OWNER |
VideoInclude.HIDDEN_PRIVACY |
VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.NOT_PUBLISHED_STATE |
VideoInclude.FILES VideoInclude.FILES
if (!search) return this.restService.addObjectParams(params, { include }) let privacyOneOf = getAllPrivacies()
if (!search) return this.restService.addObjectParams(params, { include, privacyOneOf })
const filters = this.restService.parseQueryStringFilter(search, { const filters = this.restService.parseQueryStringFilter(search, {
isLocal: { isLocal: {
@ -131,6 +137,10 @@ export class VideoAdminService {
excludeMuted: { excludeMuted: {
prefix: 'excludeMuted', prefix: 'excludeMuted',
handler: () => true handler: () => true
},
excludePublic: {
prefix: 'excludePublic',
handler: () => true
} }
}) })
@ -140,6 +150,12 @@ export class VideoAdminService {
filters.excludeMuted = undefined filters.excludeMuted = undefined
} }
return this.restService.addObjectParams(params, { ...filters, include }) if (filters.excludePublic) {
privacyOneOf = [ VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED, VideoPrivacy.INTERNAL ]
filters.excludePublic = undefined
}
return this.restService.addObjectParams(params, { ...filters, include, privacyOneOf })
} }
} }

View File

@ -38,6 +38,7 @@ export type CommonVideoParams = {
isLocal?: boolean isLocal?: boolean
categoryOneOf?: number[] categoryOneOf?: number[]
languageOneOf?: string[] languageOneOf?: string[]
privacyOneOf?: VideoPrivacy[]
isLive?: boolean isLive?: boolean
skipCount?: boolean skipCount?: boolean
@ -392,6 +393,7 @@ export class VideoService {
include, include,
categoryOneOf, categoryOneOf,
languageOneOf, languageOneOf,
privacyOneOf,
skipCount, skipCount,
nsfwPolicy, nsfwPolicy,
isLive, isLive,
@ -413,6 +415,7 @@ export class VideoService {
if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy)) if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
if (languageOneOf) newParams = this.restService.addArrayParams(newParams, 'languageOneOf', languageOneOf) if (languageOneOf) newParams = this.restService.addArrayParams(newParams, 'languageOneOf', languageOneOf)
if (categoryOneOf) newParams = this.restService.addArrayParams(newParams, 'categoryOneOf', categoryOneOf) if (categoryOneOf) newParams = this.restService.addArrayParams(newParams, 'categoryOneOf', categoryOneOf)
if (privacyOneOf) newParams = this.restService.addArrayParams(newParams, 'privacyOneOf', privacyOneOf)
return newParams return newParams
} }

View File

@ -1,6 +1,6 @@
import { intoArray, toBoolean } from '@app/helpers' import { intoArray, toBoolean } from '@app/helpers'
import { AttributesOnly } from '@shared/core-utils' import { AttributesOnly, getAllPrivacies } from '@shared/core-utils'
import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoSortField } from '@shared/models' import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoPrivacy, VideoSortField } from '@shared/models'
type VideoFiltersKeys = { type VideoFiltersKeys = {
[ id in keyof AttributesOnly<VideoFilters> ]: any [ id in keyof AttributesOnly<VideoFilters> ]: any
@ -198,13 +198,15 @@ export class VideoFilters {
toVideosAPIObject () { toVideosAPIObject () {
let isLocal: boolean let isLocal: boolean
let include: VideoInclude let include: VideoInclude
let privacyOneOf: VideoPrivacy[]
if (this.scope === 'local') { if (this.scope === 'local') {
isLocal = true isLocal = true
} }
if (this.allVideos) { if (this.allVideos) {
include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY include = VideoInclude.NOT_PUBLISHED_STATE
privacyOneOf = getAllPrivacies()
} }
let isLive: boolean let isLive: boolean
@ -219,6 +221,7 @@ export class VideoFilters {
search: this.search, search: this.search,
isLocal, isLocal,
include, include,
privacyOneOf,
isLive isLive
} }
} }

View File

@ -10,7 +10,7 @@ import { HttpStatusCode } from '@shared/models'
import { root } from '../helpers/core-utils' import { root } from '../helpers/core-utils'
import { STATIC_MAX_AGE } from '../initializers/constants' import { STATIC_MAX_AGE } from '../initializers/constants'
import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html' import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/client-html'
import { asyncMiddleware, disableRobots, embedCSP } from '../middlewares' import { asyncMiddleware, embedCSP } from '../middlewares'
const clientsRouter = express.Router() const clientsRouter = express.Router()

View File

@ -16,6 +16,7 @@ function pickCommonVideoQuery (query: VideosCommonQueryAfterSanitize) {
'categoryOneOf', 'categoryOneOf',
'licenceOneOf', 'licenceOneOf',
'languageOneOf', 'languageOneOf',
'privacyOneOf',
'tagsOneOf', 'tagsOneOf',
'tagsAllOf', 'tagsAllOf',
'isLocal', 'isLocal',

View File

@ -7,6 +7,7 @@ import { isAbleToUploadVideo } from '@server/lib/user'
import { getServerActor } from '@server/models/application/application' import { getServerActor } from '@server/models/application/application'
import { ExpressPromiseHandler } from '@server/types/express' import { ExpressPromiseHandler } from '@server/types/express'
import { MUserAccountId, MVideoFullLight } from '@server/types/models' import { MUserAccountId, MVideoFullLight } from '@server/types/models'
import { getAllPrivacies } from '@shared/core-utils'
import { VideoInclude } from '@shared/models' import { VideoInclude } from '@shared/models'
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared' import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes' import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
@ -487,6 +488,10 @@ const commonVideosFiltersValidator = [
.optional() .optional()
.customSanitizer(toArray) .customSanitizer(toArray)
.custom(isStringArray).withMessage('Should have a valid one of language array'), .custom(isStringArray).withMessage('Should have a valid one of language array'),
query('privacyOneOf')
.optional()
.customSanitizer(toArray)
.custom(isNumberArray).withMessage('Should have a valid one of privacy array'),
query('tagsOneOf') query('tagsOneOf')
.optional() .optional()
.customSanitizer(toArray) .customSanitizer(toArray)
@ -536,10 +541,12 @@ const commonVideosFiltersValidator = [
// FIXME: deprecated in 4.0, to remove // FIXME: deprecated in 4.0, to remove
{ {
if (req.query.filter === 'all-local') { if (req.query.filter === 'all-local') {
req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY req.query.include = VideoInclude.NOT_PUBLISHED_STATE
req.query.isLocal = true req.query.isLocal = true
req.query.privacyOneOf = getAllPrivacies()
} else if (req.query.filter === 'all') { } else if (req.query.filter === 'all') {
req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY req.query.include = VideoInclude.NOT_PUBLISHED_STATE
req.query.privacyOneOf = getAllPrivacies()
} else if (req.query.filter === 'local') { } else if (req.query.filter === 'local') {
req.query.isLocal = true req.query.isLocal = true
} }
@ -550,7 +557,7 @@ const commonVideosFiltersValidator = [
const user = res.locals.oauth?.token.User const user = res.locals.oauth?.token.User
if ((!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) { if ((!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) {
if (req.query.include) { if (req.query.include || req.query.privacyOneOf) {
return res.fail({ return res.fail({
status: HttpStatusCode.UNAUTHORIZED_401, status: HttpStatusCode.UNAUTHORIZED_401,
message: 'You are not allowed to see all videos.' message: 'You are not allowed to see all videos.'

View File

@ -40,6 +40,7 @@ export type BuildVideosListQueryOptions = {
languageOneOf?: string[] languageOneOf?: string[]
tagsOneOf?: string[] tagsOneOf?: string[]
tagsAllOf?: string[] tagsAllOf?: string[]
privacyOneOf?: VideoPrivacy[]
uuids?: string[] uuids?: string[]
@ -138,11 +139,6 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
this.whereStateAvailable() this.whereStateAvailable()
} }
// Only list videos with the appropriate priavcy
if (!(options.include & VideoInclude.HIDDEN_PRIVACY)) {
this.wherePrivacyAvailable(options.user)
}
if (options.videoPlaylistId) { if (options.videoPlaylistId) {
this.joinPlaylist(options.videoPlaylistId) this.joinPlaylist(options.videoPlaylistId)
} }
@ -187,6 +183,13 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
this.whereTagsAllOf(options.tagsAllOf) this.whereTagsAllOf(options.tagsAllOf)
} }
if (options.privacyOneOf) {
this.wherePrivacyOneOf(options.privacyOneOf)
} else {
// Only list videos with the appropriate priavcy
this.wherePrivacyAvailable(options.user)
}
if (options.uuids) { if (options.uuids) {
this.whereUUIDs(options.uuids) this.whereUUIDs(options.uuids)
} }
@ -435,6 +438,11 @@ export class VideosIdListQueryBuilder extends AbstractRunQuery {
) )
} }
private wherePrivacyOneOf (privacyOneOf: VideoPrivacy[]) {
this.and.push('"video"."privacy" IN (:privacyOneOf)')
this.replacements.privacyOneOf = privacyOneOf
}
private whereUUIDs (uuids: string[]) { private whereUUIDs (uuids: string[]) {
this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')') this.and.push('"video"."uuid" IN (' + createSafeIn(this.sequelize, uuids) + ')')
} }

View File

@ -1041,6 +1041,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
languageOneOf?: string[] languageOneOf?: string[]
tagsOneOf?: string[] tagsOneOf?: string[]
tagsAllOf?: string[] tagsAllOf?: string[]
privacyOneOf?: VideoPrivacy[]
accountId?: number accountId?: number
videoChannelId?: number videoChannelId?: number
@ -1059,6 +1060,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
search?: string search?: string
}) { }) {
VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user) VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user)
const trendingDays = options.sort.endsWith('trending') const trendingDays = options.sort.endsWith('trending')
? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
@ -1082,6 +1084,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
'languageOneOf', 'languageOneOf',
'tagsOneOf', 'tagsOneOf',
'tagsAllOf', 'tagsAllOf',
'privacyOneOf',
'isLocal', 'isLocal',
'include', 'include',
'displayOnlyForFollower', 'displayOnlyForFollower',
@ -1119,6 +1122,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
languageOneOf?: string[] languageOneOf?: string[]
tagsOneOf?: string[] tagsOneOf?: string[]
tagsAllOf?: string[] tagsAllOf?: string[]
privacyOneOf?: VideoPrivacy[]
displayOnlyForFollower: DisplayOnlyForFollowerOptions | null displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
@ -1140,6 +1144,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
uuids?: string[] uuids?: string[]
}) { }) {
VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user) VideoModel.throwIfPrivateIncludeWithoutUser(options.include, options.user)
VideoModel.throwIfPrivacyOneOfWithoutUser(options.privacyOneOf, options.user)
const serverActor = await getServerActor() const serverActor = await getServerActor()
@ -1153,6 +1158,7 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
'languageOneOf', 'languageOneOf',
'tagsOneOf', 'tagsOneOf',
'tagsAllOf', 'tagsAllOf',
'privacyOneOf',
'user', 'user',
'isLocal', 'isLocal',
'host', 'host',
@ -1510,14 +1516,19 @@ export class VideoModel extends Model<Partial<AttributesOnly<VideoModel>>> {
private static throwIfPrivateIncludeWithoutUser (include: VideoInclude, user: MUserAccountId) { private static throwIfPrivateIncludeWithoutUser (include: VideoInclude, user: MUserAccountId) {
if (VideoModel.isPrivateInclude(include) && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) { if (VideoModel.isPrivateInclude(include) && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) {
throw new Error('Try to filter all-local but no user has not the see all videos right') throw new Error('Try to filter all-local but user cannot see all videos')
}
}
private static throwIfPrivacyOneOfWithoutUser (privacyOneOf: VideoPrivacy[], user: MUserAccountId) {
if (privacyOneOf && !user?.hasRight(UserRight.SEE_ALL_VIDEOS)) {
throw new Error('Try to choose video privacies but user cannot see all videos')
} }
} }
private static isPrivateInclude (include: VideoInclude) { private static isPrivateInclude (include: VideoInclude) {
return include & VideoInclude.BLACKLISTED || return include & VideoInclude.BLACKLISTED ||
include & VideoInclude.BLOCKED_OWNER || include & VideoInclude.BLOCKED_OWNER ||
include & VideoInclude.HIDDEN_PRIVACY ||
include & VideoInclude.NOT_PUBLISHED_STATE include & VideoInclude.NOT_PUBLISHED_STATE
} }

View File

@ -9,7 +9,7 @@ import {
setAccessTokensToServers, setAccessTokensToServers,
setDefaultVideoChannel setDefaultVideoChannel
} from '@shared/extra-utils' } from '@shared/extra-utils'
import { HttpStatusCode, UserRole, VideoInclude } from '@shared/models' import { HttpStatusCode, UserRole, VideoInclude, VideoPrivacy } from '@shared/models'
describe('Test video filters validators', function () { describe('Test video filters validators', function () {
let server: PeerTubeServer let server: PeerTubeServer
@ -112,7 +112,7 @@ describe('Test video filters validators', function () {
const validIncludes = [ const validIncludes = [
VideoInclude.NONE, VideoInclude.NONE,
VideoInclude.HIDDEN_PRIVACY, VideoInclude.BLOCKED_OWNER,
VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED
] ]
@ -120,6 +120,7 @@ describe('Test video filters validators', function () {
token?: string token?: string
isLocal?: boolean isLocal?: boolean
include?: VideoInclude include?: VideoInclude
privacyOneOf?: VideoPrivacy[]
expectedStatus: HttpStatusCode expectedStatus: HttpStatusCode
}) { }) {
const paths = [ const paths = [
@ -136,6 +137,7 @@ describe('Test video filters validators', function () {
token: options.token || server.accessToken, token: options.token || server.accessToken,
query: { query: {
isLocal: options.isLocal, isLocal: options.isLocal,
privacyOneOf: options.privacyOneOf,
include: options.include include: options.include
}, },
expectedStatus: options.expectedStatus expectedStatus: options.expectedStatus
@ -143,6 +145,22 @@ describe('Test video filters validators', function () {
} }
} }
it('Should fail with a bad privacyOneOf', async function () {
await testEndpoints({ privacyOneOf: [ 'toto' ] as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
})
it('Should succeed with a good privacyOneOf', async function () {
await testEndpoints({ privacyOneOf: [ VideoPrivacy.INTERNAL ], expectedStatus: HttpStatusCode.OK_200 })
})
it('Should fail to use privacyOneOf with a simple user', async function () {
await testEndpoints({
privacyOneOf: [ VideoPrivacy.INTERNAL ],
token: userAccessToken,
expectedStatus: HttpStatusCode.UNAUTHORIZED_401
})
})
it('Should fail with a bad include', async function () { it('Should fail with a bad include', async function () {
await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 }) await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
}) })

View File

@ -138,6 +138,7 @@ describe('Test videos filter', function () {
hasWebtorrentFiles?: boolean hasWebtorrentFiles?: boolean
hasHLSFiles?: boolean hasHLSFiles?: boolean
include?: VideoInclude include?: VideoInclude
privacyOneOf?: VideoPrivacy[]
category?: number category?: number
tagsAllOf?: string[] tagsAllOf?: string[]
token?: string token?: string
@ -148,7 +149,7 @@ describe('Test videos filter', function () {
path: options.path, path: options.path,
token: options.token ?? options.server.accessToken, token: options.token ?? options.server.accessToken,
query: { query: {
...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf', 'hasWebtorrentFiles', 'hasHLSFiles' ]), ...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf', 'hasWebtorrentFiles', 'hasHLSFiles', 'privacyOneOf' ]),
sort: 'createdAt' sort: 'createdAt'
}, },
@ -162,6 +163,7 @@ describe('Test videos filter', function () {
server: PeerTubeServer server: PeerTubeServer
isLocal?: boolean isLocal?: boolean
include?: VideoInclude include?: VideoInclude
privacyOneOf?: VideoPrivacy[]
token?: string token?: string
expectedStatus?: HttpStatusCode expectedStatus?: HttpStatusCode
}) { }) {
@ -195,7 +197,7 @@ describe('Test videos filter', function () {
server, server,
token, token,
isLocal: true, isLocal: true,
include: VideoInclude.HIDDEN_PRIVACY privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ]
}) })
for (const names of namesResults) { for (const names of namesResults) {
@ -216,7 +218,7 @@ describe('Test videos filter', function () {
const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({ const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({
server, server,
token, token,
include: VideoInclude.HIDDEN_PRIVACY privacyOneOf: [ VideoPrivacy.UNLISTED, VideoPrivacy.PUBLIC, VideoPrivacy.PRIVATE ]
}) })
expect(channelVideos).to.have.lengthOf(3) expect(channelVideos).to.have.lengthOf(3)

View File

@ -1 +1,2 @@
export * from './bitrate' export * from './bitrate'
export * from './privacy'

View File

@ -0,0 +1,9 @@
import { VideoPrivacy } from '../../models/videos/video-privacy.enum'
function getAllPrivacies () {
return [ VideoPrivacy.PUBLIC, VideoPrivacy.INTERNAL, VideoPrivacy.PRIVATE, VideoPrivacy.UNLISTED ]
}
export {
getAllPrivacies
}

View File

@ -1,3 +1,4 @@
import { VideoPrivacy } from '@shared/models'
import { VideoInclude } from '../videos/video-include.enum' import { VideoInclude } from '../videos/video-include.enum'
import { BooleanBothQuery } from './boolean-both-query.model' import { BooleanBothQuery } from './boolean-both-query.model'
@ -23,6 +24,8 @@ export interface VideosCommonQuery {
languageOneOf?: string[] languageOneOf?: string[]
privacyOneOf?: VideoPrivacy[]
tagsOneOf?: string[] tagsOneOf?: string[]
tagsAllOf?: string[] tagsAllOf?: string[]

View File

@ -1,8 +1,7 @@
export const enum VideoInclude { export const enum VideoInclude {
NONE = 0, NONE = 0,
NOT_PUBLISHED_STATE = 1 << 0, NOT_PUBLISHED_STATE = 1 << 0,
HIDDEN_PRIVACY = 1 << 1, BLACKLISTED = 1 << 1,
BLACKLISTED = 1 << 2, BLOCKED_OWNER = 1 << 2,
BLOCKED_OWNER = 1 << 3, FILES = 1 << 3
FILES = 1 << 4
} }

View File

@ -369,6 +369,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
- $ref: '#/components/parameters/skipCount' - $ref: '#/components/parameters/skipCount'
@ -1305,6 +1306,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
- $ref: '#/components/parameters/skipCount' - $ref: '#/components/parameters/skipCount'
@ -1628,6 +1630,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
- $ref: '#/components/parameters/skipCount' - $ref: '#/components/parameters/skipCount'
@ -2867,6 +2870,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
- $ref: '#/components/parameters/skipCount' - $ref: '#/components/parameters/skipCount'
@ -3590,6 +3594,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
- $ref: '#/components/parameters/skipCount' - $ref: '#/components/parameters/skipCount'
@ -4095,6 +4100,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
responses: responses:
@ -4179,6 +4185,7 @@ paths:
- $ref: '#/components/parameters/nsfw' - $ref: '#/components/parameters/nsfw'
- $ref: '#/components/parameters/isLocal' - $ref: '#/components/parameters/isLocal'
- $ref: '#/components/parameters/include' - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/privacyOneOf'
- $ref: '#/components/parameters/hasHLSFiles' - $ref: '#/components/parameters/hasHLSFiles'
- $ref: '#/components/parameters/hasWebtorrentFiles' - $ref: '#/components/parameters/hasWebtorrentFiles'
responses: responses:
@ -4834,6 +4841,13 @@ components:
schema: schema:
type: boolean type: boolean
description: '**PeerTube >= 4.0** Display only videos that have WebTorrent files' description: '**PeerTube >= 4.0** Display only videos that have WebTorrent files'
privacyOneOf:
name: privacyOneOf
in: query
required: false
schema:
$ref: '#/components/schemas/VideoPrivacySet'
description: '**PeerTube >= 4.0** Display only videos in this specific privacy/privacies'
include: include:
name: include name: include
in: query in: query
@ -4853,11 +4867,11 @@ components:
- `1` NOT_PUBLISHED_STATE - `1` NOT_PUBLISHED_STATE
- `2` HIDDEN_PRIVACY - `2` BLACKLISTED
- `4` BLACKLISTED - `4` BLOCKED_OWNER
- `8` BLOCKED - `8` FILES
subscriptionsUris: subscriptionsUris:
name: uris name: uris
in: query in: query