mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: configurable timeout for every addon
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user