+ const query: FindOptions & { where?: null } = {
+ offset: options.start,
+ limit: options.count,
+ order: getVideoSort(options.sort)
+ }
+
+ let trendingDays: number
+ if (options.sort.endsWith('trending')) {
+ trendingDays = CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
+
+ query.group = 'VideoModel.id'
+ }
+
+ const serverActor = await getServerActor()
+
+ // followerActorId === null has a meaning, so just check undefined
+ const followerActorId = options.followerActorId !== undefined ? options.followerActorId : serverActor.id
+
+ const queryOptions = {
+ followerActorId,
+ serverAccountId: serverActor.Account.id,
+ nsfw: options.nsfw,
+ categoryOneOf: options.categoryOneOf,
+ licenceOneOf: options.licenceOneOf,
+ languageOneOf: options.languageOneOf,
+ tagsOneOf: options.tagsOneOf,
+ tagsAllOf: options.tagsAllOf,
+ filter: options.filter,
+ withFiles: options.withFiles,
+ accountId: options.accountId,
+ videoChannelId: options.videoChannelId,
+ videoPlaylistId: options.videoPlaylistId,
+ includeLocalVideos: options.includeLocalVideos,
+ user: options.user,
+ historyOfUser: options.historyOfUser,
+ trendingDays
+ }
+
+ return VideoModel.getAvailableForApi(query, queryOptions, countVideos)
+ }
+
+ static async searchAndPopulateAccountAndServer (options: {
+ includeLocalVideos: boolean
+ search?: string
+ start?: number
+ count?: number
+ sort?: string
+ startDate?: string // ISO 8601
+ endDate?: string // ISO 8601
+ originallyPublishedStartDate?: string
+ originallyPublishedEndDate?: string
+ nsfw?: boolean
+ categoryOneOf?: number[]
+ licenceOneOf?: number[]
+ languageOneOf?: string[]
+ tagsOneOf?: string[]
+ tagsAllOf?: string[]
+ durationMin?: number // seconds
+ durationMax?: number // seconds
+ user?: UserModel,
+ filter?: VideoFilter
+ }) {
+ const whereAnd = []
+
+ if (options.startDate || options.endDate) {
+ const publishedAtRange = {}
+
+ if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate
+ if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate
+
+ whereAnd.push({ publishedAt: publishedAtRange })
+ }
+
+ if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) {
+ const originallyPublishedAtRange = {}
+
+ if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate
+ if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate
+
+ whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange })
+ }
+
+ if (options.durationMin || options.durationMax) {
+ const durationRange = {}
+
+ if (options.durationMin) durationRange[ Op.gte ] = options.durationMin
+ if (options.durationMax) durationRange[ Op.lte ] = options.durationMax
+
+ whereAnd.push({ duration: durationRange })
+ }
+
+ const attributesInclude = []
+ const escapedSearch = VideoModel.sequelize.escape(options.search)
+ const escapedLikeSearch = VideoModel.sequelize.escape('%' + options.search + '%')
+ if (options.search) {
+ whereAnd.push(
+ {
+ id: {
+ [ Op.in ]: Sequelize.literal(
+ '(' +
+ 'SELECT "video"."id" FROM "video" ' +
+ 'WHERE ' +
+ 'lower(immutable_unaccent("video"."name")) % lower(immutable_unaccent(' + escapedSearch + ')) OR ' +
+ 'lower(immutable_unaccent("video"."name")) LIKE lower(immutable_unaccent(' + escapedLikeSearch + '))' +
+ 'UNION ALL ' +
+ 'SELECT "video"."id" FROM "video" LEFT JOIN "videoTag" ON "videoTag"."videoId" = "video"."id" ' +
+ 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' +
+ 'WHERE "tag"."name" = ' + escapedSearch +
+ ')'
+ )
+ }
+ }
+ )
+
+ attributesInclude.push(createSimilarityAttribute('VideoModel.name', options.search))
+ }
+
+ // Cannot search on similarity if we don't have a search
+ if (!options.search) {
+ attributesInclude.push(
+ Sequelize.literal('0 as similarity')
+ )
+ }
+
+ const query = {
+ attributes: {
+ include: attributesInclude
+ },
+ offset: options.start,
+ limit: options.count,
+ order: getVideoSort(options.sort)
+ }
+
+ const serverActor = await getServerActor()
+ const queryOptions = {
+ followerActorId: serverActor.id,
+ serverAccountId: serverActor.Account.id,
+ includeLocalVideos: options.includeLocalVideos,
+ nsfw: options.nsfw,
+ categoryOneOf: options.categoryOneOf,
+ licenceOneOf: options.licenceOneOf,
+ languageOneOf: options.languageOneOf,
+ tagsOneOf: options.tagsOneOf,
+ tagsAllOf: options.tagsAllOf,
+ user: options.user,
+ filter: options.filter,
+ baseWhere: whereAnd
+ }
+
+ return VideoModel.getAvailableForApi(query, queryOptions)
+ }
+
+ static load (id: number | string, t?: Transaction) {
+ const where = buildWhereIdOrUUID(id)
+ const options = {
+ where,
+ transaction: t
+ }
+
+ return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options)
+ }
+
+ static loadWithRights (id: number | string, t?: Transaction) {
+ const where = buildWhereIdOrUUID(id)
+ const options = {
+ where,
+ transaction: t
+ }