import { exists } from '../../helpers/custom-validators/misc' import { logger } from '../../helpers/logger' import { client } from '../../helpers/meilisearch' import { buildUrl } from '../../helpers/utils' import { CONFIG } from '../../initializers/constants' import { VideosSearchQuery } from '../../types/search-query/video-search.model' import { DBVideo, DBVideoDetails, APIVideo, IndexableVideo, IndexableVideoDetails } from '../../types/video.model' import { buildInQuery, buildInValuesArray, buildSort, extractSearchQueryResult } from './meilisearch-queries' import { addUUIDFilters } from './shared' import { formatActorForDB, formatActorSummaryForAPI } from './shared/meilisearch-actor' export async function queryVideos (search: VideosSearchQuery) { const filter: string[] = [] if (search.blockedAccounts) filter.push(`account.handle NOT IN ${buildInValuesArray(search.blockedAccounts)}`) if (search.blockedHosts) filter.push(`host NOT IN ${buildInValuesArray(search.blockedHosts)}`) if (search.startDate) filter.push(`publishedAt >= ${new Date(search.startDate).getTime()}`) if (search.endDate) filter.push(`publishedAt <= ${new Date(search.endDate).getTime()}`) if (search.originallyPublishedStartDate) { filter.push(`originallyPublishedAt >= ${new Date(search.originallyPublishedStartDate).getTime()}`) } if (search.originallyPublishedEndDate) { filter.push(`originallyPublishedAt <= ${new Date(search.originallyPublishedEndDate).getTime()}`) } if (search.categoryOneOf) filter.push(`category.id IN ${buildInValuesArray(search.categoryOneOf)}`) if (search.licenceOneOf) filter.push(`licence.id IN ${buildInValuesArray(search.licenceOneOf)}`) if (search.languageOneOf) filter.push(`language.id IN ${buildInValuesArray(search.languageOneOf)}`) if (search.durationMin) filter.push(`duration >= ${search.durationMin}`) if (search.durationMax) filter.push(`duration <= ${search.durationMax}`) if (search.nsfw && search.nsfw !== 'both') filter.push(`nsfw = ${search.nsfw}`) if (exists(search.isLive)) filter.push(`isLive = ${search.isLive}`) if (search.host) filter.push(`account.host = '${search.host}'`) if (search.uuids) addUUIDFilters(filter, search.uuids) if (search.tagsOneOf) { const tagsOneOf = search.tagsOneOf.map(t => t.toLowerCase()) filter.push(`tags IN ${buildInValuesArray(tagsOneOf)}`) } if (search.tagsAllOf) { const tagsAllOf = search.tagsAllOf.map(t => t.toLowerCase()) const clause = tagsAllOf.map(tag => `tags = '${tag}'`).join(' AND ') filter.push(clause) } logger.debug({ filter }, 'Will query Meilisearch for videos.') const result = await client.index(CONFIG.MEILISEARCH.INDEXES.VIDEOS).search(search.search, { offset: search.start, limit: search.count, sort: buildSort(search.sort), showRankingScore: true, filter: [ ...filter, `language.id IN ${buildInValuesArray(search.boostLanguages)} OR language.id IS NULL` ] }) return extractSearchQueryResult(result) } export async function getVideosUpdatedAt (uuids: string[]): Promise<{ updatedAt: number, uuid: string }[]> { const result = await client.index(CONFIG.MEILISEARCH.INDEXES.VIDEOS).getDocuments({ fields: [ 'updatedAt', 'uuid' ], filter: [ buildInQuery('uuid', uuids) ] }) return result.results } export function formatVideoForDB (v: IndexableVideo | IndexableVideoDetails): DBVideo | DBVideoDetails { const video = { id: v.id, uuid: v.uuid, shortUUID: v.shortUUID, indexedAt: new Date().getTime(), createdAt: new Date(v.createdAt).getTime(), updatedAt: new Date(v.updatedAt).getTime(), publishedAt: new Date(v.publishedAt).getTime(), originallyPublishedAt: new Date(v.originallyPublishedAt).getTime(), category: { id: v.category.id, label: v.category.label }, licence: { id: v.licence.id, label: v.licence.label }, language: { id: v.language.id, label: v.language.label }, privacy: { id: v.privacy.id, label: v.privacy.label }, name: v.name, truncatedDescription: v.truncatedDescription, description: v.description, searchableDescription: (v.description || v.truncatedDescription || '').slice(0, 250), waitTranscoding: v.waitTranscoding, duration: v.duration, thumbnailPath: v.thumbnailPath, previewPath: v.previewPath, embedPath: v.embedPath, views: v.views, viewers: v.viewers, likes: v.likes, dislikes: v.dislikes, isLive: v.isLive || false, nsfw: v.nsfw, host: v.host, url: v.url, files: v.files, streamingPlaylists: v.streamingPlaylists, tags: (v as IndexableVideoDetails).tags ? (v as IndexableVideoDetails).tags : undefined, account: formatActorForDB(v.account), channel: formatActorForDB(v.channel) } if (isVideoDetails(v)) { return { ...video, trackerUrls: v.trackerUrls, descriptionPath: v.descriptionPath, support: v.support, commentsEnabled: v.commentsEnabled, downloadEnabled: v.downloadEnabled } } return video } export function formatVideoForAPI (v: DBVideoDetails, fromHost?: string): APIVideo { return { id: v.id, uuid: v.uuid, shortUUID: v.shortUUID, score: v._rankingScore, createdAt: new Date(v.createdAt), updatedAt: new Date(v.updatedAt), publishedAt: new Date(v.publishedAt), originallyPublishedAt: new Date(v.originallyPublishedAt), category: { id: v.category.id, label: v.category.label }, licence: { id: v.licence.id, label: v.licence.label }, language: { id: v.language.id, label: v.language.label }, privacy: { id: v.privacy.id, label: v.privacy.label }, name: v.name, description: v.description, truncatedDescription: v.truncatedDescription, duration: v.duration, tags: v.tags, thumbnailPath: v.thumbnailPath, thumbnailUrl: buildUrl(v.host, v.thumbnailPath), previewPath: v.previewPath, previewUrl: buildUrl(v.host, v.previewPath), embedPath: v.embedPath, embedUrl: buildUrl(v.host, v.embedPath), url: v.url, isLocal: fromHost && fromHost === v.host, views: v.views, viewers: v.viewers, likes: v.likes, dislikes: v.dislikes, isLive: v.isLive, nsfw: v.nsfw, account: formatActorSummaryForAPI(v.account), channel: formatActorSummaryForAPI(v.channel) } } // --------------------------------------------------------------------------- export function isVideoDetails (video: IndexableVideo | IndexableVideoDetails): video is IndexableVideoDetails { return (video as IndexableVideoDetails).commentsEnabled !== undefined }