brunner56's picture
implement app
0bfe2e3
import { z } from 'zod';
import * as constants from '../utils/constants';
const ServiceIds = z.enum(constants.SERVICES);
const Resolutions = z.enum(constants.RESOLUTIONS);
const Qualities = z.enum(constants.QUALITIES);
const VisualTags = z.enum(constants.VISUAL_TAGS);
const AudioTags = z.enum(constants.AUDIO_TAGS);
const AudioChannels = z.enum(constants.AUDIO_CHANNELS);
const Encodes = z.enum(constants.ENCODES);
// const SortCriteria = z.enum(constants.SORT_CRITERIA);
// const SortDirections = z.enum(constants.SORT_DIRECTIONS);
const SortCriterion = z.object({
key: z.enum(constants.SORT_CRITERIA),
direction: z.enum(constants.SORT_DIRECTIONS),
});
export type SortCriterion = z.infer<typeof SortCriterion>;
const StreamTypes = z.enum(constants.STREAM_TYPES);
const Languages = z.enum(constants.LANGUAGES);
const Formatter = z.object({
id: z.enum(constants.FORMATTERS),
definition: z
.object({
name: z.string().max(5000),
description: z.string().max(5000),
})
.optional(),
});
const StreamProxyConfig = z.object({
enabled: z.boolean().optional(),
id: z.enum(constants.PROXY_SERVICES).optional(),
url: z.string().optional(),
credentials: z.string().optional(),
publicIp: z.string().ip().optional(),
proxiedAddons: z.array(z.string().min(1)).optional(),
proxiedServices: z.array(z.string().min(1)).optional(),
});
export type StreamProxyConfig = z.infer<typeof StreamProxyConfig>;
const ResultLimitOptions = z.object({
global: z.number().min(1).optional(),
service: z.number().min(1).optional(),
addon: z.number().min(1).optional(),
resolution: z.number().min(1).optional(),
quality: z.number().min(1).optional(),
streamType: z.number().min(1).optional(),
indexer: z.number().min(1).optional(),
releaseGroup: z.number().min(1).optional(),
});
// const SizeFilter = z.object({
// min: z.number().min(1).optional(),
// max: z.number().min(1).optional(),
// });
const SizeFilter = z.object({
movies: z
.tuple([z.number().min(0), z.number().min(0)])
// .object({
// min: z.number().min(1).optional(),
// max: z.number().min(1).optional(),
// })
.optional(),
series: z
.tuple([z.number().min(0), z.number().min(0)])
// .object({
// min: z.number().min(1).optional(),
// max: z.number().min(1).optional(),
// })
.optional(),
});
const SizeFilterOptions = z.object({
global: SizeFilter,
resolution: z.record(Resolutions, SizeFilter).optional(),
});
const ServiceSchema = z.object({
id: ServiceIds,
enabled: z.boolean().optional(),
credentials: z.record(z.string().min(1), z.string().min(1)),
});
export type Service = z.infer<typeof ServiceSchema>;
const ServiceList = z.array(ServiceSchema);
const ResourceSchema = z.enum(constants.RESOURCES);
export type Resource = z.infer<typeof ResourceSchema>;
const ResourceList = z.array(ResourceSchema);
const AddonSchema = z.object({
instanceId: z.string().min(1).optional(), // uniquely identifies the addon in a given list of addons
presetType: z.string().min(1), // reference to the type of the preset that created this addon
presetInstanceId: z.string().min(1), // reference to the instance id of the preset that created this addon
manifestUrl: z.string().url(),
enabled: z.boolean(),
resources: ResourceList.optional(),
name: z.string(),
identifier: z.string().optional(), // true identifier for generating IDs
displayIdentifier: z.string().optional(), // identifier for display purposes
timeout: z.number().min(1),
library: z.boolean().optional(),
streamPassthrough: z.boolean().optional(),
headers: z.record(z.string().min(1), z.string().min(1)).optional(),
ip: z.string().ip().optional(),
});
// preset objects are transformed into addons by a preset transformer.
const PresetSchema = z.object({
type: z.string().min(1), // the preset type e.g. 'torrentio'
instanceId: z.string().min(1), // uniquely identifies the preset in a given list of presets
enabled: z.boolean(),
options: z.record(z.string().min(1), z.any()),
});
export type PresetObject = z.infer<typeof PresetSchema>;
const PresetList = z.array(PresetSchema);
export type Addon = z.infer<typeof AddonSchema>;
export type Preset = z.infer<typeof PresetSchema>;
const DeduplicatorKey = z.enum(constants.DEDUPLICATOR_KEYS);
// deduplicator options.
// can choose what keys to use for identifying duplicates.
// can choose how duplicates are removed specifically.
// we can either
// - keep only 1 result from the highest priority service from the highest priority addon (single_result)
// - keep 1 result for each enabled service from the higest priority addon (per_service)
// - keep 1 result from the highest priority service from each enabled addon (per_addon)
const DeduplicatorMode = z.enum([
'single_result',
'per_service',
'per_addon',
'disabled',
]);
const DeduplicatorOptions = z.object({
enabled: z.boolean().optional(),
keys: z.array(DeduplicatorKey).optional(),
cached: DeduplicatorMode.optional(),
uncached: DeduplicatorMode.optional(),
p2p: DeduplicatorMode.optional(),
http: DeduplicatorMode.optional(),
live: DeduplicatorMode.optional(),
youtube: DeduplicatorMode.optional(),
external: DeduplicatorMode.optional(),
});
const OptionDefinition = z.object({
id: z.string().min(1),
name: z.string().min(1),
description: z.string().min(1),
emptyIsUndefined: z.boolean().optional(),
type: z.enum([
'string',
'password',
'number',
'boolean',
'select',
'multi-select',
'url',
'alert',
'socials',
]),
required: z.boolean().optional(),
default: z.any().optional(),
// sensitive: z.boolean().optional(),
forced: z.any().optional(),
options: z
.array(
z.object({
value: z.any(),
label: z.string().min(1),
})
)
.optional(),
intent: z
.enum([
'alert',
'info',
'success',
'warning',
'info-basic',
'success-basic',
'warning-basic',
'alert-basic',
])
.optional(),
socials: z
.array(
z.object({
id: z.enum([
'website',
'github',
'discord',
'ko-fi',
'patreon',
'buymeacoffee',
'github-sponsors',
]),
url: z.string().url(),
})
)
.optional(),
constraints: z
.object({
min: z.number().min(1).optional(), // for string inputs, consider this the minimum length.
max: z.number().min(1).optional(), // and for number inputs, consider this the minimum and maximum value.
})
.optional(),
});
export type Option = z.infer<typeof OptionDefinition>;
const NameableRegex = z.object({
name: z.string().min(0),
pattern: z.string().min(1),
});
const Group = z.object({
addons: z.array(z.string().min(1)).min(1),
condition: z.string().min(1).max(200),
});
export type Group = z.infer<typeof Group>;
// Resolution, Quality, Encode, Visual Tag, Audio Tag, Stream Type, Keyword, Regex, Cached, Uncached, Size
const CatalogModification = z.object({
id: z.string().min(1), // an id that maps to an actual catalog ID
type: z.string().min(1), // the type of catalog modification
name: z.string().min(1).optional(), // override the name of the catalog
shuffle: z.boolean().optional(), // shuffle the catalog
persistShuffleFor: z.number().min(0).max(24).optional(), // persist the shuffle for a given amount of time (in hours)
onlyOnDiscover: z.boolean().optional(), // only show the catalog on the discover page
enabled: z.boolean().optional(), // enable or disable the catalog
rpdb: z.boolean().optional(), // use rpdb for posters if supported
overrideType: z.string().min(1).optional(), // override the type of the catalog
hideable: z.boolean().optional(), // hide the catalog from the home page
addonName: z.string().min(1).optional(), // the name of the addon that provides the catalog
});
export const UserDataSchema = z.object({
uuid: z.string().uuid().optional(),
encryptedPassword: z.string().min(1).optional(),
trusted: z.boolean().optional(),
addonPassword: z.string().min(1).optional(),
ip: z.string().ip().optional(),
addonName: z.string().min(1).max(300).optional(),
addonLogo: z.string().url().optional(),
addonBackground: z.string().url().optional(),
addonDescription: z.string().min(1).optional(),
excludedResolutions: z.array(Resolutions).optional(),
includedResolutions: z.array(Resolutions).optional(),
requiredResolutions: z.array(Resolutions).optional(),
preferredResolutions: z.array(Resolutions).optional(),
excludedQualities: z.array(Qualities).optional(),
includedQualities: z.array(Qualities).optional(),
requiredQualities: z.array(Qualities).optional(),
preferredQualities: z.array(Qualities).optional(),
excludedLanguages: z.array(Languages).optional(),
includedLanguages: z.array(Languages).optional(),
requiredLanguages: z.array(Languages).optional(),
preferredLanguages: z.array(Languages).optional(),
excludedVisualTags: z.array(VisualTags).optional(),
includedVisualTags: z.array(VisualTags).optional(),
requiredVisualTags: z.array(VisualTags).optional(),
preferredVisualTags: z.array(VisualTags).optional(),
excludedAudioTags: z.array(AudioTags).optional(),
includedAudioTags: z.array(AudioTags).optional(),
requiredAudioTags: z.array(AudioTags).optional(),
preferredAudioTags: z.array(AudioTags).optional(),
excludedAudioChannels: z.array(AudioChannels).optional(),
includedAudioChannels: z.array(AudioChannels).optional(),
requiredAudioChannels: z.array(AudioChannels).optional(),
preferredAudioChannels: z.array(AudioChannels).optional(),
excludedStreamTypes: z.array(StreamTypes).optional(),
includedStreamTypes: z.array(StreamTypes).optional(),
requiredStreamTypes: z.array(StreamTypes).optional(),
preferredStreamTypes: z.array(StreamTypes).optional(),
excludedEncodes: z.array(Encodes).optional(),
includedEncodes: z.array(Encodes).optional(),
requiredEncodes: z.array(Encodes).optional(),
preferredEncodes: z.array(Encodes).optional(),
excludedRegexPatterns: z.array(z.string().min(1)).optional(),
includedRegexPatterns: z.array(z.string().min(1)).optional(),
requiredRegexPatterns: z.array(z.string().min(1)).optional(),
preferredRegexPatterns: z.array(NameableRegex).optional(),
requiredKeywords: z.array(z.string().min(1)).optional(),
includedKeywords: z.array(z.string().min(1)).optional(),
excludedKeywords: z.array(z.string().min(1)).optional(),
preferredKeywords: z.array(z.string().min(1)).optional(),
randomiseResults: z.boolean().optional(),
enhanceResults: z.boolean().optional(),
enhancePosters: z.boolean().optional(),
excludeSeederRange: z
.tuple([z.number().min(0), z.number().min(0)])
.optional(),
includeSeederRange: z
.tuple([z.number().min(0), z.number().min(0)])
.optional(),
requiredSeederRange: z
.tuple([z.number().min(0), z.number().min(0)])
.optional(),
seederRangeTypes: z.array(z.enum(['p2p', 'cached', 'uncached'])).optional(),
excludeCached: z.boolean().optional(),
excludeCachedFromAddons: z.array(z.string().min(1)).optional(),
excludeCachedFromServices: z.array(z.string().min(1)).optional(),
excludeCachedFromStreamTypes: z.array(StreamTypes).optional(),
excludeCachedMode: z.enum(['or', 'and']).optional(),
excludeUncached: z.boolean().optional(),
excludeUncachedFromAddons: z.array(z.string().min(1)).optional(),
excludeUncachedFromServices: z.array(z.string().min(1)).optional(),
excludeUncachedFromStreamTypes: z.array(StreamTypes).optional(),
excludeUncachedMode: z.enum(['or', 'and']).optional(),
excludedStreamExpressions: z.array(z.string().min(1).max(1000)).optional(),
requiredStreamExpressions: z.array(z.string().min(1).max(1000)).optional(),
preferredStreamExpressions: z.array(z.string().min(1).max(1000)).optional(),
groups: z
.array(
z.object({
addons: z.array(z.string().min(1)),
condition: z.string().min(1).max(200),
})
)
.optional(),
sortCriteria: z.object({
// global must be defined.
global: z.array(SortCriterion),
// results must be from either a movie or series search, so we can safely apply different sort criteria.
movies: z.array(SortCriterion).optional(),
series: z.array(SortCriterion).optional(),
anime: z.array(SortCriterion).optional(),
// cached and uncached results are a sort criteria themselves, so this can only be applied when cache is high enough in the global
// sort criteria, and we would have to split the results into two (cached and uncached) lists, and then apply both sort criteria below
// and then merge the results.
cached: z.array(SortCriterion).optional(),
uncached: z.array(SortCriterion).optional(),
cachedMovies: z.array(SortCriterion).optional(),
uncachedMovies: z.array(SortCriterion).optional(),
cachedSeries: z.array(SortCriterion).optional(),
uncachedSeries: z.array(SortCriterion).optional(),
cachedAnime: z.array(SortCriterion).optional(),
uncachedAnime: z.array(SortCriterion).optional(),
}),
rpdbApiKey: z.string().optional(),
formatter: Formatter,
proxy: StreamProxyConfig.optional(),
resultLimits: ResultLimitOptions.optional(),
size: SizeFilterOptions.optional(),
hideErrors: z.boolean().optional(),
hideErrorsForResources: z.array(ResourceSchema).optional(),
tmdbAccessToken: z.string().optional(),
titleMatching: z
.object({
mode: z.enum(['exact', 'contains']).optional(),
matchYear: z.boolean().optional(),
enabled: z.boolean().optional(),
requestTypes: z.array(z.string()).optional(),
addons: z.array(z.string()).optional(),
})
.optional(),
seasonEpisodeMatching: z
.object({
enabled: z.boolean().optional(),
requestTypes: z.array(z.string()).optional(),
addons: z.array(z.string()).optional(),
})
.optional(),
deduplicator: DeduplicatorOptions.optional(),
precacheNextEpisode: z.boolean().optional(),
alwaysPrecache: z.boolean().optional(),
services: ServiceList.optional(),
presets: PresetList,
catalogModifications: z.array(CatalogModification).optional(),
externalDownloads: z.boolean().optional(),
});
export type UserData = z.infer<typeof UserDataSchema>;
export const TABLES = {
USERS: `
uuid TEXT PRIMARY KEY,
password_hash TEXT NOT NULL,
config TEXT NOT NULL,
config_salt TEXT NOT NULL,
created_at TIMESTAMP DEFAULT (CURRENT_TIMESTAMP),
updated_at TIMESTAMP DEFAULT (CURRENT_TIMESTAMP),
accessed_at TIMESTAMP DEFAULT (CURRENT_TIMESTAMP)
`,
};
const strictManifestResourceSchema = z.object({
name: z.enum(constants.RESOURCES),
types: z.array(z.string()),
idPrefixes: z.array(z.string().min(1)).or(z.null()).optional(),
});
export type StrictManifestResource = z.infer<
typeof strictManifestResourceSchema
>;
const ManifestResourceSchema = z.union([
z.string(),
strictManifestResourceSchema,
]);
const ManifestExtraSchema = z.object({
name: z.string().min(1),
isRequired: z.boolean().optional(),
options: z.array(z.string().or(z.null())).or(z.null()).optional(),
optionsLimit: z.number().min(1).optional(),
});
const ManifestCatalogSchema = z.object({
type: z.string(),
id: z.string().min(1),
name: z.string().min(1),
extra: z.array(ManifestExtraSchema).optional(),
});
const AddonCatalogDefinitionSchema = z.object({
type: z.string(),
id: z.string().min(1),
name: z.string().min(1),
});
export const ManifestSchema = z
.object({
id: z.string().min(1),
name: z.string(),
description: z.string(),
version: z.string(),
types: z.array(z.string()),
idPrefixes: z.array(z.string().min(1)).or(z.null()).optional(),
resources: z.array(ManifestResourceSchema),
catalogs: z.array(ManifestCatalogSchema),
addonCatalogs: z.array(AddonCatalogDefinitionSchema).optional(),
background: z.string().or(z.null()).optional(),
logo: z.string().or(z.null()).optional(),
contactEmail: z.string().or(z.null()).optional(),
behaviorHints: z
.object({
adult: z.boolean().optional(),
p2p: z.boolean().optional(),
configurable: z.boolean().optional(),
configurationRequired: z.boolean().optional(),
})
.optional(),
// not part of the manifest scheme, but needed for stremio-addons.net
stremioAddonsConfig: z
.object({
issuer: z.string().min(1),
signature: z.string().min(1),
})
.optional(),
})
.passthrough();
export type Manifest = z.infer<typeof ManifestSchema>;
export const SubtitleSchema = z
.object({
id: z.string().min(1),
url: z.string().url(),
lang: z.string().min(1),
})
.passthrough();
export const SubtitleResponseSchema = z.object({
subtitles: z.array(SubtitleSchema),
});
export type SubtitleResponse = z.infer<typeof SubtitleResponseSchema>;
export type Subtitle = z.infer<typeof SubtitleSchema>;
export const StreamSchema = z
.object({
url: z.string().url().or(z.null()).optional(),
ytId: z.string().min(1).or(z.null()).optional(),
infoHash: z.string().min(1).or(z.null()).optional(),
fileIdx: z.number().or(z.null()).optional(),
externalUrl: z.string().min(1).or(z.null()).optional(),
name: z.string().min(1).or(z.null()).optional(),
title: z.string().min(1).or(z.null()).optional(),
description: z.string().min(1).or(z.null()).optional(),
subtitles: z.array(SubtitleSchema).or(z.null()).optional(),
sources: z.array(z.string().min(1)).or(z.null()).optional(),
behaviorHints: z
.object({
countryWhitelist: z.array(z.string().length(3)).or(z.null()).optional(),
notWebReady: z.boolean().or(z.null()).optional(),
bingeGroup: z.string().min(1).or(z.null()).optional(),
proxyHeaders: z
.object({
request: z.record(z.string().min(1), z.string().min(1)).optional(),
response: z.record(z.string().min(1), z.string().min(1)).optional(),
})
.optional(),
videoHash: z.string().min(1).or(z.null()).optional(),
videoSize: z.number().or(z.null()).optional(),
filename: z.string().min(1).or(z.null()).optional(),
})
.optional(),
})
.passthrough();
export const StreamResponseSchema = z.object({
streams: z.array(StreamSchema),
});
export type StreamResponse = z.infer<typeof StreamResponseSchema>;
export type Stream = z.infer<typeof StreamSchema>;
const TrailerSchema = z
.object({
source: z.string().min(1),
type: z.enum(['Trailer', 'Clip']),
})
.passthrough();
const MetaLinkSchema = z
.object({
name: z.string().min(1),
category: z.string().min(1),
url: z.string().url().or(z.string().startsWith('stremio:///')),
})
.passthrough();
const MetaVideoSchema = z
.object({
id: z.string().min(1),
title: z.string().or(z.null()).optional(),
name: z.string().or(z.null()).optional(),
released: z.string().datetime().or(z.null()).optional(),
thumbnail: z.string().url().or(z.null()).optional(),
streams: z.array(StreamSchema).or(z.null()).optional(),
available: z.boolean().or(z.null()).optional(),
episode: z.number().or(z.null()).optional(),
season: z.number().or(z.null()).optional(),
trailers: z.array(TrailerSchema).or(z.null()).optional(),
overview: z.string().or(z.null()).optional(),
})
.passthrough();
export const MetaPreviewSchema = z
.object({
id: z.string().min(1),
type: z.string().min(1),
name: z.string().or(z.null()).optional(),
poster: z.string().or(z.null()).optional(),
posterShape: z
.enum(['square', 'poster', 'landscape', 'regular'])
.optional(),
// discover sidebar
//@deprecated use links instead
genres: z.array(z.string()).or(z.null()).optional(),
imdbRating: z.string().or(z.null()).or(z.number()).optional(),
releaseInfo: z.string().or(z.number()).or(z.null()).optional(),
//@deprecated
director: z.array(z.string()).or(z.null()).optional(),
//@deprecated
cast: z.array(z.string()).or(z.null()).optional(),
// background: z.string().min(1).optional(),
// logo: z.string().min(1).optional(),
description: z.string().or(z.null()).optional(),
trailers: z.array(TrailerSchema).or(z.null()).optional(),
links: z.array(MetaLinkSchema).or(z.null()).optional(),
// released: z.string().datetime().optional(),
})
.passthrough();
export const MetaSchema = MetaPreviewSchema.extend({
poster: z.string().or(z.null()).optional(),
background: z.string().or(z.null()).optional(),
logo: z.string().or(z.null()).optional(),
videos: z.array(MetaVideoSchema).or(z.null()).optional(),
runtime: z.coerce.string().or(z.null()).optional(),
language: z.string().or(z.null()).optional(),
country: z.string().or(z.null()).optional(),
awards: z.string().or(z.null()).optional(),
website: z.string().url().or(z.null()).optional(),
behaviorHints: z
.object({
defaultVideoId: z.string().or(z.null()).optional(),
})
.optional(),
}).passthrough();
export const MetaResponseSchema = z.object({
meta: MetaSchema,
});
export const CatalogResponseSchema = z.object({
metas: z.array(MetaPreviewSchema),
});
export type MetaResponse = z.infer<typeof MetaResponseSchema>;
export type CatalogResponse = z.infer<typeof CatalogResponseSchema>;
export type Meta = z.infer<typeof MetaSchema>;
export type MetaPreview = z.infer<typeof MetaPreviewSchema>;
export const AddonCatalogSchema = z
.object({
transportName: z.literal('http'),
transportUrl: z.string().url(),
manifest: ManifestSchema,
})
.passthrough();
export const AddonCatalogResponseSchema = z.object({
addons: z.array(AddonCatalogSchema),
});
export type AddonCatalogResponse = z.infer<typeof AddonCatalogResponseSchema>;
export type AddonCatalog = z.infer<typeof AddonCatalogSchema>;
const ParsedFileSchema = z.object({
releaseGroup: z.string().optional(),
resolution: z.string().optional(),
quality: z.string().optional(),
encode: z.string().optional(),
audioChannels: z.array(z.string()),
visualTags: z.array(z.string()),
audioTags: z.array(z.string()),
languages: z.array(z.string()),
title: z.string().optional(),
year: z.coerce.string().optional(),
season: z.number().optional(),
seasons: z.array(z.number()).optional(),
episode: z.number().optional(),
seasonEpisode: z.array(z.string()).optional(),
});
export type ParsedFile = z.infer<typeof ParsedFileSchema>;
export const ParsedStreamSchema = z.object({
id: z.string().min(1),
proxied: z.boolean().optional(),
addon: AddonSchema,
parsedFile: ParsedFileSchema.optional(),
message: z.string().max(1000).optional(),
regexMatched: z
.object({
name: z.string().optional(),
pattern: z.string().min(1).optional(),
index: z.number(),
})
.optional(),
keywordMatched: z.boolean().optional(),
streamExpressionMatched: z.number().optional(),
size: z.number().optional(),
folderSize: z.number().optional(),
type: StreamTypes,
indexer: z.string().optional(),
age: z.string().optional(),
torrent: z
.object({
infoHash: z.string().min(1).optional(),
fileIdx: z.number().optional(),
seeders: z.number().optional(),
sources: z.array(z.string().min(1)).optional(), // array of tracker urls and DHT nodes
})
.optional(),
countryWhitelist: z.array(z.string().length(3)).optional(),
notWebReady: z.boolean().optional(),
bingeGroup: z.string().min(1).optional(),
requestHeaders: z.record(z.string().min(1), z.string().min(1)).optional(),
responseHeaders: z.record(z.string().min(1), z.string().min(1)).optional(),
videoHash: z.string().min(1).optional(),
subtitles: z.array(SubtitleSchema).optional(),
filename: z.string().optional(),
folderName: z.string().optional(),
service: z
.object({
id: z.enum(constants.SERVICES),
cached: z.boolean(),
})
.optional(),
duration: z.number().optional(),
library: z.boolean().optional(),
url: z.string().url().optional(),
ytId: z.string().min(1).optional(),
externalUrl: z.string().min(1).optional(),
error: z
.object({
title: z.string().min(1),
description: z.string().min(1),
})
.optional(),
originalName: z.string().optional(),
originalDescription: z.string().optional(),
});
export const ParsedStreams = z.array(ParsedStreamSchema);
export type ParsedStream = z.infer<typeof ParsedStreamSchema>;
export type ParsedStreams = z.infer<typeof ParsedStreams>;
export const AIOStream = StreamSchema.extend({
streamData: z.object({
error: z
.object({
title: z.string().min(1),
description: z.string().min(1),
})
.optional(),
proxied: z.boolean().optional(),
addon: z.string().optional(),
filename: z.string().optional(),
folderName: z.string().optional(),
service: z
.object({
id: z.enum(constants.SERVICES),
cached: z.boolean(),
})
.optional(),
parsedFile: ParsedFileSchema.optional(),
message: z.string().max(1000).optional(),
regexMatched: z
.object({
name: z.string().optional(),
pattern: z.string().min(1).optional(),
index: z.number(),
})
.optional(),
keywordMatched: z.boolean().optional(),
streamExpressionMatched: z.number().optional(),
size: z.number().optional(),
folderSize: z.number().optional(),
type: StreamTypes.optional(),
indexer: z.string().optional(),
age: z.string().optional(),
torrent: z
.object({
infoHash: z.string().min(1).optional(),
fileIdx: z.number().optional(),
seeders: z.number().optional(),
sources: z.array(z.string().min(1)).optional(), // array of tracker urls and DHT nodes
})
.optional(),
duration: z.number().optional(),
library: z.boolean().optional(),
}),
});
export type AIOStream = z.infer<typeof AIOStream>;
const AIOStreamResponseSchema = z.object({
streams: z.array(AIOStream),
});
export type AIOStreamResponse = z.infer<typeof AIOStreamResponseSchema>;
const PresetMetadataSchema = z.object({
ID: z.string(),
NAME: z.string(),
DISABLED: z
.object({
reason: z.string(),
disabled: z.boolean(),
})
.optional(),
LOGO: z.string().optional(),
DESCRIPTION: z.string(),
URL: z.string(),
TIMEOUT: z.number(),
USER_AGENT: z.string(),
SUPPORTED_SERVICES: z.array(z.string()),
OPTIONS: z.array(OptionDefinition),
SUPPORTED_STREAM_TYPES: z.array(StreamTypes),
SUPPORTED_RESOURCES: z.array(ResourceSchema),
});
const PresetMinimalMetadataSchema = z.object({
ID: z.string(),
NAME: z.string(),
LOGO: z.string().optional(),
DESCRIPTION: z.string(),
URL: z.string(),
DISABLED: z
.object({
reason: z.string(),
disabled: z.boolean(),
})
.optional(),
SUPPORTED_RESOURCES: z.array(ResourceSchema),
SUPPORTED_STREAM_TYPES: z.array(StreamTypes),
SUPPORTED_SERVICES: z.array(z.string()),
OPTIONS: z.array(OptionDefinition),
});
const StatusResponseSchema = z.object({
version: z.string(),
tag: z.string(),
commit: z.string(),
buildTime: z.string(),
commitTime: z.string(),
users: z.number().or(z.null()),
settings: z.object({
baseUrl: z.string().url().optional(),
addonName: z.string(),
customHtml: z.string().optional(),
protected: z.boolean(),
regexFilterAccess: z.enum(['none', 'trusted', 'all']),
tmdbApiAvailable: z.boolean(),
forced: z.object({
proxy: z.object({
enabled: z.boolean().or(z.null()),
id: z.string().or(z.null()),
url: z.string().or(z.null()),
publicIp: z.string().or(z.null()),
credentials: z.string().or(z.null()),
disableProxiedAddons: z.boolean(),
proxiedServices: z.array(z.string()).or(z.null()),
}),
}),
defaults: z.object({
proxy: z.object({
enabled: z.boolean().or(z.null()),
id: z.string().or(z.null()),
url: z.string().or(z.null()),
publicIp: z.string().or(z.null()),
credentials: z.string().or(z.null()),
proxiedServices: z.array(z.string()).or(z.null()),
}),
timeout: z.number().or(z.null()),
}),
presets: z.array(PresetMinimalMetadataSchema),
services: z.record(
z.enum(constants.SERVICES),
z.object({
id: z.enum(constants.SERVICES),
name: z.string(),
shortName: z.string(),
knownNames: z.array(z.string()),
signUpText: z.string(),
credentials: z.array(OptionDefinition),
})
),
}),
});
export type StatusResponse = z.infer<typeof StatusResponseSchema>;
export type PresetMetadata = z.infer<typeof PresetMetadataSchema>;
export type PresetMinimalMetadata = z.infer<typeof PresetMinimalMetadataSchema>;
export const RPDBIsValidResponse = z.object({
valid: z.boolean(),
});
export type RPDBIsValidResponse = z.infer<typeof RPDBIsValidResponse>;