Spaces:
Build error
Build error
File size: 7,321 Bytes
0bfe2e3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 |
import {
Option,
Resource,
Stream,
ParsedStream,
UserData,
PresetMetadata,
Addon,
} from '../db';
import { StreamParser } from '../parser';
import { Env, ServiceId, constants } from '../utils';
/**
*
* What modifications are needed for each preset:
*
* comet: apply FORCE_COMET_HOSTNAME, FORCE_COMET_PORT, FORCE_COMET_PROTOCOl to stream urls if they are defined
* dmm cast: need to split title by newline, replace trailing dashes, excluding lines with box emoji, and
* then joining the array back together.
* easynews,easynews+,easynews++: need to set type as usenet
* jackettio: apply FORCE_JACKETTIO_HOSTNAME, FORCE_JACKETTIO_PORT, FORCE_JACKETTIO_PROTOCOL to stream urls if they are defined
* mediafusion: need to add hint for folder name, 📁 emoji, and split on arrow, take last index.
* stremio-jacektt: need to inspect stream urls to extract service info.
* stremthruStore: need to mark each stream as 'inLibrary' and unset any parsed 'indexer'
* torbox: need to use different regex for probably everything.
* torrentio: extract folder name from first line
*/
// name: z.string().min(1),
// enabled: z.boolean().optional(),
// baseUrl: z.string().url().optional(),
// timeout: z.number().min(1).optional(),
// resources: ResourceList.optional(),
export const baseOptions = (
name: string,
resources: Resource[],
timeout: number = Env.DEFAULT_TIMEOUT
): Option[] => [
{
id: 'name',
name: 'Name',
description: 'What to call this addon',
type: 'string',
required: true,
default: name,
},
{
id: 'timeout',
name: 'Timeout',
description: 'The timeout for this addon',
type: 'number',
required: true,
default: timeout,
constraints: {
min: Env.MIN_TIMEOUT,
max: Env.MAX_TIMEOUT,
},
},
{
id: 'resources',
name: 'Resources',
description: 'Optionally override the resources to use ',
type: 'multi-select',
required: false,
default: resources,
options: resources.map((resource) => ({
label: resource,
value: resource,
})),
},
{
id: 'url',
name: 'URL',
description:
'Optionally override either the manifest generated, or override the base url used when generating the manifests',
type: 'url',
required: false,
emptyIsUndefined: true,
default: undefined,
},
];
export abstract class Preset {
static get METADATA(): PresetMetadata {
throw new Error('METADATA must be implemented by derived classes');
}
static getParser(): typeof StreamParser {
return StreamParser;
}
/**
* Creates a preset from a preset id.
* @param presetId - The id of the preset to create.
* @returns The preset.
*/
static generateAddons(
userData: UserData,
options: Record<string, any>
): Promise<Addon[]> {
throw new Error('generateAddons must be implemented by derived classes');
}
// Utility functions for generating config strings
/**
* Encodes a JSON object into a base64 encoded string.
* @param json - The JSON object to encode.
* @returns The base64 encoded string.
*/
protected static base64EncodeJSON(
json: any,
urlEncode: boolean = false, // url encode the string
makeUrlSafe: boolean = false // replace + with -, / with _ and = with nothing
) {
let encoded = Buffer.from(JSON.stringify(json)).toString('base64');
if (makeUrlSafe) {
encoded = encoded
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
} else if (urlEncode) {
encoded = encodeURIComponent(encoded);
}
return encoded;
}
protected static urlEncodeJSON(json: any) {
return encodeURIComponent(JSON.stringify(json));
}
/**
* Transforms key-value pairs into a url encoded string
* @param options - The key-value pair object to encode.
* @returns The encoded string.
*/
protected static urlEncodeKeyValuePairs(
options: Record<string, string> | string[][],
separator: string = '|',
encode: boolean = true
) {
const string = (Array.isArray(options) ? options : Object.entries(options))
.map(([key, value]) => `${key}=${value}`)
.join(separator);
return encode ? encodeURIComponent(string) : string;
}
protected static getUsableServices(
userData: UserData,
specifiedServices?: ServiceId[]
) {
let usableServices = userData.services?.filter(
(service) =>
this.METADATA.SUPPORTED_SERVICES.includes(service.id) && service.enabled
);
if (specifiedServices) {
// Validate specified services exist and are enabled
for (const service of specifiedServices) {
const userService = userData.services?.find((s) => s.id === service);
const meta = Object.values(constants.SERVICE_DETAILS).find(
(s) => s.id === service
);
if (!userService || !userService.enabled || !userService.credentials) {
throw new Error(
`You have specified ${meta?.name || service} in your configuration, but it is not enabled or has missing credentials`
);
}
}
// Filter to only specified services
usableServices = usableServices?.filter((service) =>
specifiedServices.includes(service.id)
);
}
return usableServices;
}
protected static getServiceCredential(
serviceId: ServiceId,
userData: UserData,
specialCases?: Partial<Record<ServiceId, (credentials: any) => any>>
) {
const service = constants.SERVICE_DETAILS[serviceId];
if (!service) {
throw new Error(`Service ${serviceId} not found`);
}
const serviceCredentials = userData.services?.find(
(service) => service.id === serviceId
)?.credentials;
if (!serviceCredentials) {
throw new Error(`No credentials found for service ${serviceId}`);
}
// Handle special cases if provided
if (specialCases?.[serviceId]) {
return specialCases[serviceId](serviceCredentials);
}
// handle seedr
if (serviceId === constants.SEEDR_SERVICE) {
if (serviceCredentials.encodedToken) {
return serviceCredentials.encodedToken;
}
throw new Error(
`Missing encoded token for ${serviceId}. Please add an encoded token using MediaFusion`
);
}
// handle easynews
if (serviceId === constants.EASYNEWS_SERVICE) {
if (!serviceCredentials.username || !serviceCredentials.password) {
throw new Error(
`Missing username or password for ${serviceId}. Please add a username and password.`
);
}
return {
username: serviceCredentials.username,
password: serviceCredentials.password,
};
}
if (serviceId === constants.PIKPAK_SERVICE) {
if (!serviceCredentials.email || !serviceCredentials.password) {
throw new Error(
`Missing email or password for ${serviceId}. Please add an email and password.`
);
}
return {
email: serviceCredentials.email,
password: serviceCredentials.password,
};
}
// Default case - API key
const { apiKey } = serviceCredentials;
if (!apiKey) {
throw new Error(
`Missing credentials for ${serviceId}. Please add an API key.`
);
}
return apiKey;
}
}
|