231 lines
6.7 KiB
TypeScript
231 lines
6.7 KiB
TypeScript
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 boostLanguages = search.boostLanguages || []
|
|
boostLanguages.push('zxx') // No linguistic content
|
|
|
|
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(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
|
|
}
|