<my-video-cell [video]="video"></my-video-cell>
</td>
- <td>
- <span class="badge badge-blue" i18n>{{ video.privacy.label }}</span>
+ <td class="badges">
+ <span [ngClass]="getPrivacyBadgeClass(video.privacy.id)" class="badge" i18n>{{ video.privacy.label }}</span>
+
<span *ngIf="video.nsfw" class="badge badge-red" i18n>NSFW</span>
- <span *ngIf="video.blocked" class="badge badge-red" i18n>NSFW</span>
+
+ <span *ngIf="isUnpublished(video.state.id)" class="badge badge-yellow" i18n>Not published yet</span>
+
+ <span *ngIf="isAccountBlocked(video)" class="badge badge-red" i18n>Account muted</span>
+ <span *ngIf="isServerBlocked(video)" class="badge badge-red" i18n>Server muted</span>
+
+ <span *ngIf="isVideoBlocked(video)" class="badge badge-red" i18n>Blocked</span>
</td>
<td>
.badge {
@include table-badge;
+
+ margin-right: 5px;
}
import { ActivatedRoute, Router } from '@angular/router'
import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core'
import { DropdownAction, Video, VideoService } from '@app/shared/shared-main'
-import { UserRight } from '@shared/models'
+import { UserRight, VideoPrivacy, VideoState } from '@shared/models'
import { AdvancedInputFilter } from '@app/shared/shared-forms'
import { VideoActionsDisplayType } from '@app/shared/shared-video-miniature'
title: $localize`Advanced filters`,
children: [
{
- queryParams: { search: 'local:true' },
- label: $localize`Only local videos`
+ queryParams: { search: 'isLocal:false' },
+ label: $localize`Remote videos`
+ },
+ {
+ queryParams: { search: 'isLocal:true' },
+ label: $localize`Local videos`
}
]
}
this.reloadData()
}
+ getPrivacyBadgeClass (privacy: VideoPrivacy) {
+ if (privacy === VideoPrivacy.PUBLIC) return 'badge-blue'
+
+ return 'badge-yellow'
+ }
+
+ isUnpublished (state: VideoState) {
+ return state !== VideoState.PUBLISHED
+ }
+
+ isAccountBlocked (video: Video) {
+ return video.blockedOwner
+ }
+
+ isServerBlocked (video: Video) {
+ return video.blockedServer
+ }
+
+ isVideoBlocked (video: Video) {
+ return video.blacklisted
+ }
+
protected reloadData () {
this.selectedVideos = []
<div class="alert alert-danger" *ngIf="video?.blacklisted">
<div class="blocked-label" i18n>This video is blocked.</div>
- {{ video.blockedReason }}
+ {{ video.blacklistedReason }}
</div>
getSyndicationItems (filters: VideoFilters) {
const result = filters.toVideosAPIObject()
- return this.videoService.getVideoFeedUrls(result.sort, result.filter)
+ return this.videoService.getVideoFeedUrls(result.sort, result.isLocal)
}
onFiltersChanged (filters: VideoFilters) {
ContainerMarkupData,
EmbedMarkupData,
PlaylistMiniatureMarkupData,
- VideoFilter,
VideoMiniatureMarkupData,
VideosListMarkupData
} from '@shared/models'
isLive: this.buildBoolean(data.isLive),
- filter: this.buildBoolean(data.onlyLocal) ? 'local' as VideoFilter : undefined
+ isLocal: this.buildBoolean(data.onlyLocal) ? true : undefined
}
this.dynamicElementService.setModel(component, model)
import { finalize } from 'rxjs/operators'
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'
import { AuthService, Notifier } from '@app/core'
-import { VideoFilter, VideoSortField } from '@shared/models'
+import { VideoSortField } from '@shared/models'
import { Video, VideoService } from '../../shared-main'
import { MiniatureDisplayOptions } from '../../shared-video-miniature'
import { CustomMarkupComponent } from './shared'
@Input() languageOneOf: string[]
@Input() count: number
@Input() onlyDisplayTitle: boolean
- @Input() filter: VideoFilter
+ @Input() isLocal: boolean
@Input() isLive: boolean
@Input() maxRows: number
@Input() channelHandle: string
},
categoryOneOf: this.categoryOneOf,
languageOneOf: this.languageOneOf,
- filter: this.filter,
+ isLocal: this.isLocal,
isLive: this.isLive,
sort: this.sort as VideoSortField,
account: { nameWithHost: this.accountHandle },
waitTranscoding?: boolean
state?: VideoConstant<VideoState>
scheduledUpdate?: VideoScheduleUpdate
+
blacklisted?: boolean
- blockedReason?: string
+ blacklistedReason?: string
+
+ blockedOwner?: boolean
+ blockedServer?: boolean
account: {
id: number
if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
this.blacklisted = hash.blacklisted
- this.blockedReason = hash.blacklistedReason
+ this.blacklistedReason = hash.blacklistedReason
+
+ this.blockedOwner = hash.blockedOwner
+ this.blockedServer = hash.blockedServer
this.userHistory = hash.userHistory
VideoConstant,
VideoDetails as VideoDetailsServerModel,
VideoFileMetadata,
- VideoFilter,
+ VideoInclude,
VideoPrivacy,
VideoSortField,
VideoUpdate
export type CommonVideoParams = {
videoPagination?: ComponentPaginationLight
sort: VideoSortField | SortMeta
- filter?: VideoFilter
+ include?: VideoInclude
+ isLocal?: boolean
categoryOneOf?: number[]
languageOneOf?: string[]
isLive?: boolean
skipCount?: boolean
+
// FIXME: remove?
nsfwPolicy?: NSFWPolicyType
nsfw?: BooleanBothQuery
}
getAdminVideos (
- parameters: Omit<CommonVideoParams, 'filter'> & { pagination: RestPagination, search?: string }
+ parameters: CommonVideoParams & { pagination: RestPagination, search?: string }
): Observable<ResultList<Video>> {
const { pagination, search } = parameters
+ const include = VideoInclude.BLACKLISTED | VideoInclude.BLOCKED_OWNER | VideoInclude.HIDDEN_PRIVACY | VideoInclude.NOT_PUBLISHED_STATE
+
let params = new HttpParams()
- params = this.buildCommonVideosParams({ params, ...parameters })
+ params = this.buildCommonVideosParams({ params, include, ...parameters })
params = params.set('start', pagination.start.toString())
.set('count', pagination.count.toString())
params = this.buildAdminParamsFromSearch(search, params)
}
- if (!params.has('filter')) params = params.set('filter', 'all')
-
return this.authHttp
.get<ResultList<Video>>(VideoService.BASE_VIDEO_URL, { params })
.pipe(
return feeds
}
- getVideoFeedUrls (sort: VideoSortField, filter?: VideoFilter, categoryOneOf?: number[]) {
+ getVideoFeedUrls (sort: VideoSortField, isLocal: boolean, categoryOneOf?: number[]) {
let params = this.restService.addRestGetParams(new HttpParams(), undefined, sort)
- if (filter) params = params.set('filter', filter)
+ if (isLocal) params = params.set('isLocal', isLocal)
if (categoryOneOf) {
for (const c of categoryOneOf) {
params,
videoPagination,
sort,
- filter,
+ isLocal,
+ include,
categoryOneOf,
languageOneOf,
skipCount,
let newParams = this.restService.addRestGetParams(params, pagination, sort)
- if (filter) newParams = newParams.set('filter', filter)
if (skipCount) newParams = newParams.set('skipCount', skipCount + '')
+ if (isLocal) newParams = newParams.set('isLocal', isLocal)
+ if (include) newParams = newParams.set('include', include)
if (isLive) newParams = newParams.set('isLive', isLive)
if (nsfw) newParams = newParams.set('nsfw', nsfw)
if (nsfwPolicy) newParams = newParams.set('nsfw', this.nsfwPolicyToParam(nsfwPolicy))
private buildAdminParamsFromSearch (search: string, params: HttpParams) {
const filters = this.restService.parseQueryStringFilter(search, {
- filter: {
- prefix: 'local:',
- handler: v => {
- if (v === 'true') return 'all-local'
-
- return 'all'
- }
+ isLocal: {
+ prefix: 'isLocal:',
+ isBoolean: true
}
})
this.hide()
this.video.blacklisted = true
- this.video.blockedReason = reason
+ this.video.blacklistedReason = reason
this.videoBlocked.emit()
},
this.notifier.success($localize`Video ${this.video.name} unblocked.`)
this.video.blacklisted = false
- this.video.blockedReason = null
+ this.video.blacklistedReason = null
this.videoUnblocked.emit()
},
import { intoArray, toBoolean } from '@app/helpers'
import { AttributesOnly } from '@shared/core-utils'
-import { BooleanBothQuery, NSFWPolicyType, VideoFilter, VideoSortField } from '@shared/models'
+import { BooleanBothQuery, NSFWPolicyType, VideoInclude, VideoSortField } from '@shared/models'
type VideoFiltersKeys = {
[ id in keyof AttributesOnly<VideoFilters> ]: any
}
toVideosAPIObject () {
- let filter: VideoFilter
-
- if (this.scope === 'local' && this.allVideos) {
- filter = 'all-local'
- } else if (this.scope === 'federated' && this.allVideos) {
- filter = 'all'
- } else if (this.scope === 'local') {
- filter = 'local'
+ let isLocal: boolean
+ let include: VideoInclude
+
+ if (this.scope === 'local') {
+ isLocal = true
+ }
+
+ if (this.allVideos) {
+ include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY
}
let isLive: boolean
languageOneOf: this.languageOneOf,
categoryOneOf: this.categoryOneOf,
search: this.search,
- filter,
+ isLocal,
+ include,
isLive
}
}
<div *ngIf="displayOptions.blacklistInfo && video.blacklisted" class="video-info-blocked">
<span class="blocked-label" i18n>Blocked</span>
- <span class="blocked-reason" *ngIf="video.blockedReason">{{ video.blockedReason }}</span>
+ <span class="blocked-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
</div>
<div i18n *ngIf="displayOptions.nsfw && video.nsfw" class="video-info-nsfw">
import { pickCommonVideoQuery } from '@server/helpers/query'
import { ActorFollowModel } from '@server/models/actor/actor-follow'
import { getServerActor } from '@server/models/application/application'
+import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
import { getFormattedObjects } from '../../helpers/utils'
import { JobQueue } from '../../lib/job-queue'
}
async function listAccountVideos (req: express.Request, res: express.Response) {
+ const serverActor = await getServerActor()
+
const account = res.locals.account
- const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
+
+ const displayOnlyForFollower = isUserAbleToSearchRemoteURI(res)
+ ? null
+ : {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ }
+
const countVideos = getCountVideos(req)
const query = pickCommonVideoQuery(req.query)
const apiOptions = await Hooks.wrapObject({
...query,
- followerActorId,
- search: req.query.search,
- includeLocalVideos: true,
+ displayOnlyForFollower,
nsfw: buildNSFWFilter(res, query.nsfw),
withFiles: false,
accountId: account.id,
'filter:api.accounts.videos.list.result'
)
- return res.json(getFormattedObjects(resultList.data, resultList.total))
+ return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
}
async function listAccountRatings (req: express.Request, res: express.Response) {
import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants'
import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares'
import { TagModel } from '../../models/video/tag'
+import { getServerActor } from '@server/models/application/application'
const overviewsRouter = express.Router()
res: express.Response,
where: { videoChannelId?: number, tagsOneOf?: string[], categoryOneOf?: number[] }
) {
+ const serverActor = await getServerActor()
+
const query = await Hooks.wrapObject({
start: 0,
count: 12,
sort: '-createdAt',
- includeLocalVideos: true,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ },
nsfw: buildNSFWFilter(res),
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
withFiles: false,
import { getOrCreateAPVideo } from '@server/lib/activitypub/videos'
import { Hooks } from '@server/lib/plugins/hooks'
import { buildMutedForSearchIndex, isSearchIndexSearch, isURISearch } from '@server/lib/search'
+import { getServerActor } from '@server/models/application/application'
+import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
import { HttpStatusCode, ResultList, Video } from '@shared/models'
import { VideosSearchQueryAfterSanitize } from '../../../../shared/models/search'
import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../../helpers/express-utils'
}
async function searchVideosDB (query: VideosSearchQueryAfterSanitize, res: express.Response) {
+ const serverActor = await getServerActor()
+
const apiOptions = await Hooks.wrapObject({
...query,
- includeLocalVideos: true,
- filter: query.filter,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ },
nsfw: buildNSFWFilter(res, query.nsfw),
user: res.locals.oauth
'filter:api.search.videos.local.list.result'
)
- return res.json(getFormattedObjects(resultList.data, resultList.total))
+ return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
}
async function searchVideoURI (url: string, res: express.Response) {
import express from 'express'
import { pickCommonVideoQuery } from '@server/helpers/query'
import { sendUndoFollow } from '@server/lib/activitypub/send'
+import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
import { VideoChannelModel } from '@server/models/video/video-channel'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils'
const resultList = await VideoModel.listForApi({
...query,
- includeLocalVideos: false,
+ displayOnlyForFollower: {
+ actorId: user.Account.Actor.id,
+ orLocalVideos: false
+ },
nsfw: buildNSFWFilter(res, query.nsfw),
withFiles: false,
- followerActorId: user.Account.Actor.id,
user,
countVideos
})
- return res.json(getFormattedObjects(resultList.data, resultList.total))
+ return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
}
import { Hooks } from '@server/lib/plugins/hooks'
import { ActorFollowModel } from '@server/models/actor/actor-follow'
import { getServerActor } from '@server/models/application/application'
+import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
import { MChannelBannerAccountDefault } from '@server/types/models'
import { ActorImageType, VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
import { HttpStatusCode } from '../../../shared/models/http/http-error-codes'
}
async function listVideoChannelVideos (req: express.Request, res: express.Response) {
+ const serverActor = await getServerActor()
+
const videoChannelInstance = res.locals.videoChannel
- const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
+
+ const displayOnlyForFollower = isUserAbleToSearchRemoteURI(res)
+ ? null
+ : {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ }
+
const countVideos = getCountVideos(req)
const query = pickCommonVideoQuery(req.query)
const apiOptions = await Hooks.wrapObject({
...query,
- followerActorId,
- includeLocalVideos: true,
+ displayOnlyForFollower,
nsfw: buildNSFWFilter(res, query.nsfw),
withFiles: false,
videoChannelId: videoChannelInstance.id,
'filter:api.video-channels.videos.list.result'
)
- return res.json(getFormattedObjects(resultList.data, resultList.total))
+ return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
}
async function listVideoChannelFollowers (req: express.Request, res: express.Response) {
import { LiveManager } from '@server/lib/live'
import { openapiOperationDoc } from '@server/middlewares/doc'
import { getServerActor } from '@server/models/application/application'
+import { guessAdditionalAttributesFromQuery } from '@server/models/video/formatter/video-format-utils'
import { MVideoAccountLight } from '@server/types/models'
import { HttpStatusCode } from '../../../../shared/models'
import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
}
async function listVideos (req: express.Request, res: express.Response) {
+ const serverActor = await getServerActor()
+
const query = pickCommonVideoQuery(req.query)
const countVideos = getCountVideos(req)
const apiOptions = await Hooks.wrapObject({
...query,
- includeLocalVideos: true,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ },
nsfw: buildNSFWFilter(res, query.nsfw),
withFiles: false,
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
'filter:api.videos.list.result'
)
- return res.json(getFormattedObjects(resultList.data, resultList.total))
+ return res.json(getFormattedObjects(resultList.data, resultList.total, guessAdditionalAttributesFromQuery(query)))
}
async function removeVideo (_req: express.Request, res: express.Response) {
+import { getServerActor } from '@server/models/application/application'
import express from 'express'
import { truncate } from 'lodash'
import { SitemapStream, streamToPromise } from 'sitemap'
}
async function getSitemapLocalVideoUrls () {
+ const serverActor = await getServerActor()
+
const { data } = await VideoModel.listForApi({
start: 0,
count: undefined,
sort: 'createdAt',
- includeLocalVideos: true,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ },
+ isLocal: true,
nsfw: buildNSFWFilter(),
- filter: 'local',
withFiles: false,
countVideos: false
})
import express from 'express'
import Feed from 'pfeed'
+import { getServerActor } from '@server/models/application/application'
import { getCategoryLabel } from '@server/models/video/formatter/video-format-utils'
-import { VideoFilter } from '../../shared/models/videos/video-query.type'
import { buildNSFWFilter } from '../helpers/express-utils'
import { CONFIG } from '../initializers/config'
import { FEEDS, PREVIEWS_SIZE, ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
videoChannelId: videoChannel ? videoChannel.id : null
}
+ const server = await getServerActor()
const { data } = await VideoModel.listForApi({
start,
count: FEEDS.COUNT,
sort: req.query.sort,
- includeLocalVideos: true,
+ displayOnlyForFollower: {
+ actorId: server.id,
+ orLocalVideos: true
+ },
nsfw,
- filter: req.query.filter as VideoFilter,
+ isLocal: req.query.isLocal,
+ include: req.query.include,
withFiles: true,
countVideos: false,
...options
start,
count: FEEDS.COUNT,
sort: req.query.sort,
- includeLocalVideos: false,
nsfw,
- filter: req.query.filter as VideoFilter,
+
+ isLocal: req.query.isLocal,
+ include: req.query.include,
withFiles: true,
countVideos: false,
- followerActorId: res.locals.user.Account.Actor.id,
+ displayOnlyForFollower: {
+ actorId: res.locals.user.Account.Actor.id,
+ orLocalVideos: false
+ },
user: res.locals.user
})
import { values } from 'lodash'
import magnetUtil from 'magnet-uri'
import validator from 'validator'
+import { VideoInclude } from '@shared/models'
import { VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared'
import {
CONSTRAINTS_FIELDS,
return filter === 'local' || filter === 'all-local' || filter === 'all'
}
+function isVideoIncludeValid (include: VideoInclude) {
+ return exists(include) && validator.isInt('' + include)
+}
+
function isVideoCategoryValid (value: any) {
return value === null || VIDEO_CATEGORIES[value] !== undefined
}
isVideoOriginallyPublishedAtValid,
isVideoMagnetUriValid,
isVideoStateValid,
+ isVideoIncludeValid,
isVideoViewsValid,
isVideoRatingTypeValid,
isVideoFileExtnameValid,
'languageOneOf',
'tagsOneOf',
'tagsAllOf',
- 'filter',
- 'skipCount'
+ 'isLocal',
+ 'include',
+ 'skipCount',
+ 'search'
])
}
...pick(query, [
'searchTarget',
- 'search',
'host',
'startDate',
'endDate',
import { getServerActor } from '@server/models/application/application'
import { ExpressPromiseHandler } from '@server/types/express'
import { MUserAccountId, MVideoFullLight } from '@server/types/models'
+import { VideoInclude } from '@shared/models'
import { ServerErrorCode, UserRight, VideoPrivacy } from '../../../../shared'
import { HttpStatusCode } from '../../../../shared/models/http/http-error-codes'
import {
isVideoFileSizeValid,
isVideoFilterValid,
isVideoImage,
+ isVideoIncludeValid,
isVideoLanguageValid,
isVideoLicenceValid,
isVideoNameValid,
query('filter')
.optional()
.custom(isVideoFilterValid).withMessage('Should have a valid filter attribute'),
+ query('include')
+ .optional()
+ .custom(isVideoIncludeValid).withMessage('Should have a valid include attribute'),
+ query('isLocal')
+ .optional()
+ .customSanitizer(toBooleanOrNull)
+ .custom(isBooleanValid).withMessage('Should have a valid local boolean'),
query('skipCount')
.optional()
.customSanitizer(toBooleanOrNull)
if (areValidationErrors(req, res)) return
- const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
- if (
- (req.query.filter === 'all-local' || req.query.filter === 'all') &&
- (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)
- ) {
+ // FIXME: deprecated in 4.0, to remove
+ {
+ if (req.query.filter === 'all-local') {
+ req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY
+ req.query.isLocal = true
+ } else if (req.query.filter === 'all') {
+ req.query.include = VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.HIDDEN_PRIVACY
+ } else if (req.query.filter === 'local') {
+ req.query.isLocal = true
+ }
+
+ req.query.filter = undefined
+ }
+
+ const user = res.locals.oauth?.token.User
+
+ if (req.query.include && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) !== true)) {
res.fail({
status: HttpStatusCode.UNAUTHORIZED_401,
message: 'You are not allowed to see all local videos.'
name: 'targetAccountId',
allowNull: false
},
- as: 'BlockedAccounts',
+ as: 'BlockedBy',
onDelete: 'CASCADE'
})
- BlockedAccounts: AccountBlocklistModel[]
+ BlockedBy: AccountBlocklistModel[]
@BeforeDestroy
static async sendDeleteIfOwned (instance: AccountModel, options) {
}
isBlocked () {
- return this.BlockedAccounts && this.BlockedAccounts.length !== 0
+ return this.BlockedBy && this.BlockedBy.length !== 0
}
}
},
onDelete: 'CASCADE'
})
- BlockedByAccounts: ServerBlocklistModel[]
+ BlockedBy: ServerBlocklistModel[]
static load (id: number, transaction?: Transaction): Promise<MServer> {
const query = {
}
isBlocked () {
- return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0
+ return this.BlockedBy && this.BlockedBy.length !== 0
}
toFormattedJSON (this: MServerFormattable) {
import { AttributesOnly } from '@shared/core-utils'
import { VideoModel } from '../video/video'
import { UserModel } from './user'
+import { getServerActor } from '../application/application'
@Table({
tableName: 'userVideoHistory',
})
User: UserModel
- static listForApi (user: MUserAccountId, start: number, count: number, search?: string) {
+ static async listForApi (user: MUserAccountId, start: number, count: number, search?: string) {
+ const serverActor = await getServerActor()
+
return VideoModel.listForApi({
start,
count,
search,
sort: '-"userVideoHistory"."updatedAt"',
nsfw: null, // All
- includeLocalVideos: true,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ },
withFiles: false,
user,
historyOfUser: user
import { uuidToShort } from '@server/helpers/uuid'
import { generateMagnetUri } from '@server/helpers/webtorrent'
import { getLocalVideoFileMetadataUrl } from '@server/lib/video-urls'
+import { VideosCommonQueryAfterSanitize } from '@shared/models'
import { VideoFile } from '@shared/models/videos/video-file.model'
import { ActivityTagObject, ActivityUrlObject, VideoObject } from '../../../../shared/models/activitypub/objects'
-import { Video, VideoDetails } from '../../../../shared/models/videos'
+import { Video, VideoDetails, VideoInclude } from '../../../../shared/models/videos'
import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model'
import { isArray } from '../../../helpers/custom-validators/misc'
import {
getLocalVideoSharesActivityPubUrl
} from '../../../lib/activitypub/url'
import {
+ MServer,
MStreamingPlaylistRedundanciesOpt,
MVideo,
MVideoAP,
export type VideoFormattingJSONOptions = {
completeDescription?: boolean
- additionalAttributes: {
+
+ additionalAttributes?: {
state?: boolean
waitTranscoding?: boolean
scheduledUpdate?: boolean
blacklistInfo?: boolean
+ blockedOwner?: boolean
}
}
-function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video {
+function guessAdditionalAttributesFromQuery (query: VideosCommonQueryAfterSanitize): VideoFormattingJSONOptions {
+ if (!query || !query.include) return {}
+
+ return {
+ additionalAttributes: {
+ state: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
+ waitTranscoding: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
+ scheduledUpdate: !!(query.include & VideoInclude.NOT_PUBLISHED_STATE),
+ blacklistInfo: !!(query.include & VideoInclude.BLACKLISTED),
+ blockedOwner: !!(query.include & VideoInclude.BLOCKED_OWNER)
+ }
+ }
+}
+
+function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoFormattingJSONOptions = {}): Video {
const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined
const videoObject: Video = {
pluginData: (video as any).pluginData
}
- if (options) {
- if (options.additionalAttributes.state === true) {
- videoObject.state = {
- id: video.state,
- label: getStateLabel(video.state)
- }
+ const add = options.additionalAttributes
+ if (add?.state === true) {
+ videoObject.state = {
+ id: video.state,
+ label: getStateLabel(video.state)
}
+ }
- if (options.additionalAttributes.waitTranscoding === true) {
- videoObject.waitTranscoding = video.waitTranscoding
- }
+ if (add?.waitTranscoding === true) {
+ videoObject.waitTranscoding = video.waitTranscoding
+ }
- if (options.additionalAttributes.scheduledUpdate === true && video.ScheduleVideoUpdate) {
- videoObject.scheduledUpdate = {
- updateAt: video.ScheduleVideoUpdate.updateAt,
- privacy: video.ScheduleVideoUpdate.privacy || undefined
- }
+ if (add?.scheduledUpdate === true && video.ScheduleVideoUpdate) {
+ videoObject.scheduledUpdate = {
+ updateAt: video.ScheduleVideoUpdate.updateAt,
+ privacy: video.ScheduleVideoUpdate.privacy || undefined
}
+ }
- if (options.additionalAttributes.blacklistInfo === true) {
- videoObject.blacklisted = !!video.VideoBlacklist
- videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
- }
+ if (add?.blacklistInfo === true) {
+ videoObject.blacklisted = !!video.VideoBlacklist
+ videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null
+ }
+
+ if (add?.blockedOwner === true) {
+ videoObject.blockedOwner = video.VideoChannel.Account.isBlocked()
+
+ const server = video.VideoChannel.Account.Actor.Server as MServer
+ videoObject.blockedServer = !!(server?.isBlocked())
}
return videoObject
videoModelToActivityPubObject,
getActivityStreamDuration,
+ guessAdditionalAttributesFromQuery,
+
getCategoryLabel,
getLicenceLabel,
getLanguageLabel,
+import { createSafeIn } from '@server/models/utils'
+import { MUserAccountId } from '@server/types/models'
import validator from 'validator'
import { AbstractVideosQueryBuilder } from './abstract-videos-query-builder'
import { VideoTables } from './video-tables'
}
}
+ protected includeBlockedOwnerAndServer (serverAccountId: number, user?: MUserAccountId) {
+ const blockerIds = [ serverAccountId ]
+ if (user) blockerIds.push(user.Account.id)
+
+ const inClause = createSafeIn(this.sequelize, blockerIds)
+
+ this.addJoin(
+ 'LEFT JOIN "accountBlocklist" AS "VideoChannel->Account->AccountBlocklist" ' +
+ 'ON "VideoChannel->Account"."id" = "VideoChannel->Account->AccountBlocklist"."targetAccountId" ' +
+ 'AND "VideoChannel->Account->AccountBlocklist"."accountId" IN (' + inClause + ')'
+ )
+
+ this.addJoin(
+ 'LEFT JOIN "serverBlocklist" AS "VideoChannel->Account->Actor->Server->ServerBlocklist" ' +
+ 'ON "VideoChannel->Account->Actor->Server->ServerBlocklist"."targetServerId" = "VideoChannel->Account->Actor"."serverId" ' +
+ 'AND "VideoChannel->Account->Actor->Server->ServerBlocklist"."accountId" IN (' + inClause + ') '
+ )
+
+ this.attributes = {
+ ...this.attributes,
+
+ ...this.buildAttributesObject('VideoChannel->Account->AccountBlocklist', this.tables.getBlocklistAttributes()),
+ ...this.buildAttributesObject('VideoChannel->Account->Actor->Server->ServerBlocklist', this.tables.getBlocklistAttributes())
+ }
+ }
+
protected includeScheduleUpdate () {
this.addJoin(
'LEFT OUTER JOIN "scheduleVideoUpdate" AS "ScheduleVideoUpdate" ON "video"."id" = "ScheduleVideoUpdate"."videoId"'
import { AccountModel } from '@server/models/account/account'
+import { AccountBlocklistModel } from '@server/models/account/account-blocklist'
import { ActorModel } from '@server/models/actor/actor'
import { ActorImageModel } from '@server/models/actor/actor-image'
import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
import { ServerModel } from '@server/models/server/server'
+import { ServerBlocklistModel } from '@server/models/server/server-blocklist'
import { TrackerModel } from '@server/models/server/tracker'
import { UserVideoHistoryModel } from '@server/models/user/user-video-history'
+import { VideoInclude } from '@shared/models'
import { ScheduleVideoUpdateModel } from '../../schedule-video-update'
import { TagModel } from '../../tag'
import { ThumbnailModel } from '../../thumbnail'
private thumbnailsDone: Set<any>
private historyDone: Set<any>
private blacklistDone: Set<any>
+ private accountBlocklistDone: Set<any>
+ private serverBlocklistDone: Set<any>
private liveDone: Set<any>
private redundancyDone: Set<any>
private scheduleVideoUpdateDone: Set<any>
}
- buildVideosFromRows (rows: SQLRow[], rowsWebTorrentFiles?: SQLRow[], rowsStreamingPlaylist?: SQLRow[]) {
+ buildVideosFromRows (options: {
+ rows: SQLRow[]
+ include?: VideoInclude
+ rowsWebTorrentFiles?: SQLRow[]
+ rowsStreamingPlaylist?: SQLRow[]
+ }) {
+ const { rows, rowsWebTorrentFiles, rowsStreamingPlaylist, include } = options
+
this.reinit()
for (const row of rows) {
this.setBlacklisted(row, videoModel)
this.setScheduleVideoUpdate(row, videoModel)
this.setLive(row, videoModel)
+ } else {
+ if (include & VideoInclude.BLACKLISTED) {
+ this.setBlacklisted(row, videoModel)
+ }
+
+ if (include & VideoInclude.BLOCKED_OWNER) {
+ this.setBlockedOwner(row, videoModel)
+ this.setBlockedServer(row, videoModel)
+ }
}
}
this.videoStreamingPlaylistMemo = {}
this.videoFileMemo = {}
- this.thumbnailsDone = new Set<number>()
- this.historyDone = new Set<number>()
- this.blacklistDone = new Set<number>()
- this.liveDone = new Set<number>()
- this.redundancyDone = new Set<number>()
- this.scheduleVideoUpdateDone = new Set<number>()
+ this.thumbnailsDone = new Set()
+ this.historyDone = new Set()
+ this.blacklistDone = new Set()
+ this.liveDone = new Set()
+ this.redundancyDone = new Set()
+ this.scheduleVideoUpdateDone = new Set()
+
+ this.accountBlocklistDone = new Set()
+ this.serverBlocklistDone = new Set()
- this.trackersDone = new Set<string>()
- this.tagsDone = new Set<string>()
+ this.trackersDone = new Set()
+ this.tagsDone = new Set()
this.videos = []
}
const accountModel = new AccountModel(this.grab(row, this.tables.getAccountAttributes(), 'VideoChannel.Account'), this.buildOpts)
accountModel.Actor = this.buildActor(row, 'VideoChannel.Account')
+ accountModel.BlockedBy = []
+
channelModel.Account = accountModel
videoModel.VideoChannel = channelModel
? new ServerModel(this.grab(row, this.tables.getServerAttributes(), serverPrefix), this.buildOpts)
: null
+ if (serverModel) serverModel.BlockedBy = []
+
const actorModel = new ActorModel(this.grab(row, this.tables.getActorAttributes(), actorPrefix), this.buildOpts)
actorModel.Avatar = avatarModel
actorModel.Server = serverModel
this.blacklistDone.add(id)
}
+ private setBlockedOwner (row: SQLRow, videoModel: VideoModel) {
+ const id = row['VideoChannel.Account.AccountBlocklist.id']
+ if (!id) return
+
+ const key = `${videoModel.id}-${id}`
+ if (this.accountBlocklistDone.has(key)) return
+
+ const attributes = this.grab(row, this.tables.getBlocklistAttributes(), 'VideoChannel.Account.AccountBlocklist')
+ videoModel.VideoChannel.Account.BlockedBy.push(new AccountBlocklistModel(attributes, this.buildOpts))
+
+ this.accountBlocklistDone.add(key)
+ }
+
+ private setBlockedServer (row: SQLRow, videoModel: VideoModel) {
+ const id = row['VideoChannel.Account.Actor.Server.ServerBlocklist.id']
+ if (!id || this.serverBlocklistDone.has(id)) return
+
+ const key = `${videoModel.id}-${id}`
+ if (this.serverBlocklistDone.has(key)) return
+
+ const attributes = this.grab(row, this.tables.getBlocklistAttributes(), 'VideoChannel.Account.Actor.Server.ServerBlocklist')
+ videoModel.VideoChannel.Account.Actor.Server.BlockedBy.push(new ServerBlocklistModel(attributes, this.buildOpts))
+
+ this.serverBlocklistDone.add(key)
+ }
+
private setScheduleVideoUpdate (row: SQLRow, videoModel: VideoModel) {
const id = row['ScheduleVideoUpdate.id']
if (!id || this.scheduleVideoUpdateDone.has(id)) return
return [ 'id', 'reason', 'unfederated' ]
}
+ getBlocklistAttributes () {
+ return [ 'id' ]
+ }
+
getScheduleUpdateAttributes () {
return [
'id',
: Promise.resolve(undefined)
])
- const videos = this.videoModelBuilder.buildVideosFromRows(videoRows, webtorrentFilesRows, streamingPlaylistFilesRows)
+ const videos = this.videoModelBuilder.buildVideosFromRows({
+ rows: videoRows,
+ rowsWebTorrentFiles: webtorrentFilesRows,
+ rowsStreamingPlaylist: streamingPlaylistFilesRows
+ })
if (videos.length > 1) {
throw new Error('Video results is more than ')
import { WEBSERVER } from '@server/initializers/constants'
import { buildDirectionAndField, createSafeIn } from '@server/models/utils'
import { MUserAccountId, MUserId } from '@server/types/models'
-import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models'
+import { VideoInclude, VideoPrivacy, VideoState } from '@shared/models'
import { AbstractVideosQueryBuilder } from './shared/abstract-videos-query-builder'
/**
*
*/
+export type DisplayOnlyForFollowerOptions = {
+ actorId: number
+ orLocalVideos: boolean
+}
+
export type BuildVideosListQueryOptions = {
attributes?: string[]
- serverAccountId: number
- followerActorId: number
- includeLocalVideos: boolean
+ serverAccountIdForBlock: number
+
+ displayOnlyForFollower: DisplayOnlyForFollowerOptions
count: number
start: number
sort: string
nsfw?: boolean
- filter?: VideoFilter
host?: string
isLive?: boolean
+ isLocal?: boolean
+ include?: VideoInclude
categoryOneOf?: number[]
licenceOneOf?: number[]
getIdsListQueryAndSort (options: BuildVideosListQueryOptions) {
this.buildIdsListQuery(options)
+
return { query: this.query, sort: this.sort, replacements: this.replacements }
}
'INNER JOIN "actor" "accountActor" ON "account"."actorId" = "accountActor"."id"'
])
- this.whereNotBlacklisted()
+ if (!(options.include & VideoInclude.BLACKLISTED)) {
+ this.whereNotBlacklisted()
+ }
- if (options.serverAccountId) {
- this.whereNotBlocked(options.serverAccountId, options.user)
+ if (options.serverAccountIdForBlock && !(options.include & VideoInclude.BLOCKED_OWNER)) {
+ this.whereNotBlocked(options.serverAccountIdForBlock, options.user)
}
- // Only list public/published videos
- if (!options.filter || (options.filter !== 'all-local' && options.filter !== 'all')) {
- this.whereStateAndPrivacyAvailable(options.user)
+ // Only list published videos
+ if (!(options.include & VideoInclude.NOT_PUBLISHED_STATE)) {
+ this.whereStateAvailable()
+ }
+
+ // Only list videos with the appropriate priavcy
+ if (!(options.include & VideoInclude.HIDDEN_PRIVACY)) {
+ this.wherePrivacyAvailable(options.user)
}
if (options.videoPlaylistId) {
this.joinPlaylist(options.videoPlaylistId)
}
- if (options.filter && (options.filter === 'local' || options.filter === 'all-local')) {
- this.whereOnlyLocal()
+ if (exists(options.isLocal)) {
+ this.whereLocal(options.isLocal)
}
if (options.host) {
this.whereChannelId(options.videoChannelId)
}
- if (options.followerActorId) {
- this.whereFollowerActorId(options.followerActorId, options.includeLocalVideos)
+ if (options.displayOnlyForFollower) {
+ this.whereFollowerActorId(options.displayOnlyForFollower)
}
if (options.withFiles === true) {
this.replacements.videoPlaylistId = playlistId
}
- private whereStateAndPrivacyAvailable (user?: MUserAccountId) {
+ private whereStateAvailable () {
this.and.push(
`("video"."state" = ${VideoState.PUBLISHED} OR ` +
`("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))`
)
+ }
+ private wherePrivacyAvailable (user?: MUserAccountId) {
if (user) {
this.and.push(
`("video"."privacy" = ${VideoPrivacy.PUBLIC} OR "video"."privacy" = ${VideoPrivacy.INTERNAL})`
}
}
- private whereOnlyLocal () {
- this.and.push('"video"."remote" IS FALSE')
+ private whereLocal (isLocal: boolean) {
+ const isRemote = isLocal ? 'FALSE' : 'TRUE'
+
+ this.and.push('"video"."remote" IS ' + isRemote)
}
private whereHost (host: string) {
this.replacements.videoChannelId = channelId
}
- private whereFollowerActorId (followerActorId: number, includeLocalVideos: boolean) {
+ private whereFollowerActorId (options: { actorId: number, orLocalVideos: boolean }) {
let query =
'(' +
' EXISTS (' + // Videos shared by actors we follow
' AND "actorFollow"."state" = \'accepted\'' +
' )'
- if (includeLocalVideos) {
+ if (options.orLocalVideos) {
query += ' OR "video"."remote" IS FALSE'
}
query += ')'
this.and.push(query)
- this.replacements.followerActorId = followerActorId
+ this.replacements.followerActorId = options.actorId
}
private whereFileExists () {
+import { VideoInclude } from '@shared/models'
import { Sequelize } from 'sequelize'
import { AbstractVideosModelQueryBuilder } from './shared/abstract-videos-model-query-builder'
import { VideoModelBuilder } from './shared/video-model-builder'
this.buildListQueryFromIdsQuery(options)
return this.runQuery()
- .then(rows => this.videoModelBuilder.buildVideosFromRows(rows))
+ .then(rows => this.videoModelBuilder.buildVideosFromRows({ rows, include: options.include }))
}
private buildInnerQuery (options: BuildVideosListQueryOptions) {
this.includePlaylist(options.videoPlaylistId)
}
+ if (options.include & VideoInclude.BLACKLISTED) {
+ this.includeBlacklisted()
+ }
+
+ if (options.include & VideoInclude.BLOCKED_OWNER) {
+ this.includeBlockedOwnerAndServer(options.serverAccountIdForBlock, options.user)
+ }
+
const select = this.buildSelect()
this.query = `${select} FROM (${this.innerQuery}) AS "tmp" ${this.joins} ${this.innerSort}`
import { getServerActor } from '@server/models/application/application'
import { ModelCache } from '@server/models/model-cache'
import { AttributesOnly, buildVideoEmbedPath, buildVideoWatchPath, pick } from '@shared/core-utils'
+import { VideoInclude } from '@shared/models'
import { VideoFile } from '@shared/models/videos/video-file.model'
import { ResultList, UserRight, VideoPrivacy, VideoState } from '../../../shared'
import { VideoObject } from '../../../shared/models/activitypub/objects'
import { Video, VideoDetails, VideoRateType, VideoStorage } from '../../../shared/models/videos'
import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type'
-import { VideoFilter } from '../../../shared/models/videos/video-query.type'
import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type'
import { peertubeTruncate } from '../../helpers/core-utils'
import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
} from './formatter/video-format-utils'
import { ScheduleVideoUpdateModel } from './schedule-video-update'
import { VideosModelGetQueryBuilder } from './sql/video-model-get-query-builder'
-import { BuildVideosListQueryOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
+import { BuildVideosListQueryOptions, DisplayOnlyForFollowerOptions, VideosIdListQueryBuilder } from './sql/videos-id-list-query-builder'
import { VideosModelListQueryBuilder } from './sql/videos-model-list-query-builder'
import { TagModel } from './tag'
import { ThumbnailModel } from './thumbnail'
withAccountBlockerIds?: number[]
}
-export type AvailableForListIDsOptions = {
- serverAccountId: number
- followerActorId: number
- includeLocalVideos: boolean
-
- attributesType?: 'none' | 'id' | 'all'
-
- filter?: VideoFilter
- categoryOneOf?: number[]
- nsfw?: boolean
- licenceOneOf?: number[]
- languageOneOf?: string[]
- tagsOneOf?: string[]
- tagsAllOf?: string[]
-
- withFiles?: boolean
-
- accountId?: number
- videoChannelId?: number
-
- videoPlaylistId?: number
-
- trendingDays?: number
- user?: MUserAccountId
- historyOfUser?: MUserId
-
- baseWhere?: WhereOptions[]
-}
-
@Scopes(() => ({
[ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: {
attributes: [ 'id', 'url', 'uuid', 'remote' ]
sort: string
nsfw: boolean
- filter?: VideoFilter
isLive?: boolean
+ isLocal?: boolean
+ include?: VideoInclude
- includeLocalVideos: boolean
withFiles: boolean
categoryOneOf?: number[]
accountId?: number
videoChannelId?: number
- followerActorId?: number
+ displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
videoPlaylistId?: number
search?: string
}) {
- if ((options.filter === 'all-local' || options.filter === 'all') && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
+ if (options.include && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) {
throw new Error('Try to filter all-local but no user has not the see all videos right')
}
const serverActor = await getServerActor()
- // followerActorId === null has a meaning, so just check undefined
- const followerActorId = options.followerActorId !== undefined
- ? options.followerActorId
- : serverActor.id
-
const queryOptions = {
...pick(options, [
'start',
'languageOneOf',
'tagsOneOf',
'tagsAllOf',
- 'filter',
+ 'isLocal',
+ 'include',
+ 'displayOnlyForFollower',
'withFiles',
'accountId',
'videoChannelId',
'videoPlaylistId',
- 'includeLocalVideos',
'user',
'historyOfUser',
'search'
]),
- followerActorId,
- serverAccountId: serverActor.Account.id,
+ serverAccountIdForBlock: serverActor.Account.id,
trendingDays,
trendingAlgorithm
}
start: number
count: number
sort: string
- includeLocalVideos: boolean
search?: string
host?: string
startDate?: string // ISO 8601
originallyPublishedEndDate?: string
nsfw?: boolean
isLive?: boolean
+ isLocal?: boolean
+ include?: VideoInclude
categoryOneOf?: number[]
licenceOneOf?: number[]
languageOneOf?: string[]
durationMin?: number // seconds
durationMax?: number // seconds
user?: MUserAccountId
- filter?: VideoFilter
uuids?: string[]
+ displayOnlyForFollower: DisplayOnlyForFollowerOptions | null
}) {
const serverActor = await getServerActor()
const queryOptions = {
...pick(options, [
- 'includeLocalVideos',
+ 'include',
'nsfw',
'isLive',
'categoryOneOf',
'tagsOneOf',
'tagsAllOf',
'user',
- 'filter',
+ 'isLocal',
'host',
'start',
'count',
'durationMin',
'durationMax',
'uuids',
- 'search'
+ 'search',
+ 'displayOnlyForFollower'
]),
-
- followerActorId: serverActor.id,
- serverAccountId: serverActor.Account.id
+ serverAccountIdForBlock: serverActor.Account.id
}
return VideoModel.getAvailableForApi(queryOptions)
// Sequelize could return null...
if (!totalLocalVideoViews) totalLocalVideoViews = 0
+ const serverActor = await getServerActor()
+
const { total: totalVideos } = await VideoModel.listForApi({
start: 0,
count: 0,
sort: '-publishedAt',
nsfw: buildNSFWFilter(),
- includeLocalVideos: true,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ },
withFiles: false
})
// threshold corresponds to how many video the field should have to be returned
static async getRandomFieldSamples (field: 'category' | 'channelId', threshold: number, count: number) {
const serverActor = await getServerActor()
- const followerActorId = serverActor.id
const queryOptions: BuildVideosListQueryOptions = {
attributes: [ `"${field}"` ],
start: 0,
sort: 'random',
count,
- serverAccountId: serverActor.Account.id,
- followerActorId,
- includeLocalVideos: true
+ serverAccountIdForBlock: serverActor.Account.id,
+ displayOnlyForFollower: {
+ actorId: serverActor.id,
+ orLocalVideos: true
+ }
}
const queryBuilder = new VideosIdListQueryBuilder(VideoModel.sequelize)
import './video-imports'
import './video-playlists'
import './videos'
-import './videos-filter'
+import './videos-common-filters'
import './videos-history'
import './videos-overviews'
--- /dev/null
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import 'mocha'
+import {
+ cleanupTests,
+ createSingleServer,
+ makeGetRequest,
+ PeerTubeServer,
+ setAccessTokensToServers,
+ setDefaultVideoChannel
+} from '@shared/extra-utils'
+import { HttpStatusCode, UserRole, VideoInclude } from '@shared/models'
+
+describe('Test video filters validators', function () {
+ let server: PeerTubeServer
+ let userAccessToken: string
+ let moderatorAccessToken: string
+
+ // ---------------------------------------------------------------
+
+ before(async function () {
+ this.timeout(30000)
+
+ server = await createSingleServer(1)
+
+ await setAccessTokensToServers([ server ])
+ await setDefaultVideoChannel([ server ])
+
+ const user = { username: 'user1', password: 'my super password' }
+ await server.users.create({ username: user.username, password: user.password })
+ userAccessToken = await server.login.getAccessToken(user)
+
+ const moderator = { username: 'moderator', password: 'my super password' }
+ await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
+
+ moderatorAccessToken = await server.login.getAccessToken(moderator)
+ })
+
+ describe('When setting a deprecated video filter', function () {
+
+ async function testEndpoints (token: string, filter: string, expectedStatus: HttpStatusCode) {
+ const paths = [
+ '/api/v1/video-channels/root_channel/videos',
+ '/api/v1/accounts/root/videos',
+ '/api/v1/videos',
+ '/api/v1/search/videos'
+ ]
+
+ for (const path of paths) {
+ await makeGetRequest({
+ url: server.url,
+ path,
+ token,
+ query: {
+ filter
+ },
+ expectedStatus
+ })
+ }
+ }
+
+ it('Should fail with a bad filter', async function () {
+ await testEndpoints(server.accessToken, 'bad-filter', HttpStatusCode.BAD_REQUEST_400)
+ })
+
+ it('Should succeed with a good filter', async function () {
+ await testEndpoints(server.accessToken, 'local', HttpStatusCode.OK_200)
+ })
+
+ it('Should fail to list all-local/all with a simple user', async function () {
+ await testEndpoints(userAccessToken, 'all-local', HttpStatusCode.UNAUTHORIZED_401)
+ await testEndpoints(userAccessToken, 'all', HttpStatusCode.UNAUTHORIZED_401)
+ })
+
+ it('Should succeed to list all-local/all with a moderator', async function () {
+ await testEndpoints(moderatorAccessToken, 'all-local', HttpStatusCode.OK_200)
+ await testEndpoints(moderatorAccessToken, 'all', HttpStatusCode.OK_200)
+ })
+
+ it('Should succeed to list all-local/all with an admin', async function () {
+ await testEndpoints(server.accessToken, 'all-local', HttpStatusCode.OK_200)
+ await testEndpoints(server.accessToken, 'all', HttpStatusCode.OK_200)
+ })
+
+ // Because we cannot authenticate the user on the RSS endpoint
+ it('Should fail on the feeds endpoint with the all-local/all filter', async function () {
+ for (const filter of [ 'all', 'all-local' ]) {
+ await makeGetRequest({
+ url: server.url,
+ path: '/feeds/videos.json',
+ expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
+ query: {
+ filter
+ }
+ })
+ }
+ })
+
+ it('Should succeed on the feeds endpoint with the local filter', async function () {
+ await makeGetRequest({
+ url: server.url,
+ path: '/feeds/videos.json',
+ expectedStatus: HttpStatusCode.OK_200,
+ query: {
+ filter: 'local'
+ }
+ })
+ })
+ })
+
+ describe('When setting video filters', function () {
+
+ const validIncludes = [
+ VideoInclude.NONE,
+ VideoInclude.HIDDEN_PRIVACY,
+ VideoInclude.NOT_PUBLISHED_STATE | VideoInclude.BLACKLISTED
+ ]
+
+ async function testEndpoints (options: {
+ token?: string
+ isLocal?: boolean
+ include?: VideoInclude
+ expectedStatus: HttpStatusCode
+ }) {
+ const paths = [
+ '/api/v1/video-channels/root_channel/videos',
+ '/api/v1/accounts/root/videos',
+ '/api/v1/videos',
+ '/api/v1/search/videos'
+ ]
+
+ for (const path of paths) {
+ await makeGetRequest({
+ url: server.url,
+ path,
+ token: options.token || server.accessToken,
+ query: {
+ isLocal: options.isLocal,
+ include: options.include
+ },
+ expectedStatus: options.expectedStatus
+ })
+ }
+ }
+
+ it('Should fail with a bad include', async function () {
+ await testEndpoints({ include: 'toto' as any, expectedStatus: HttpStatusCode.BAD_REQUEST_400 })
+ })
+
+ it('Should succeed with a good include', async function () {
+ for (const include of validIncludes) {
+ await testEndpoints({ include, expectedStatus: HttpStatusCode.OK_200 })
+ }
+ })
+
+ it('Should fail to include more videos with a simple user', async function () {
+ for (const include of validIncludes) {
+ await testEndpoints({ token: userAccessToken, include, expectedStatus: HttpStatusCode.UNAUTHORIZED_401 })
+ }
+ })
+
+ it('Should succeed to list all local/all with a moderator', async function () {
+ for (const include of validIncludes) {
+ await testEndpoints({ token: moderatorAccessToken, include, expectedStatus: HttpStatusCode.OK_200 })
+ }
+ })
+
+ it('Should succeed to list all local/all with an admin', async function () {
+ for (const include of validIncludes) {
+ await testEndpoints({ token: server.accessToken, include, expectedStatus: HttpStatusCode.OK_200 })
+ }
+ })
+
+ // Because we cannot authenticate the user on the RSS endpoint
+ it('Should fail on the feeds endpoint with the all filter', async function () {
+ for (const include of [ VideoInclude.NOT_PUBLISHED_STATE ]) {
+ await makeGetRequest({
+ url: server.url,
+ path: '/feeds/videos.json',
+ expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
+ query: {
+ include
+ }
+ })
+ }
+ })
+
+ it('Should succeed on the feeds endpoint with the local filter', async function () {
+ await makeGetRequest({
+ url: server.url,
+ path: '/feeds/videos.json',
+ expectedStatus: HttpStatusCode.OK_200,
+ query: {
+ isLocal: true
+ }
+ })
+ })
+ })
+
+ after(async function () {
+ await cleanupTests([ server ])
+ })
+})
+++ /dev/null
-/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
-
-import 'mocha'
-import {
- cleanupTests,
- createSingleServer,
- makeGetRequest,
- PeerTubeServer,
- setAccessTokensToServers,
- setDefaultVideoChannel
-} from '@shared/extra-utils'
-import { HttpStatusCode, UserRole } from '@shared/models'
-
-async function testEndpoints (server: PeerTubeServer, token: string, filter: string, expectedStatus: HttpStatusCode) {
- const paths = [
- '/api/v1/video-channels/root_channel/videos',
- '/api/v1/accounts/root/videos',
- '/api/v1/videos',
- '/api/v1/search/videos'
- ]
-
- for (const path of paths) {
- await makeGetRequest({
- url: server.url,
- path,
- token,
- query: {
- filter
- },
- expectedStatus
- })
- }
-}
-
-describe('Test video filters validators', function () {
- let server: PeerTubeServer
- let userAccessToken: string
- let moderatorAccessToken: string
-
- // ---------------------------------------------------------------
-
- before(async function () {
- this.timeout(30000)
-
- server = await createSingleServer(1)
-
- await setAccessTokensToServers([ server ])
- await setDefaultVideoChannel([ server ])
-
- const user = { username: 'user1', password: 'my super password' }
- await server.users.create({ username: user.username, password: user.password })
- userAccessToken = await server.login.getAccessToken(user)
-
- const moderator = { username: 'moderator', password: 'my super password' }
- await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
-
- moderatorAccessToken = await server.login.getAccessToken(moderator)
- })
-
- describe('When setting a video filter', function () {
-
- it('Should fail with a bad filter', async function () {
- await testEndpoints(server, server.accessToken, 'bad-filter', HttpStatusCode.BAD_REQUEST_400)
- })
-
- it('Should succeed with a good filter', async function () {
- await testEndpoints(server, server.accessToken, 'local', HttpStatusCode.OK_200)
- })
-
- it('Should fail to list all-local/all with a simple user', async function () {
- await testEndpoints(server, userAccessToken, 'all-local', HttpStatusCode.UNAUTHORIZED_401)
- await testEndpoints(server, userAccessToken, 'all', HttpStatusCode.UNAUTHORIZED_401)
- })
-
- it('Should succeed to list all-local/all with a moderator', async function () {
- await testEndpoints(server, moderatorAccessToken, 'all-local', HttpStatusCode.OK_200)
- await testEndpoints(server, moderatorAccessToken, 'all', HttpStatusCode.OK_200)
- })
-
- it('Should succeed to list all-local/all with an admin', async function () {
- await testEndpoints(server, server.accessToken, 'all-local', HttpStatusCode.OK_200)
- await testEndpoints(server, server.accessToken, 'all', HttpStatusCode.OK_200)
- })
-
- // Because we cannot authenticate the user on the RSS endpoint
- it('Should fail on the feeds endpoint with the all-local/all filter', async function () {
- for (const filter of [ 'all', 'all-local' ]) {
- await makeGetRequest({
- url: server.url,
- path: '/feeds/videos.json',
- expectedStatus: HttpStatusCode.UNAUTHORIZED_401,
- query: {
- filter
- }
- })
- }
- })
-
- it('Should succeed on the feeds endpoint with the local filter', async function () {
- await makeGetRequest({
- url: server.url,
- path: '/feeds/videos.json',
- expectedStatus: HttpStatusCode.OK_200,
- query: {
- filter: 'local'
- }
- })
- })
- })
-
- after(async function () {
- await cleanupTests([ server ])
- })
-})
import './video-privacy'
import './video-schedule-update'
import './video-transcoder'
-import './videos-filter'
+import './videos-common-filters'
import './videos-history'
import './videos-overview'
import './videos-views-cleaner'
describe('It should list local videos', function () {
it('Should list only local videos on server 1', async function () {
- const { data, total } = await servers[0].videos.list({ filter: 'local' })
+ const { data, total } = await servers[0].videos.list({ isLocal: true })
expect(total).to.equal(1)
expect(data).to.be.an('array')
})
it('Should list only local videos on server 2', async function () {
- const { data, total } = await servers[1].videos.list({ filter: 'local' })
+ const { data, total } = await servers[1].videos.list({ isLocal: true })
expect(total).to.equal(1)
expect(data).to.be.an('array')
})
it('Should list only local videos on server 3', async function () {
- const { data, total } = await servers[2].videos.list({ filter: 'local' })
+ const { data, total } = await servers[2].videos.list({ isLocal: true })
expect(total).to.equal(2)
expect(data).to.be.an('array')
await server.videos.update({ id: videoId, attributes })
})
- it('Should filter by tags and category', async function () {
- {
- const { data, total } = await server.videos.list({ tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 4 ] })
- expect(total).to.equal(1)
- expect(data[0].name).to.equal('my super video updated')
- }
-
- {
- const { total } = await server.videos.list({ tagsAllOf: [ 'tagup1', 'tagup2' ], categoryOneOf: [ 3 ] })
- expect(total).to.equal(0)
- }
- })
-
it('Should have the video updated', async function () {
this.timeout(60000)
--- /dev/null
+/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
+
+import 'mocha'
+import { expect } from 'chai'
+import { pick } from '@shared/core-utils'
+import {
+ cleanupTests,
+ createMultipleServers,
+ doubleFollow,
+ makeGetRequest,
+ PeerTubeServer,
+ setAccessTokensToServers,
+ setDefaultVideoChannel,
+ waitJobs
+} from '@shared/extra-utils'
+import { HttpStatusCode, UserRole, Video, VideoInclude, VideoPrivacy } from '@shared/models'
+
+describe('Test videos filter', function () {
+ let servers: PeerTubeServer[]
+ let paths: string[]
+ let remotePaths: string[]
+
+ // ---------------------------------------------------------------
+
+ before(async function () {
+ this.timeout(160000)
+
+ servers = await createMultipleServers(2)
+
+ await setAccessTokensToServers(servers)
+ await setDefaultVideoChannel(servers)
+
+ for (const server of servers) {
+ const moderator = { username: 'moderator', password: 'my super password' }
+ await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
+ server['moderatorAccessToken'] = await server.login.getAccessToken(moderator)
+
+ await server.videos.upload({ attributes: { name: 'public ' + server.serverNumber } })
+
+ {
+ const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED }
+ await server.videos.upload({ attributes })
+ }
+
+ {
+ const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE }
+ await server.videos.upload({ attributes })
+ }
+ }
+
+ await doubleFollow(servers[0], servers[1])
+
+ paths = [
+ `/api/v1/video-channels/root_channel/videos`,
+ `/api/v1/accounts/root/videos`,
+ '/api/v1/videos',
+ '/api/v1/search/videos'
+ ]
+
+ remotePaths = [
+ `/api/v1/video-channels/root_channel@${servers[1].host}/videos`,
+ `/api/v1/accounts/root@${servers[1].host}/videos`,
+ '/api/v1/videos',
+ '/api/v1/search/videos'
+ ]
+ })
+
+ describe('Check deprecated videos filter', function () {
+
+ async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) {
+ const videosResults: Video[][] = []
+
+ for (const path of paths) {
+ const res = await makeGetRequest({
+ url: server.url,
+ path,
+ token,
+ query: {
+ sort: 'createdAt',
+ filter
+ },
+ expectedStatus
+ })
+
+ videosResults.push(res.body.data.map(v => v.name))
+ }
+
+ return videosResults
+ }
+
+ it('Should display local videos', async function () {
+ for (const server of servers) {
+ const namesResults = await getVideosNames(server, server.accessToken, 'local')
+ for (const names of namesResults) {
+ expect(names).to.have.lengthOf(1)
+ expect(names[0]).to.equal('public ' + server.serverNumber)
+ }
+ }
+ })
+
+ it('Should display all local videos by the admin or the moderator', async function () {
+ for (const server of servers) {
+ for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
+
+ const namesResults = await getVideosNames(server, token, 'all-local')
+ for (const names of namesResults) {
+ expect(names).to.have.lengthOf(3)
+
+ expect(names[0]).to.equal('public ' + server.serverNumber)
+ expect(names[1]).to.equal('unlisted ' + server.serverNumber)
+ expect(names[2]).to.equal('private ' + server.serverNumber)
+ }
+ }
+ }
+ })
+
+ it('Should display all videos by the admin or the moderator', async function () {
+ for (const server of servers) {
+ for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
+
+ const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all')
+ expect(channelVideos).to.have.lengthOf(3)
+ expect(accountVideos).to.have.lengthOf(3)
+
+ expect(videos).to.have.lengthOf(5)
+ expect(searchVideos).to.have.lengthOf(5)
+ }
+ }
+ })
+ })
+
+ describe('Check videos filters', function () {
+
+ async function listVideos (options: {
+ server: PeerTubeServer
+ path: string
+ isLocal?: boolean
+ include?: VideoInclude
+ category?: number
+ tagsAllOf?: string[]
+ token?: string
+ expectedStatus?: HttpStatusCode
+ }) {
+ const res = await makeGetRequest({
+ url: options.server.url,
+ path: options.path,
+ token: options.token ?? options.server.accessToken,
+ query: {
+ ...pick(options, [ 'isLocal', 'include', 'category', 'tagsAllOf' ]),
+
+ sort: 'createdAt'
+ },
+ expectedStatus: options.expectedStatus ?? HttpStatusCode.OK_200
+ })
+
+ return res.body.data as Video[]
+ }
+
+ async function getVideosNames (options: {
+ server: PeerTubeServer
+ isLocal?: boolean
+ include?: VideoInclude
+ token?: string
+ expectedStatus?: HttpStatusCode
+ }) {
+ const videosResults: string[][] = []
+
+ for (const path of paths) {
+ const videos = await listVideos({ ...options, path })
+
+ videosResults.push(videos.map(v => v.name))
+ }
+
+ return videosResults
+ }
+
+ it('Should display local videos', async function () {
+ for (const server of servers) {
+ const namesResults = await getVideosNames({ server, isLocal: true })
+
+ for (const names of namesResults) {
+ expect(names).to.have.lengthOf(1)
+ expect(names[0]).to.equal('public ' + server.serverNumber)
+ }
+ }
+ })
+
+ it('Should display local videos with hidden privacy by the admin or the moderator', async function () {
+ for (const server of servers) {
+ for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
+
+ const namesResults = await getVideosNames({
+ server,
+ token,
+ isLocal: true,
+ include: VideoInclude.HIDDEN_PRIVACY
+ })
+
+ for (const names of namesResults) {
+ expect(names).to.have.lengthOf(3)
+
+ expect(names[0]).to.equal('public ' + server.serverNumber)
+ expect(names[1]).to.equal('unlisted ' + server.serverNumber)
+ expect(names[2]).to.equal('private ' + server.serverNumber)
+ }
+ }
+ }
+ })
+
+ it('Should display all videos by the admin or the moderator', async function () {
+ for (const server of servers) {
+ for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
+
+ const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames({
+ server,
+ token,
+ include: VideoInclude.HIDDEN_PRIVACY
+ })
+
+ expect(channelVideos).to.have.lengthOf(3)
+ expect(accountVideos).to.have.lengthOf(3)
+
+ expect(videos).to.have.lengthOf(5)
+ expect(searchVideos).to.have.lengthOf(5)
+ }
+ }
+ })
+
+ it('Should display only remote videos', async function () {
+ this.timeout(40000)
+
+ await servers[1].videos.upload({ attributes: { name: 'remote video' } })
+
+ await waitJobs(servers)
+
+ const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
+
+ for (const path of remotePaths) {
+ {
+ const videos = await listVideos({ server: servers[0], path })
+ const video = finder(videos)
+ expect(video).to.exist
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, isLocal: false })
+ const video = finder(videos)
+ expect(video).to.exist
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, isLocal: true })
+ const video = finder(videos)
+ expect(video).to.not.exist
+ }
+ }
+ })
+
+ it('Should include not published videos', async function () {
+ await servers[0].config.enableLive({ allowReplay: false, transcoding: false })
+ await servers[0].live.create({ fields: { name: 'live video', channelId: servers[0].store.channel.id, privacy: VideoPrivacy.PUBLIC } })
+
+ const finder = (videos: Video[]) => videos.find(v => v.name === 'live video')
+
+ for (const path of paths) {
+ {
+ const videos = await listVideos({ server: servers[0], path })
+ const video = finder(videos)
+ expect(video).to.not.exist
+ expect(videos[0].state).to.not.exist
+ expect(videos[0].waitTranscoding).to.not.exist
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, include: VideoInclude.NOT_PUBLISHED_STATE })
+ const video = finder(videos)
+ expect(video).to.exist
+ expect(video.state).to.exist
+ }
+ }
+ })
+
+ it('Should include blacklisted videos', async function () {
+ const { id } = await servers[0].videos.upload({ attributes: { name: 'blacklisted' } })
+
+ await servers[0].blacklist.add({ videoId: id })
+
+ const finder = (videos: Video[]) => videos.find(v => v.name === 'blacklisted')
+
+ for (const path of paths) {
+ {
+ const videos = await listVideos({ server: servers[0], path })
+ const video = finder(videos)
+ expect(video).to.not.exist
+ expect(videos[0].blacklisted).to.not.exist
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLACKLISTED })
+ const video = finder(videos)
+ expect(video).to.exist
+ expect(video.blacklisted).to.be.true
+ }
+ }
+ })
+
+ it('Should include videos from muted account', async function () {
+ const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
+
+ await servers[0].blocklist.addToServerBlocklist({ account: 'root@' + servers[1].host })
+
+ for (const path of remotePaths) {
+ {
+ const videos = await listVideos({ server: servers[0], path })
+ const video = finder(videos)
+ expect(video).to.not.exist
+
+ // Some paths won't have videos
+ if (videos[0]) {
+ expect(videos[0].blockedOwner).to.not.exist
+ expect(videos[0].blockedServer).to.not.exist
+ }
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLOCKED_OWNER })
+
+ const video = finder(videos)
+ expect(video).to.exist
+ expect(video.blockedServer).to.be.false
+ expect(video.blockedOwner).to.be.true
+ }
+ }
+
+ await servers[0].blocklist.removeFromServerBlocklist({ account: 'root@' + servers[1].host })
+ })
+
+ it('Should include videos from muted server', async function () {
+ const finder = (videos: Video[]) => videos.find(v => v.name === 'remote video')
+
+ await servers[0].blocklist.addToServerBlocklist({ server: servers[1].host })
+
+ for (const path of remotePaths) {
+ {
+ const videos = await listVideos({ server: servers[0], path })
+ const video = finder(videos)
+ expect(video).to.not.exist
+
+ // Some paths won't have videos
+ if (videos[0]) {
+ expect(videos[0].blockedOwner).to.not.exist
+ expect(videos[0].blockedServer).to.not.exist
+ }
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, include: VideoInclude.BLOCKED_OWNER })
+ const video = finder(videos)
+ expect(video).to.exist
+ expect(video.blockedServer).to.be.true
+ expect(video.blockedOwner).to.be.false
+ }
+ }
+
+ await servers[0].blocklist.removeFromServerBlocklist({ server: servers[1].host })
+ })
+
+ it('Should filter by tags and category', async function () {
+ await servers[0].videos.upload({ attributes: { name: 'tag filter', tags: [ 'tag1', 'tag2' ] } })
+ await servers[0].videos.upload({ attributes: { name: 'tag filter with category', tags: [ 'tag3' ], category: 4 } })
+
+ for (const path of paths) {
+ {
+
+ const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag2' ] })
+ expect(videos).to.have.lengthOf(1)
+ expect(videos[0].name).to.equal('tag filter')
+
+ }
+
+ {
+ const videos = await listVideos({ server: servers[0], path, tagsAllOf: [ 'tag1', 'tag3' ] })
+ expect(videos).to.have.lengthOf(0)
+ }
+
+ {
+ const { data, total } = await servers[0].videos.list({ tagsAllOf: [ 'tag3' ], categoryOneOf: [ 4 ] })
+ expect(total).to.equal(1)
+ expect(data[0].name).to.equal('tag filter with category')
+ }
+
+ {
+ const { total } = await servers[0].videos.list({ tagsAllOf: [ 'tag4' ], categoryOneOf: [ 4 ] })
+ expect(total).to.equal(0)
+ }
+ }
+ })
+ })
+
+ after(async function () {
+ await cleanupTests(servers)
+ })
+})
+++ /dev/null
-/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
-
-import 'mocha'
-import { expect } from 'chai'
-import {
- cleanupTests,
- createMultipleServers,
- doubleFollow,
- makeGetRequest,
- PeerTubeServer,
- setAccessTokensToServers
-} from '@shared/extra-utils'
-import { HttpStatusCode, UserRole, Video, VideoPrivacy } from '@shared/models'
-
-async function getVideosNames (server: PeerTubeServer, token: string, filter: string, expectedStatus = HttpStatusCode.OK_200) {
- const paths = [
- '/api/v1/video-channels/root_channel/videos',
- '/api/v1/accounts/root/videos',
- '/api/v1/videos',
- '/api/v1/search/videos'
- ]
-
- const videosResults: Video[][] = []
-
- for (const path of paths) {
- const res = await makeGetRequest({
- url: server.url,
- path,
- token,
- query: {
- sort: 'createdAt',
- filter
- },
- expectedStatus
- })
-
- videosResults.push(res.body.data.map(v => v.name))
- }
-
- return videosResults
-}
-
-describe('Test videos filter', function () {
- let servers: PeerTubeServer[]
-
- // ---------------------------------------------------------------
-
- before(async function () {
- this.timeout(160000)
-
- servers = await createMultipleServers(2)
-
- await setAccessTokensToServers(servers)
-
- for (const server of servers) {
- const moderator = { username: 'moderator', password: 'my super password' }
- await server.users.create({ username: moderator.username, password: moderator.password, role: UserRole.MODERATOR })
- server['moderatorAccessToken'] = await server.login.getAccessToken(moderator)
-
- await server.videos.upload({ attributes: { name: 'public ' + server.serverNumber } })
-
- {
- const attributes = { name: 'unlisted ' + server.serverNumber, privacy: VideoPrivacy.UNLISTED }
- await server.videos.upload({ attributes })
- }
-
- {
- const attributes = { name: 'private ' + server.serverNumber, privacy: VideoPrivacy.PRIVATE }
- await server.videos.upload({ attributes })
- }
- }
-
- await doubleFollow(servers[0], servers[1])
- })
-
- describe('Check videos filter', function () {
-
- it('Should display local videos', async function () {
- for (const server of servers) {
- const namesResults = await getVideosNames(server, server.accessToken, 'local')
- for (const names of namesResults) {
- expect(names).to.have.lengthOf(1)
- expect(names[0]).to.equal('public ' + server.serverNumber)
- }
- }
- })
-
- it('Should display all local videos by the admin or the moderator', async function () {
- for (const server of servers) {
- for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
-
- const namesResults = await getVideosNames(server, token, 'all-local')
- for (const names of namesResults) {
- expect(names).to.have.lengthOf(3)
-
- expect(names[0]).to.equal('public ' + server.serverNumber)
- expect(names[1]).to.equal('unlisted ' + server.serverNumber)
- expect(names[2]).to.equal('private ' + server.serverNumber)
- }
- }
- }
- })
-
- it('Should display all videos by the admin or the moderator', async function () {
- for (const server of servers) {
- for (const token of [ server.accessToken, server['moderatorAccessToken'] ]) {
-
- const [ channelVideos, accountVideos, videos, searchVideos ] = await getVideosNames(server, token, 'all')
- expect(channelVideos).to.have.lengthOf(3)
- expect(accountVideos).to.have.lengthOf(3)
-
- expect(videos).to.have.lengthOf(5)
- expect(searchVideos).to.have.lengthOf(5)
- }
- }
- })
- })
-
- after(async function () {
- await cleanupTests(servers)
- })
-})
export type MAccountSummaryBlocks =
MAccountSummary &
- Use<'BlockedAccounts', MAccountBlocklistId[]>
+ Use<'BlockedByAccounts', MAccountBlocklistId[]>
export type MAccountAPI =
MAccount &
VideoDetails,
VideoFileMetadata,
VideoPrivacy,
- VideosCommonQuery,
- VideosWithSearchCommonQuery
+ VideosCommonQuery
} from '@shared/models'
import { buildAbsoluteFixturePath, wait } from '../miscs'
import { unwrapBody } from '../requests'
})
}
- listByAccount (options: OverrideCommandOptions & VideosWithSearchCommonQuery & {
+ listByAccount (options: OverrideCommandOptions & VideosCommonQuery & {
handle: string
}) {
const { handle, search } = options
})
}
- listByChannel (options: OverrideCommandOptions & VideosWithSearchCommonQuery & {
+ listByChannel (options: OverrideCommandOptions & VideosCommonQuery & {
handle: string
}) {
const { handle } = options
'languageOneOf',
'tagsOneOf',
'tagsAllOf',
- 'filter',
+ 'isLocal',
+ 'include',
'skipCount'
])
}
-import { VideoFilter } from '../videos'
+import { VideoInclude } from '../videos/video-include.enum'
import { BooleanBothQuery } from './boolean-both-query.model'
// These query parameters can be used with any endpoint that list videos
isLive?: boolean
+ // FIXME: deprecated in 4.0 in favour of isLocal and include, to remove
+ filter?: never
+
+ isLocal?: boolean
+ include?: VideoInclude
+
categoryOneOf?: number[]
licenceOneOf?: number[]
tagsOneOf?: string[]
tagsAllOf?: string[]
- filter?: VideoFilter
-
skipCount?: boolean
+
+ search?: string
}
export interface VideosCommonQueryAfterSanitize extends VideosCommonQuery {
start: number
count: number
sort: string
-}
-export interface VideosWithSearchCommonQuery extends VideosCommonQuery {
- search?: string
+ // FIXME: deprecated in 4.0, to remove
+ filter?: never
}
start: number
count: number
sort: string
+
+ // FIXME: deprecated in 4.0, to remove
+ filter?: never
}
export * from './video-file.model'
export * from './video-privacy.enum'
-export * from './video-query.type'
+export * from './video-filter.type'
+export * from './video-include.enum'
export * from './video-rate.type'
export * from './video-resolution.enum'
--- /dev/null
+export const enum VideoInclude {
+ NONE = 0,
+ NOT_PUBLISHED_STATE = 1 << 0,
+ HIDDEN_PRIVACY = 1 << 1,
+ BLACKLISTED = 1 << 2,
+ BLOCKED_OWNER = 1 << 3
+}
dislikes: number
nsfw: boolean
- waitTranscoding?: boolean
- state?: VideoConstant<VideoState>
- scheduledUpdate?: VideoScheduleUpdate
-
- blacklisted?: boolean
- blacklistedReason?: string
-
account: AccountSummary
channel: VideoChannelSummary
}
pluginData?: any
+
+ // Additional attributes dependending on the query
+ waitTranscoding?: boolean
+ state?: VideoConstant<VideoState>
+ scheduledUpdate?: VideoScheduleUpdate
+
+ blacklisted?: boolean
+ blacklistedReason?: string
+
+ blockedOwner?: boolean
+ blockedServer?: boolean
}
export interface VideoDetails extends Video {
commentsEnabled: boolean
downloadEnabled: boolean
- // Not optional in details (unlike in Video)
+ // Not optional in details (unlike in parent Video)
waitTranscoding: boolean
state: VideoConstant<VideoState>
- $ref: '#/components/parameters/licenceOneOf'
- $ref: '#/components/parameters/languageOneOf'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/skipCount'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/count'
- $ref: '#/components/parameters/licenceOneOf'
- $ref: '#/components/parameters/languageOneOf'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/skipCount'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/count'
- $ref: '#/components/parameters/licenceOneOf'
- $ref: '#/components/parameters/languageOneOf'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/skipCount'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/count'
- $ref: '#/components/parameters/licenceOneOf'
- $ref: '#/components/parameters/languageOneOf'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/skipCount'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/count'
- $ref: '#/components/parameters/licenceOneOf'
- $ref: '#/components/parameters/languageOneOf'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
- $ref: '#/components/parameters/skipCount'
- $ref: '#/components/parameters/start'
- $ref: '#/components/parameters/count'
type: string
- $ref: '#/components/parameters/sort'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
responses:
'204':
description: successful operation
required: true
- $ref: '#/components/parameters/sort'
- $ref: '#/components/parameters/nsfw'
- - $ref: '#/components/parameters/filter'
+ - $ref: '#/components/parameters/isLocal'
+ - $ref: '#/components/parameters/include'
responses:
'204':
description: successful operation
enum:
- 'true'
- 'false'
- filter:
- name: filter
+ isLocal:
+ name: isLocal
in: query
required: false
- description: >
- Special filters which might require special rights:
- * `local` - only videos local to the instance
- * `all-local` - only videos local to the instance, but showing private and unlisted videos (requires Admin privileges)
- * `all` - all videos, showing private and unlisted videos (requires Admin privileges)
schema:
- type: string
+ type: boolean
+ description: 'Display only local or remote videos'
+ include:
+ name: include
+ in: query
+ required: false
+ schema:
+ type: integer
enum:
- - local
- - all-local
+ - 0
+ - 1
+ - 2
+ - 4
+ - 8
+ description: >
+ Include additional videos in results (can be combined using bitwise or operator)
+
+ - `0` NONE
+
+ - `1` NOT_PUBLISHED_STATE
+
+ - `2` HIDDEN_PRIVACY
+
+ - `4` BLACKLISTED
+
+ - `8` BLOCKED
subscriptionsUris:
name: uris
in: query
enum:
- 0
- 1
- - 3
+ - 2
Notification:
properties:
id: