mirror of
https://github.com/Chocobozzz/PeerTube.git
synced 2025-10-03 09:49:20 +02:00

* Add NSFW flags to videos so the publisher can add more NSFW context * Add NSFW summary to videos, similar to content warning system so the publisher has a free text to describe NSFW aspect of its video * Add additional "warn" NSFW policy: the video thumbnail is not blurred and we display a tag below the video miniature, the video player includes the NSFW warning (with context if available) and it also prevent autoplay * "blur" NSFW settings inherits "warn" policy and also blur the video thumbnail * Add NSFW flag settings to users so they can have more granular control about what content they want to hide, warn or display
136 lines
4.2 KiB
TypeScript
136 lines
4.2 KiB
TypeScript
import { CategoryOverview, ChannelOverview, TagOverview, VideosOverview } from '@peertube/peertube-models'
|
|
import { logger } from '@server/helpers/logger.js'
|
|
import { Hooks } from '@server/lib/plugins/hooks.js'
|
|
import { getServerActor } from '@server/models/application/application.js'
|
|
import { VideoModel } from '@server/models/video/video.js'
|
|
import express from 'express'
|
|
import memoizee from 'memoizee'
|
|
import { buildNSFWFilters } from '../../helpers/express-utils.js'
|
|
import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants.js'
|
|
import { apiRateLimiter, asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares/index.js'
|
|
import { TagModel } from '../../models/video/tag.js'
|
|
|
|
const overviewsRouter = express.Router()
|
|
|
|
overviewsRouter.use(apiRateLimiter)
|
|
|
|
overviewsRouter.get('/videos', videosOverviewValidator, optionalAuthenticate, asyncMiddleware(getVideosOverview))
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export { overviewsRouter }
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const buildSamples = memoizee(async function () {
|
|
const [ categories, channels, tags ] = await Promise.all([
|
|
VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
|
|
VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT),
|
|
TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT)
|
|
])
|
|
|
|
const result = { categories, channels, tags }
|
|
|
|
logger.debug('Building samples for overview endpoint.', { result })
|
|
|
|
return result
|
|
}, { maxAge: MEMOIZE_TTL.OVERVIEWS_SAMPLE })
|
|
|
|
// This endpoint could be quite long, but we cache it
|
|
async function getVideosOverview (req: express.Request, res: express.Response) {
|
|
const attributes = await buildSamples()
|
|
|
|
const page = req.query.page || 1
|
|
const index = page - 1
|
|
|
|
const categories: CategoryOverview[] = []
|
|
const channels: ChannelOverview[] = []
|
|
const tags: TagOverview[] = []
|
|
|
|
await Promise.all([
|
|
getVideosByCategory(attributes.categories, index, res, categories),
|
|
getVideosByChannel(attributes.channels, index, res, channels),
|
|
getVideosByTag(attributes.tags, index, res, tags)
|
|
])
|
|
|
|
const result: VideosOverview = {
|
|
categories,
|
|
channels,
|
|
tags
|
|
}
|
|
|
|
return res.json(result)
|
|
}
|
|
|
|
async function getVideosByTag (tagsSample: string[], index: number, res: express.Response, acc: TagOverview[]) {
|
|
if (tagsSample.length <= index) return
|
|
|
|
const tag = tagsSample[index]
|
|
const videos = await getVideos(res, { tagsOneOf: [ tag ] })
|
|
|
|
if (videos.length === 0) return
|
|
|
|
acc.push({
|
|
tag,
|
|
videos
|
|
})
|
|
}
|
|
|
|
async function getVideosByCategory (categoriesSample: number[], index: number, res: express.Response, acc: CategoryOverview[]) {
|
|
if (categoriesSample.length <= index) return
|
|
|
|
const category = categoriesSample[index]
|
|
const videos = await getVideos(res, { categoryOneOf: [ category ] })
|
|
|
|
if (videos.length === 0) return
|
|
|
|
acc.push({
|
|
category: videos[0].category,
|
|
videos
|
|
})
|
|
}
|
|
|
|
async function getVideosByChannel (channelsSample: number[], index: number, res: express.Response, acc: ChannelOverview[]) {
|
|
if (channelsSample.length <= index) return
|
|
|
|
const channelId = channelsSample[index]
|
|
const videos = await getVideos(res, { videoChannelId: channelId })
|
|
|
|
if (videos.length === 0) return
|
|
|
|
acc.push({
|
|
channel: videos[0].channel,
|
|
videos
|
|
})
|
|
}
|
|
|
|
async function getVideos (
|
|
res: express.Response,
|
|
where: { videoChannelId?: number, tagsOneOf?: string[], categoryOneOf?: number[] }
|
|
) {
|
|
const serverActor = await getServerActor()
|
|
|
|
const query = await Hooks.wrapObject({
|
|
...buildNSFWFilters({ res }),
|
|
|
|
start: 0,
|
|
count: 12,
|
|
sort: '-createdAt',
|
|
displayOnlyForFollower: {
|
|
actorId: serverActor.id,
|
|
orLocalVideos: true
|
|
},
|
|
user: res.locals.oauth ? res.locals.oauth.token.User : undefined,
|
|
countVideos: false,
|
|
|
|
...where
|
|
}, 'filter:api.overviews.videos.list.params')
|
|
|
|
const { data } = await Hooks.wrapPromiseFun(
|
|
VideoModel.listForApi.bind(VideoModel),
|
|
query,
|
|
'filter:api.overviews.videos.list.result'
|
|
)
|
|
|
|
return data.map(d => d.toFormattedJSON())
|
|
}
|