gongxiangp2/lib/video-download-policy.ts

133 lines
4.2 KiB
TypeScript

import { INSTALL_VIDEO_TYPE, isVideoMaterialType, normalizeVideoMaterialType } from './video-material-types'
export const VIDEO_DOWNLOAD_WAIT_DAYS = 15
export const VIDEO_DOWNLOAD_WAIT_MS = VIDEO_DOWNLOAD_WAIT_DAYS * 24 * 60 * 60 * 1000
/**
* 15 天保护期的显式豁免。仅站内用户经超级管理员开通后生效;
* 外部 apiKey 接口不传此项,维持完整保护期语义。
*/
export interface VideoPolicyOptions {
bypassWaitPeriod?: boolean
}
export interface VideoDownloadAvailability {
allowed: boolean
reason?: 'VIDEO_WAIT_PERIOD'
availableAt?: Date
remainingDays?: number
}
export interface VideoPreviewPolicyFields {
videoPreviewBlocked: boolean
videoPreviewUnavailableReason?: 'VIDEO_WAIT_PERIOD' | null
videoPreviewAvailableAt?: string | null
videoPreviewRemainingDays?: number | null
videoPreviewUnavailableMessage?: string | null
}
export function userHasVideoEarlyAccess(user?: unknown): boolean {
return Boolean((user as { videoEarlyAccess?: boolean } | null)?.videoEarlyAccess)
}
export function getVideoDownloadAvailability(
materialType?: string | null,
createdAt?: Date | string | null,
now: Date = new Date(),
options?: VideoPolicyOptions,
): VideoDownloadAvailability {
if (options?.bypassWaitPeriod) {
return { allowed: true }
}
const normalizedType = normalizeVideoMaterialType(materialType)
if (!isVideoMaterialType(normalizedType) || normalizedType === INSTALL_VIDEO_TYPE) {
return { allowed: true }
}
const uploadedAt = createdAt instanceof Date ? createdAt : new Date(String(createdAt || ''))
if (Number.isNaN(uploadedAt.getTime())) {
return { allowed: true }
}
const availableAt = new Date(uploadedAt.getTime() + VIDEO_DOWNLOAD_WAIT_MS)
if (now.getTime() >= availableAt.getTime()) {
return { allowed: true }
}
const remainingMs = availableAt.getTime() - now.getTime()
return {
allowed: false,
reason: 'VIDEO_WAIT_PERIOD',
availableAt,
remainingDays: Math.max(1, Math.ceil(remainingMs / (24 * 60 * 60 * 1000))),
}
}
export function getVideoDownloadUnavailableMessage(availability: VideoDownloadAvailability): string {
const remainingDays = availability.remainingDays || VIDEO_DOWNLOAD_WAIT_DAYS
return `视频类素材上传后满 ${VIDEO_DOWNLOAD_WAIT_DAYS} 天才能下载,当前还需约 ${remainingDays} 天`
}
export function getVideoPreviewUnavailableMessage(availability: VideoDownloadAvailability): string {
const remainingDays = availability.remainingDays || VIDEO_DOWNLOAD_WAIT_DAYS
return `视频类素材上传后满 ${VIDEO_DOWNLOAD_WAIT_DAYS} 天才能预览和下载,当前还需约 ${remainingDays} 天`
}
export function getVideoPreviewPolicyFields(
materialType?: string | null,
createdAt?: Date | string | null,
now: Date = new Date(),
options?: VideoPolicyOptions,
): VideoPreviewPolicyFields {
const availability = getVideoDownloadAvailability(materialType, createdAt, now, options)
if (availability.allowed) {
return {
videoPreviewBlocked: false,
videoPreviewUnavailableReason: null,
videoPreviewAvailableAt: null,
videoPreviewRemainingDays: null,
videoPreviewUnavailableMessage: null,
}
}
return {
videoPreviewBlocked: true,
videoPreviewUnavailableReason: availability.reason || 'VIDEO_WAIT_PERIOD',
videoPreviewAvailableAt: availability.availableAt?.toISOString() || null,
videoPreviewRemainingDays: availability.remainingDays || VIDEO_DOWNLOAD_WAIT_DAYS,
videoPreviewUnavailableMessage: getVideoPreviewUnavailableMessage(availability),
}
}
export function applyVideoPreviewPolicy<T extends Record<string, any>>(
material: T,
now: Date = new Date(),
options?: VideoPolicyOptions,
): T & VideoPreviewPolicyFields {
const policyFields = getVideoPreviewPolicyFields(
material?.materialType,
material?.createdAt,
now,
options,
)
const nextMaterial: any = {
...material,
...policyFields,
}
if (policyFields.videoPreviewBlocked) {
nextMaterial.videoSourceUrl = null
nextMaterial.videoPreview540Url = null
nextMaterial.videoPreviewClipUrl = null
if ('sourceFileUrl' in nextMaterial) {
nextMaterial.sourceFileUrl = null
}
}
return nextMaterial
}