feat: configurable timeout for every addon

This commit is contained in:
Viren070
2024-12-27 23:59:32 +00:00
parent be77ad0248
commit 8abf25b8e8
7 changed files with 154 additions and 52 deletions
+7 -5
View File
@@ -306,7 +306,7 @@ export class AIOStreams {
switch (addonId) {
case 'torbox': {
return await getTorboxStreams(this.config, streamRequest);
return await getTorboxStreams(this.config, addon.options, streamRequest);
}
case 'torrentio': {
return await getTorrentioStreams(
@@ -315,15 +315,17 @@ export class AIOStreams {
streamRequest
);
}
case 'gdrive':
const addonUrl = addon.options.addonUrl.replace('/manifest.json', '/');
const wrapper = new BaseWrapper('GDrive', addonUrl);
case 'gdrive': {
let addonUrl = addon.options.addonUrl as string;
addonUrl = addonUrl.replace('/manifest.json', '/')
const wrapper = new BaseWrapper('GDrive', addonUrl, addon.options.indexerTimeout ? addon.options.indexerTimeout as number : undefined);
return await wrapper.getParsedStreams(streamRequest);
}
default: {
console.log(
`Using base wrapper for addon ${addon.options.name} with url ${addon.options.url}`
);
const wrapper = new BaseWrapper(addon.options.name, addon.options.url);
const wrapper = new BaseWrapper(addon.options.name as string, addon.options.url as string, addon.options.indexerTimeout ? addon.options.indexerTimeout as number : undefined);
return await wrapper.getParsedStreams(streamRequest);
}
}
+80 -3
View File
@@ -14,7 +14,6 @@ export const addonDetails: AddonDetail[] = [
{
id: 'overrideUrl',
required: false,
label: 'Override URL',
description:
'Override the URL used to fetch streams from the torrentio addon',
@@ -23,17 +22,40 @@ export const addonDetails: AddonDetail[] = [
{
id: 'useMultipleInstances',
required: false,
label: 'Use Multiple Instances',
description:
'Use multiple instances of the torrentio addon to fetch streams when using multiple services',
type: 'checkbox',
},
{
id: 'indexerTimeout',
required: false,
label: 'Override Indexer Timeout',
description: 'The timeout for fetching streams from the Torrentio addon',
type: 'number',
constraints: {
min: 1000,
max: 20000
}
}
],
},
{
name: 'Torbox',
id: 'torbox',
options: [
{
id: 'indexerTimeout',
required: false,
label: 'Override Indexer Timeout',
description: 'The timeout for fetching streams from the Torbox addon in milliseconds',
type: 'number',
constraints: {
min: 1000,
max: 20000
}
}
],
},
{
name: 'Google Drive (Viren070)',
@@ -46,6 +68,17 @@ export const addonDetails: AddonDetail[] = [
description: 'The URL of the Google Drive addon',
type: 'text',
},
{
id: 'indexerTimeout',
required: false,
label: 'Override Indexer Timeout',
description: 'The timeout for fetching streams from the Google Drive addon in milliseconds',
type: 'number',
constraints: {
min: 1000,
max: 20000
}
}
],
},
{
@@ -66,6 +99,17 @@ export const addonDetails: AddonDetail[] = [
label: 'Name',
type: 'text',
},
{
id: 'indexerTimeout',
required: false,
label: 'Override Indexer Timeout',
description: 'The timeout for fetching streams from the custom addon in milliseconds',
type: 'number',
constraints: {
min: 1000,
max: 20000
}
}
],
},
];
@@ -255,7 +299,7 @@ export function validateConfig(config: Config): {
) {
console.log('checking url', addon.options[option.id]);
try {
new URL(addon.options[option.id]);
new URL(addon.options[option.id] as string);
} catch (_) {
return createResponse(
false,
@@ -264,6 +308,39 @@ export function validateConfig(config: Config): {
);
}
}
if (option.type === 'number' && addon.options[option.id]) {
if (typeof addon.options[option.id] !== 'number') {
return createResponse(
false,
'invalidNumber',
`${option.label} must be a number`
);
}
if (option.constraints) {
if (
option.constraints.min !== undefined &&
addon.options[option.id] as number < option.constraints.min
) {
return createResponse(
false,
'invalidNumber',
`${option.label} must be greater than or equal to ${option.constraints.min}`
);
}
if (
option.constraints.max !== undefined &&
addon.options[option.id] as number > option.constraints.max
) {
return createResponse(
false,
'invalidNumber',
`${option.label} must be less than or equal to ${option.constraints.max}`
);
}
}
}
}
}
}
@@ -90,7 +90,9 @@
align-items: center;
}
.option input[type='text'] {
.option input[type='text'],
.option input[type='number']
{
padding: 5px;
border-radius: 4px;
border: 1px solid #555;
+19 -25
View File
@@ -1,28 +1,12 @@
import React, { useState } from 'react';
import styles from './AddonsList.module.css';
interface AddonDetail {
name: string;
id: string;
options?: {
id: string;
required?: boolean;
label: string;
description?: string;
type: 'text' | 'checkbox';
}[];
}
interface Addon {
id: string;
options: { [key: string]: string };
}
import { AddonDetail, Config } from '@aiostreams/types';
interface AddonsListProps {
choosableAddons: string[];
addonDetails: AddonDetail[];
addons: Addon[];
setAddons: (addons: Addon[]) => void;
addons: Config['addons'];
setAddons: (addons: Config['addons']) => void;
}
const AddonsList: React.FC<AddonsListProps> = ({
@@ -49,10 +33,10 @@ const AddonsList: React.FC<AddonsListProps> = ({
const updateOption = (
addonIndex: number,
optionKey: string,
value: string
value?: string | boolean | number
) => {
const newAddons = [...addons];
newAddons[addonIndex].options[optionKey] = value.trim();
newAddons[addonIndex].options[optionKey] = typeof value === 'string' ? value.trim() : value;
setAddons(newAddons);
};
@@ -108,12 +92,12 @@ const AddonsList: React.FC<AddonsListProps> = ({
{option.type === 'checkbox' && (
<input
type="checkbox"
checked={addon.options[option.id] === 'true'}
checked={addon.options[option.id] === true}
onChange={(e) =>
updateOption(
index,
option.id,
e.target.checked.toString()
e.target.checked
)
}
className={styles.checkbox}
@@ -124,9 +108,19 @@ const AddonsList: React.FC<AddonsListProps> = ({
{option.type === 'text' && (
<input
type="text"
value={addon.options[option.id] || ''}
value={addon.options[option.id] as string || '' }
onChange={(e) =>
updateOption(index, option.id, e.target.value)
updateOption(index, option.id, e.target.value ? e.target.value : undefined)
}
className={styles.textInput}
/>
)}
{option.type === 'number' && (
<input
type="number"
value={addon.options[option.id] as number}
onChange={(e) =>
updateOption(index, option.id, e.target.value ? parseInt(e.target.value) : undefined)
}
className={styles.textInput}
/>
+28 -8
View File
@@ -108,7 +108,7 @@ export interface Config {
minSize: number | null;
addons: {
id: string;
options: { [key: string]: string };
options: { [key: string]: string | boolean | number | undefined };
}[];
services: {
name: string;
@@ -118,14 +118,34 @@ export interface Config {
}[];
}
interface BaseOptionDetail {
id: string;
required?: boolean;
label: string;
description?: string;
}
export interface TextOptionDetail extends BaseOptionDetail {
type: 'text';
}
export interface CheckboxOptionDetail extends BaseOptionDetail {
type: 'checkbox';
}
export interface NumberOptionDetail extends BaseOptionDetail {
type: 'number';
constraints: {
min?: number;
max?: number;
}
}
export type AddonOptionDetail = TextOptionDetail | CheckboxOptionDetail | NumberOptionDetail;
export interface AddonDetail {
name: string;
id: string;
options?: {
id: string;
required?: boolean;
label: string;
description?: string;
type: 'text' | 'checkbox';
}[];
options?: AddonOptionDetail[];
}
+6 -3
View File
@@ -19,8 +19,8 @@ interface TorboxStream {
export class Torbox extends BaseWrapper {
private readonly name: string = 'Torbox';
constructor(apiKey: string) {
super('Torbox', 'https://stremio.torbox.app/' + apiKey + '/', 10000);
constructor(apiKey: string, indexerTimeout: number = 10000) {
super('Torbox', 'https://stremio.torbox.app/' + apiKey + '/', indexerTimeout);
}
protected parseStream(stream: TorboxStream): ParsedStream | undefined {
@@ -100,6 +100,9 @@ export class Torbox extends BaseWrapper {
export async function getTorboxStreams(
config: Config,
torboxOptions: {
indexerTimeout?: number;
},
streamRequest: StreamRequest
): Promise<ParsedStream[]> {
const torboxService = config.services.find(
@@ -114,6 +117,6 @@ export async function getTorboxStreams(
throw new Error('Torbox API key not found');
}
const torbox = new Torbox(torboxApiKey);
const torbox = new Torbox(torboxApiKey, torboxOptions.indexerTimeout);
return await torbox.getParsedStreams(streamRequest);
}
+11 -7
View File
@@ -6,7 +6,7 @@ import { BaseWrapper } from './base';
export class Torrentio extends BaseWrapper {
private readonly name: string = 'Torrentio';
constructor(configString: string | null, overrideUrl: string | null) {
constructor(configString: string | null, overrideUrl: string | null, indexerTimeout: number = 10000) {
if (overrideUrl && overrideUrl.endsWith('/manifest.json')) {
overrideUrl = overrideUrl.replace('/manifest.json', '/');
}
@@ -16,7 +16,7 @@ export class Torrentio extends BaseWrapper {
: 'https://torrentio.strem.fun/' +
(configString ? configString + '/' : '');
super('Torrentio', url);
super('Torrentio', url, indexerTimeout);
}
protected parseStream(stream: Stream): ParsedStream {
@@ -68,7 +68,11 @@ export class Torrentio extends BaseWrapper {
export async function getTorrentioStreams(
config: Config,
torrentioOptions: { [key: string]: string },
torrentioOptions: {
useMultipleInstances?: boolean;
overrideUrl?: string;
indexerTimeout?: number;
},
streamRequest: StreamRequest
): Promise<ParsedStream[]> {
const supportedServices: string[] = [
@@ -84,7 +88,7 @@ export async function getTorrentioStreams(
// If overrideUrl is provided, use it to get streams and skip all other steps
if (torrentioOptions.overrideUrl) {
const torrentio = new Torrentio(null, torrentioOptions.overrideUrl);
const torrentio = new Torrentio(null, torrentioOptions.overrideUrl as string, torrentioOptions.indexerTimeout);
return torrentio.getParsedStreams(streamRequest);
}
@@ -95,7 +99,7 @@ export async function getTorrentioStreams(
// if no usable services found, use torrentio without any configuration
if (usableServices.length < 0) {
const torrentio = new Torrentio(null, null);
const torrentio = new Torrentio(null, null, torrentioOptions.indexerTimeout);
return await torrentio.getParsedStreams(streamRequest);
}
@@ -117,7 +121,7 @@ export async function getTorrentioStreams(
}
console.log('Creating Torrentio instance with service:', service.id);
let configString = getServicePair(service.id, service.credentials);
const torrentio = new Torrentio(configString, null);
const torrentio = new Torrentio(configString, null, torrentioOptions.indexerTimeout);
const streams = await torrentio.getParsedStreams(streamRequest);
parsedStreams.push(...streams);
}
@@ -130,7 +134,7 @@ export async function getTorrentioStreams(
}
configString += getServicePair(service.id, service.credentials) + '|';
}
const torrentio = new Torrentio(configString, null);
const torrentio = new Torrentio(configString, null, torrentioOptions.indexerTimeout);
return await torrentio.getParsedStreams(streamRequest);
}
}