Spaces:
Paused
Paused
| import { | |
| ParsedFileSchema, | |
| ParsedStream, | |
| Resource, | |
| SourceSchema, | |
| SubtitleSchema, | |
| UserData, | |
| } from '../db/index.js'; | |
| import { AIOStreamsResponse } from '../main/types.js'; | |
| import { generateBingeGroup } from './utils.js'; | |
| export interface SearchApiResponseData { | |
| results: SearchApiResult[]; | |
| filtered: number; | |
| errors: { | |
| title: string; | |
| description: string; | |
| }[]; | |
| statistics: { | |
| title: string; | |
| description: string; | |
| }[]; | |
| } | |
| import { z } from 'zod'; | |
| const SearchApiResultSchema = z.object({ | |
| infoHash: z.string().nullable(), | |
| seeders: z.number().nullable(), | |
| age: z.number().nullable(), | |
| sources: z.array(z.string()).nullable(), | |
| ytId: z.string().nullable(), | |
| externalUrl: z.string().nullable(), | |
| fileIdx: z.number().nullable(), | |
| url: z.string().nullable(), | |
| nzbUrl: z.string().nullable(), | |
| rarUrls: z.array(SourceSchema).nullable(), | |
| '7zipUrls': z.array(SourceSchema).nullable(), | |
| zipUrls: z.array(SourceSchema).nullable(), | |
| tarUrls: z.array(SourceSchema).nullable(), | |
| tgzUrls: z.array(SourceSchema).nullable(), | |
| proxied: z.boolean(), | |
| filename: z.string().nullable(), | |
| folderName: z.string().nullable(), | |
| size: z.number().nullable(), | |
| folderSize: z.number().nullable(), | |
| message: z.string().nullable(), | |
| library: z.boolean(), | |
| type: z.string(), | |
| indexer: z.string().nullable(), | |
| addon: z.string().nullable(), | |
| duration: z.number().nullable(), | |
| bitrate: z.number().nullable(), | |
| videoHash: z.string().nullable(), | |
| subtitles: z.array(SubtitleSchema), | |
| countryWhitelist: z.array(z.string()), | |
| requestHeaders: z.partialRecord(z.string(), z.string()), | |
| responseHeaders: z.partialRecord(z.string(), z.string()), | |
| parsedFile: ParsedFileSchema.optional(), | |
| service: z.string().nullable(), | |
| cached: z.boolean().nullable(), | |
| servers: z.array(z.string()).nullable(), | |
| notWebReady: z.boolean().nullable(), | |
| bingeGroup: z.string().nullable(), | |
| private: z.boolean().nullable(), | |
| seadexBest: z.boolean().nullable(), | |
| seadex: z.boolean().nullable(), | |
| }); | |
| export type SearchApiResult = z.infer<typeof SearchApiResultSchema>; | |
| export type SearchApiResultField = keyof SearchApiResult; | |
| export const SearchApiResultField = z.keyof(SearchApiResultSchema); | |
| export class ApiTransformer { | |
| constructor(private readonly userData: UserData) {} | |
| async transformStreams( | |
| response: AIOStreamsResponse<{ | |
| streams: ParsedStream[]; | |
| statistics: { title: string; description: string; forced?: boolean }[]; | |
| }>, | |
| requiredFields: SearchApiResultField[] | |
| ): Promise<SearchApiResponseData> { | |
| const { data, errors } = response; | |
| let filteredCount = 0; | |
| const results: SearchApiResult[] = data.streams | |
| .map((stream: ParsedStream, index: number) => ({ | |
| infoHash: stream.torrent?.infoHash ?? null, | |
| url: stream.url ?? null, | |
| nzbUrl: stream.nzbUrl ?? null, | |
| rarUrls: stream.rarUrls ?? null, | |
| '7zipUrls': stream['7zipUrls'] ?? null, | |
| zipUrls: stream.zipUrls ?? null, | |
| tarUrls: stream.tarUrls ?? null, | |
| tgzUrls: stream.tgzUrls ?? null, | |
| seeders: stream.torrent?.seeders ?? null, | |
| age: stream.age ?? null, | |
| sources: stream.torrent?.sources ?? null, | |
| ytId: stream.ytId ?? null, | |
| externalUrl: stream.externalUrl ?? null, | |
| fileIdx: stream.torrent?.fileIdx ?? null, | |
| proxied: stream.proxied ?? false, | |
| filename: stream.filename ?? null, | |
| folderName: stream.folderName ?? null, | |
| size: stream.size ?? null, | |
| folderSize: stream.folderSize ?? null, | |
| message: stream.message ?? null, | |
| library: stream.library ?? false, | |
| addon: stream.addon.name ?? null, | |
| type: stream.type ?? '', | |
| indexer: stream.indexer ?? null, | |
| duration: stream.duration ?? null, | |
| bitrate: stream.bitrate ?? null, | |
| videoHash: stream.videoHash ?? null, | |
| subtitles: stream.subtitles ?? [], | |
| countryWhitelist: stream.countryWhitelist ?? [], | |
| requestHeaders: stream.requestHeaders ?? {}, | |
| responseHeaders: stream.responseHeaders ?? {}, | |
| parsedFile: stream.parsedFile, | |
| service: stream.service?.id ?? null, | |
| cached: stream.service?.cached ?? null, | |
| servers: stream.servers ?? null, | |
| notWebReady: stream.notWebReady ?? null, | |
| bingeGroup: generateBingeGroup(stream, index, this.userData) ?? null, | |
| private: stream.torrent?.private ?? null, | |
| seadexBest: stream.seadex?.isBest ?? null, | |
| seadex: stream.seadex?.isSeadex ?? null, | |
| })) | |
| ?.filter((result) => { | |
| const hasRequiredFields = requiredFields.every( | |
| (field) => result[field] !== null | |
| ); | |
| if (!hasRequiredFields) { | |
| filteredCount++; | |
| } | |
| return hasRequiredFields; | |
| }); | |
| const statistics = []; | |
| const forcedStats = data.statistics | |
| .filter((s) => s.forced) | |
| .map((s) => ({ | |
| title: s.title, | |
| description: s.description, | |
| })); | |
| const userStats = data.statistics | |
| .filter((s) => !s.forced) | |
| .map((s) => ({ | |
| title: s.title, | |
| description: s.description, | |
| })); | |
| // Forced stats always surface regardless of user config, but respect position | |
| if (forcedStats.length > 0) { | |
| statistics.push(...forcedStats); | |
| } | |
| if (this.userData.statistics?.enabled) { | |
| statistics.push(...userStats); | |
| } | |
| return { | |
| filtered: filteredCount, | |
| results, | |
| statistics, | |
| errors: errors.map((error) => ({ | |
| title: error.title ?? '', | |
| description: error.description ?? '', | |
| })), | |
| }; | |
| } | |
| } | |