mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
style: format
This commit is contained in:
@@ -29,4 +29,4 @@ jobs:
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/viren070/aiostreams:${{ github.ref_name }}
|
||||
ghcr.io/viren070/aiostreams:latest
|
||||
ghcr.io/viren070/aiostreams:latest
|
||||
|
||||
@@ -3,54 +3,54 @@
|
||||
> [!WARNING]
|
||||
> This addon is currently still in development. Things may not work as expected.
|
||||
|
||||
Combines streams from other addons into one and provides much greater customisation:
|
||||
Combines streams from other addons into one and provides much greater customisation:
|
||||
|
||||
- Change the format of the resutls
|
||||
- Filter all results by resolution, quality, visual tags.
|
||||
- Sort all results by quality, resolution, size, cached, visual tags, seeders
|
||||
- Prioritise specific languages
|
||||
- Specify a minimum and/or maximum size
|
||||
|
||||
You simply configure your options, add any API keys for any services you use, then enable whichever addons you want, and install.
|
||||
You simply configure your options, add any API keys for any services you use, then enable whichever addons you want, and install.
|
||||
|
||||
The addon will scrape all results from all addons, apply your configuration, and give the results back to you in one go.
|
||||
The addon will scrape all results from all addons, apply your configuration, and give the results back to you in one go.
|
||||
|
||||
> [!NOTE]
|
||||
> Do not install other addons that you have enabled through this addon. You will only cause unnecessary requests to the addon.
|
||||
> I also do not recommend installing/enabling too many addons as they all scrape mostly the same sources.
|
||||
|
||||
|
||||
> I also do not recommend installing/enabling too many addons as they all scrape mostly the same sources.
|
||||
|
||||
## FAQ
|
||||
|
||||
### How does it work?
|
||||
### How does it work?
|
||||
|
||||
The addon has parsers for specific addons and can extract the relevant information.
|
||||
It goes through each addon you selected and obtains the results with all the parsed information.
|
||||
The addon has parsers for specific addons and can extract the relevant information.
|
||||
It goes through each addon you selected and obtains the results with all the parsed information.
|
||||
|
||||
Once it has all the parsed information for each result, it can apply your configured sorting and filtering options.
|
||||
Once it has all the parsed information for each result, it can apply your configured sorting and filtering options.
|
||||
|
||||
### What are the currently supported addons?
|
||||
### What are the currently supported addons?
|
||||
|
||||
It currently supports:
|
||||
|
||||
It currently supports:
|
||||
- Torrentio
|
||||
- Torbox
|
||||
- Custom: You can input an addon URL and name and it will parse as much information as it can.
|
||||
> [!NOTE]
|
||||
> The URL can either be a URL to the manifest.json or the url without the manifest.json
|
||||
> e.g. `https://torrentio.strem.fun/` or `https://torrentio.strem.fun/manifest.json`
|
||||
> [!NOTE]
|
||||
> The URL can either be a URL to the manifest.json or the url without the manifest.json
|
||||
> e.g. `https://torrentio.strem.fun/` or `https://torrentio.strem.fun/manifest.json`
|
||||
|
||||
### What are the supported formatters?
|
||||
### What are the supported formatters?
|
||||
|
||||
The addon can display your results in different formats. The two formats available are:
|
||||
The addon can display your results in different formats. The two formats available are:
|
||||
|
||||
- gdrive:
|
||||
Uses the format from this [Stremio GDrive](https://github.com/Viren070/stremio-gdrive-addon) addon
|
||||
|
||||
|
||||

|
||||
|
||||
- torrentio:
|
||||
Uses the format from the Torrentio addon.
|
||||
|
||||
|
||||

|
||||
|
||||
- torbox:
|
||||
@@ -58,11 +58,9 @@ The addon can display your results in different formats. The two formats availab
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## Self-Hosting
|
||||
|
||||
### From source
|
||||
### From source
|
||||
|
||||
You need Node.js and git installed. Node v22 and npm v10.9 were used in the development of this project. I can not guarantee earlier versions will work.
|
||||
|
||||
@@ -73,19 +71,18 @@ You need Node.js and git installed. Node v22 and npm v10.9 were used in the deve
|
||||
```
|
||||
cd aiostreams
|
||||
```
|
||||
3. Install project dependencies
|
||||
2. Install project dependencies
|
||||
```
|
||||
npm i
|
||||
```
|
||||
4. Build project
|
||||
3. Build project
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
5. Run project
|
||||
4. Run project
|
||||
```
|
||||
npm run start:addon
|
||||
```
|
||||
6. Go to `http://localhost:3000/configure`
|
||||
|
||||
You can change the PORT environment variable to change the port that the addon will listen on.
|
||||
5. Go to `http://localhost:3000/configure`
|
||||
|
||||
You can change the PORT environment variable to change the port that the addon will listen on.
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { BaseWrapper, getTorboxStreams, getTorrentioStreams } from '@aiostreams/wrappers';
|
||||
import {
|
||||
BaseWrapper,
|
||||
getTorboxStreams,
|
||||
getTorrentioStreams,
|
||||
} from '@aiostreams/wrappers';
|
||||
import { Stream, ParsedStream, StreamRequest, Config } from '@aiostreams/types';
|
||||
import { gdriveFormat, torrentioFormat, torboxFormat } from '@aiostreams/formatters';
|
||||
import {
|
||||
gdriveFormat,
|
||||
torrentioFormat,
|
||||
torboxFormat,
|
||||
} from '@aiostreams/formatters';
|
||||
|
||||
export class AIOStreams {
|
||||
private config: Config;
|
||||
@@ -244,7 +252,7 @@ export class AIOStreams {
|
||||
streamRequest: StreamRequest
|
||||
): Promise<ParsedStream[]> {
|
||||
const parsedStreams: ParsedStream[] = [];
|
||||
|
||||
|
||||
for (const addon of this.config.addons) {
|
||||
try {
|
||||
const streams = await this.getStreamsFromAddon(addon.id, streamRequest);
|
||||
@@ -270,10 +278,16 @@ export class AIOStreams {
|
||||
return await getTorboxStreams(this.config, streamRequest);
|
||||
}
|
||||
case 'torrentio': {
|
||||
return await getTorrentioStreams(this.config, addon.options, streamRequest);
|
||||
return await getTorrentioStreams(
|
||||
this.config,
|
||||
addon.options,
|
||||
streamRequest
|
||||
);
|
||||
}
|
||||
default: {
|
||||
console.log(`Using base wrapper for addon ${addon.options.name} with url ${addon.options.url}`);
|
||||
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);
|
||||
return await wrapper.getParsedStreams(streamRequest);
|
||||
}
|
||||
|
||||
+182
-112
@@ -1,10 +1,8 @@
|
||||
import { Config } from "@aiostreams/types";
|
||||
import { AddonDetail } from "@aiostreams/types";
|
||||
|
||||
import { Config } from '@aiostreams/types';
|
||||
import { AddonDetail } from '@aiostreams/types';
|
||||
|
||||
export const allowedFormatters = ['gdrive', 'torrentio', 'torbox'];
|
||||
|
||||
|
||||
export const addonDetails: AddonDetail[] = [
|
||||
{
|
||||
name: 'Torrentio',
|
||||
@@ -64,13 +62,18 @@ export const addonDetails: AddonDetail[] = [
|
||||
description: 'The name of the custom addon',
|
||||
label: 'Name',
|
||||
type: 'text',
|
||||
}
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
export const allowedLanguages = ['English', 'Spanish', 'French', 'German', 'Chinese'];
|
||||
export const allowedLanguages = [
|
||||
'English',
|
||||
'Spanish',
|
||||
'French',
|
||||
'German',
|
||||
'Chinese',
|
||||
];
|
||||
|
||||
export const serviceCredentials = [
|
||||
{
|
||||
@@ -81,8 +84,8 @@ export const serviceCredentials = [
|
||||
label: 'API Key',
|
||||
id: 'apiKey',
|
||||
link: 'https://real-debrid.com/apitoken',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'All Debrid',
|
||||
@@ -92,8 +95,8 @@ export const serviceCredentials = [
|
||||
label: 'API Key',
|
||||
id: 'apiKey',
|
||||
link: 'https://alldebrid.com/apikeys',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Premiumize',
|
||||
@@ -103,8 +106,8 @@ export const serviceCredentials = [
|
||||
label: 'API Key',
|
||||
id: 'apiKey',
|
||||
link: 'https://www.premiumize.me/account',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Debrid Link',
|
||||
@@ -114,8 +117,8 @@ export const serviceCredentials = [
|
||||
label: 'API Key',
|
||||
id: 'apiKey',
|
||||
link: 'https://debrid-link.com/webapp/apikey',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Torbox',
|
||||
@@ -125,8 +128,8 @@ export const serviceCredentials = [
|
||||
label: 'API Key',
|
||||
id: 'apiKey',
|
||||
link: 'https://torbox.app/settings',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Offcloud',
|
||||
@@ -136,8 +139,8 @@ export const serviceCredentials = [
|
||||
label: 'API Key',
|
||||
id: 'apiKey',
|
||||
link: 'https://offcloud.com/#/account',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'put.io',
|
||||
@@ -152,8 +155,8 @@ export const serviceCredentials = [
|
||||
label: 'Token',
|
||||
id: 'token',
|
||||
link: 'https://put.io/oauth',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Easynews',
|
||||
@@ -168,115 +171,182 @@ export const serviceCredentials = [
|
||||
label: 'Password',
|
||||
id: 'password',
|
||||
link: 'https://www.easynews.com/',
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
];
|
||||
|
||||
export function validateConfig(config: Config): { valid: boolean; errorCode: string | null; errorMessage: string | null } {
|
||||
export function validateConfig(config: Config): {
|
||||
valid: boolean;
|
||||
errorCode: string | null;
|
||||
errorMessage: string | null;
|
||||
} {
|
||||
const createResponse = (
|
||||
valid: boolean,
|
||||
errorCode: string | null,
|
||||
errorMessage: string | null
|
||||
) => {
|
||||
return { valid, errorCode, errorMessage };
|
||||
};
|
||||
|
||||
const createResponse = (valid: boolean, errorCode: string | null, errorMessage: string | null) => {
|
||||
return { valid, errorCode, errorMessage };
|
||||
}
|
||||
// check for any duplicate addons where both the ID and options are the same
|
||||
const duplicateAddons = config.addons.filter(
|
||||
(addon, index) =>
|
||||
config.addons.findIndex(
|
||||
(a) =>
|
||||
a.id === addon.id &&
|
||||
JSON.stringify(a.options) === JSON.stringify(addon.options)
|
||||
) !== index
|
||||
);
|
||||
|
||||
// check for any duplicate addons where both the ID and options are the same
|
||||
const duplicateAddons = config.addons.filter(
|
||||
(addon, index) =>
|
||||
config.addons.findIndex(
|
||||
(a) =>
|
||||
a.id === addon.id &&
|
||||
JSON.stringify(a.options) === JSON.stringify(addon.options)
|
||||
) !== index
|
||||
if (duplicateAddons.length > 0) {
|
||||
return createResponse(
|
||||
false,
|
||||
'duplicateAddons',
|
||||
'Duplicate addons found. Please remove any duplicates'
|
||||
);
|
||||
}
|
||||
|
||||
if (duplicateAddons.length > 0) {
|
||||
return createResponse(false, 'duplicateAddons', 'Duplicate addons found. Please remove any duplicates');
|
||||
}
|
||||
|
||||
|
||||
for (const addon of config.addons) {
|
||||
// if torbox addon is enabled, torbox service must be enabled and torbox api key must be set
|
||||
if (addon.id === 'torbox') {
|
||||
const torboxService = config.services.find(
|
||||
(service) => service.id === 'torbox'
|
||||
for (const addon of config.addons) {
|
||||
// if torbox addon is enabled, torbox service must be enabled and torbox api key must be set
|
||||
if (addon.id === 'torbox') {
|
||||
const torboxService = config.services.find(
|
||||
(service) => service.id === 'torbox'
|
||||
);
|
||||
if (!torboxService) {
|
||||
return createResponse(
|
||||
false,
|
||||
'torboxServiceNotEnabled',
|
||||
'Torbox service must be enabled to use the Torbox addon'
|
||||
);
|
||||
if (!torboxService) {
|
||||
return createResponse(false, 'torboxServiceNotEnabled', 'Torbox service must be enabled to use the Torbox addon');
|
||||
}
|
||||
if (!torboxService.credentials.apiKey) {
|
||||
return createResponse(false, 'torboxApiKeyNotSet', 'Torbox API Key must be set to use the Torbox addon');
|
||||
|
||||
}
|
||||
}
|
||||
const details = addonDetails.find((detail) => detail.id === addon.id);
|
||||
if (!details) {
|
||||
return createResponse(false, 'invalidAddon', `Invalid addon: ${addon.id}`);
|
||||
}
|
||||
if (details.options) {
|
||||
for (const option of details.options) {
|
||||
if (option.required && !addon.options[option.id]) {
|
||||
return createResponse(false, 'missingRequiredOption', `Option ${option.label} is required for addon ${addon.id}`);
|
||||
}
|
||||
|
||||
if (option.id.toLowerCase().includes('url') && addon.options[option.id]) {
|
||||
console.log('checking url', addon.options[option.id]);
|
||||
try {
|
||||
new URL(addon.options[option.id]);
|
||||
} catch (_) {
|
||||
return createResponse(false, 'invalidUrl', `Invalid URL for ${option.label}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowedFormatters.includes(config.formatter)) {
|
||||
return createResponse(false, 'invalidFormatter', `Invalid formatter: ${config.formatter}`);
|
||||
}
|
||||
|
||||
for (const service of config.services) {
|
||||
if (service.enabled) {
|
||||
const serviceDetail = serviceCredentials.find(
|
||||
(detail) => detail.id === service.id
|
||||
if (!torboxService.credentials.apiKey) {
|
||||
return createResponse(
|
||||
false,
|
||||
'torboxApiKeyNotSet',
|
||||
'Torbox API Key must be set to use the Torbox addon'
|
||||
);
|
||||
if (!serviceDetail) {
|
||||
return createResponse(false, 'invalidService', `Invalid service: ${service.id}`);
|
||||
|
||||
}
|
||||
}
|
||||
const details = addonDetails.find((detail) => detail.id === addon.id);
|
||||
if (!details) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidAddon',
|
||||
`Invalid addon: ${addon.id}`
|
||||
);
|
||||
}
|
||||
if (details.options) {
|
||||
for (const option of details.options) {
|
||||
if (option.required && !addon.options[option.id]) {
|
||||
return createResponse(
|
||||
false,
|
||||
'missingRequiredOption',
|
||||
`Option ${option.label} is required for addon ${addon.id}`
|
||||
);
|
||||
}
|
||||
for (const credential of serviceDetail.credentials) {
|
||||
if (!service.credentials[credential.id]) {
|
||||
return createResponse(false, 'missingCredential', `${credential.label} is required for ${service.name}`);
|
||||
|
||||
if (
|
||||
option.id.toLowerCase().includes('url') &&
|
||||
addon.options[option.id]
|
||||
) {
|
||||
console.log('checking url', addon.options[option.id]);
|
||||
try {
|
||||
new URL(addon.options[option.id]);
|
||||
} catch (_) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidUrl',
|
||||
`Invalid URL for ${option.label}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// need at least one visual tag, resolution, quality
|
||||
if (config.visualTags.length === 0) {
|
||||
return createResponse(false, 'noVisualTags', 'At least one visual tag must be selected');
|
||||
if (!allowedFormatters.includes(config.formatter)) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidFormatter',
|
||||
`Invalid formatter: ${config.formatter}`
|
||||
);
|
||||
}
|
||||
|
||||
for (const service of config.services) {
|
||||
if (service.enabled) {
|
||||
const serviceDetail = serviceCredentials.find(
|
||||
(detail) => detail.id === service.id
|
||||
);
|
||||
if (!serviceDetail) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidService',
|
||||
`Invalid service: ${service.id}`
|
||||
);
|
||||
}
|
||||
for (const credential of serviceDetail.credentials) {
|
||||
if (!service.credentials[credential.id]) {
|
||||
return createResponse(
|
||||
false,
|
||||
'missingCredential',
|
||||
`${credential.label} is required for ${service.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.resolutions.length === 0) {
|
||||
return createResponse(false, 'noResolutions', 'At least one resolution must be selected');
|
||||
// need at least one visual tag, resolution, quality
|
||||
if (config.visualTags.length === 0) {
|
||||
return createResponse(
|
||||
false,
|
||||
'noVisualTags',
|
||||
'At least one visual tag must be selected'
|
||||
);
|
||||
}
|
||||
|
||||
if (config.resolutions.length === 0) {
|
||||
return createResponse(
|
||||
false,
|
||||
'noResolutions',
|
||||
'At least one resolution must be selected'
|
||||
);
|
||||
}
|
||||
|
||||
if (config.qualities.length === 0) {
|
||||
return createResponse(
|
||||
false,
|
||||
'noQualities',
|
||||
'At least one quality must be selected'
|
||||
);
|
||||
}
|
||||
|
||||
if (config.minSize && config.maxSize) {
|
||||
if (config.minSize >= config.maxSize) {
|
||||
return createResponse(
|
||||
false,
|
||||
'invalidSizeRange',
|
||||
"Your minimum size limit can't be greater than or equal to your maximum size limit"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.qualities.length === 0) {
|
||||
return createResponse(false, 'noQualities', 'At least one quality must be selected');
|
||||
}
|
||||
if (config.addons.length < 1) {
|
||||
return createResponse(
|
||||
false,
|
||||
'noAddons',
|
||||
'At least one addon must be selected'
|
||||
);
|
||||
}
|
||||
|
||||
if (config.minSize && config.maxSize) {
|
||||
if (config.minSize >= config.maxSize) {
|
||||
return createResponse(false, 'invalidSizeRange', 'Your minimum size limit can\'t be greater than or equal to your maximum size limit');
|
||||
}
|
||||
}
|
||||
if (config.addons.length > 10) {
|
||||
return createResponse(
|
||||
false,
|
||||
'tooManyAddons',
|
||||
'You can only select a maximum of 10 addons'
|
||||
);
|
||||
}
|
||||
|
||||
if (config.addons.length < 1) {
|
||||
return createResponse(false, 'noAddons', 'At least one addon must be selected');
|
||||
}
|
||||
|
||||
if (config.addons.length > 10) {
|
||||
return createResponse(false, 'tooManyAddons', 'You can only select a maximum of 10 addons');
|
||||
}
|
||||
|
||||
return createResponse(true, null, null);
|
||||
};
|
||||
return createResponse(true, null, null);
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from './addon';
|
||||
export * from './config';
|
||||
export * from './config';
|
||||
|
||||
@@ -85,14 +85,13 @@ app.get('/:config/stream/:type/:id', (req: Request, res: Response) => {
|
||||
|
||||
const decodedPath = decodeURIComponent(req.path);
|
||||
|
||||
|
||||
const streamMatch = new RegExp(
|
||||
`/${config}/stream/(movie|series)/tt([0-9]{7,})(?::([0-9]+):([0-9]+))?\.json`
|
||||
).exec(decodedPath);
|
||||
|
||||
if (!streamMatch) {
|
||||
// log after removing config if present
|
||||
console.error(`Invalid request: ${decodedPath.replace(`/${config}`, '')}`);
|
||||
console.error(`Invalid request: ${decodedPath.replace(`/${config}`, '')}`);
|
||||
res.status(400).send('Invalid request');
|
||||
return;
|
||||
}
|
||||
@@ -109,7 +108,9 @@ app.get('/:config/stream/:type/:id', (req: Request, res: Response) => {
|
||||
case 'series':
|
||||
if (!season || !episode) {
|
||||
res.status(400).send('Invalid request');
|
||||
console.log(`Request type was series but season or episode was missing`);
|
||||
console.log(
|
||||
`Request type was series but season or episode was missing`
|
||||
);
|
||||
return;
|
||||
}
|
||||
streamRequest = {
|
||||
@@ -146,8 +147,8 @@ app.get('/:config/stream/:type/:id', (req: Request, res: Response) => {
|
||||
name: 'Invalid Config',
|
||||
description: errorMessage,
|
||||
},
|
||||
]
|
||||
})
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const aioStreams = new AIOStreams(configJson);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export * from './gdrive';
|
||||
export * from './utils';
|
||||
export * from './torrentio';
|
||||
export * from './torbox';
|
||||
export * from './torbox';
|
||||
|
||||
@@ -8,7 +8,7 @@ export function torboxFormat(stream: ParsedStream): {
|
||||
let name: string = '';
|
||||
|
||||
name += `${stream.addonName}`;
|
||||
|
||||
|
||||
if (stream.provider) {
|
||||
name += stream.provider.cached
|
||||
? ` (Instant ${stream.provider.name})`
|
||||
@@ -21,13 +21,17 @@ export function torboxFormat(stream: ParsedStream): {
|
||||
|
||||
name += ` (${stream.resolution})`;
|
||||
|
||||
|
||||
|
||||
let description: string = '';
|
||||
|
||||
description += `Quality: ${stream.quality}\nName: ${stream.filename}\nSize: ${formatSize(stream.size || 0)}\nLanguage: ${stream.languages.length > 0 ? stream.languages.join(', ') : 'Unknown'}`;
|
||||
|
||||
let streamType = stream.torrent ? 'Torrent' : stream.usenet ? 'Usenet' : stream.url ? 'Direct' : 'Unknown';
|
||||
let streamType = stream.torrent
|
||||
? 'Torrent'
|
||||
: stream.usenet
|
||||
? 'Usenet'
|
||||
: stream.url
|
||||
? 'Direct'
|
||||
: 'Unknown';
|
||||
description += `\nType: ${streamType}`;
|
||||
|
||||
if (streamType === 'Torrent' || streamType === 'Usenet') {
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.content {
|
||||
background-color: #000000; /* Slightly lighter black */
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -14,7 +14,13 @@ import ServiceInput from '../../components/ServiceInput';
|
||||
import AddonsList from '../../components/AddonsList';
|
||||
import { Slide, ToastContainer, toast } from 'react-toastify';
|
||||
import addonPackage from '../../../package.json';
|
||||
import { allowedFormatters, allowedLanguages, addonDetails, validateConfig, serviceCredentials } from '@aiostreams/config';
|
||||
import {
|
||||
allowedFormatters,
|
||||
allowedLanguages,
|
||||
addonDetails,
|
||||
validateConfig,
|
||||
serviceCredentials,
|
||||
} from '@aiostreams/config';
|
||||
|
||||
const version = addonPackage.version;
|
||||
|
||||
@@ -85,63 +91,52 @@ const defaultServices = [
|
||||
name: 'Real Debrid',
|
||||
id: 'realdebrid',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'All Debrid',
|
||||
id: 'alldebrid',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'Premiumize',
|
||||
id: 'premiumize',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'Debrid Link',
|
||||
id: 'debridlink',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'Torbox',
|
||||
id: 'torbox',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'Offcloud',
|
||||
id: 'offcloud',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'put.io',
|
||||
id: 'putio',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
{
|
||||
name: 'Easynews',
|
||||
id: 'easynews',
|
||||
enabled: false,
|
||||
credentials: {
|
||||
},
|
||||
credentials: {},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
||||
export default function Configure() {
|
||||
const [resolutions, setResolutions] =
|
||||
useState<Resolution[]>(defaultResolutions);
|
||||
@@ -209,7 +204,11 @@ export default function Configure() {
|
||||
|
||||
const { valid, errorCode, errorMessage } = validateConfig(config);
|
||||
if (!valid) {
|
||||
showToast(errorMessage || 'Invalid config', 'error', errorCode || 'error');
|
||||
showToast(
|
||||
errorMessage || 'Invalid config',
|
||||
'error',
|
||||
errorCode || 'error'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -477,51 +476,60 @@ export default function Configure() {
|
||||
newServices[serviceIndex] = { ...service, enabled };
|
||||
setServices(newServices);
|
||||
}}
|
||||
fields={serviceCredentials.find(
|
||||
(detail) => detail.id === service.id
|
||||
)?.credentials.map((credential) => ({
|
||||
label: credential.label,
|
||||
link: credential.link,
|
||||
value: service.credentials[credential.id] || '',
|
||||
setValue: (value) => {
|
||||
const newServices = [...services];
|
||||
const serviceIndex = newServices.findIndex(
|
||||
(s) => s.id === service.id
|
||||
);
|
||||
newServices[serviceIndex] = {
|
||||
...service,
|
||||
credentials: {
|
||||
...service.credentials,
|
||||
[credential.id]: value,
|
||||
fields={
|
||||
serviceCredentials
|
||||
.find((detail) => detail.id === service.id)
|
||||
?.credentials.map((credential) => ({
|
||||
label: credential.label,
|
||||
link: credential.link,
|
||||
value: service.credentials[credential.id] || '',
|
||||
setValue: (value) => {
|
||||
const newServices = [...services];
|
||||
const serviceIndex = newServices.findIndex(
|
||||
(s) => s.id === service.id
|
||||
);
|
||||
newServices[serviceIndex] = {
|
||||
...service,
|
||||
credentials: {
|
||||
...service.credentials,
|
||||
[credential.id]: value,
|
||||
},
|
||||
};
|
||||
setServices(newServices);
|
||||
},
|
||||
};
|
||||
setServices(newServices);
|
||||
},
|
||||
})) || []
|
||||
})) || []
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className={styles.section} style={{ marginTop: '20px', marginBottom: '0px' }}>
|
||||
<div className={styles.setting}>
|
||||
<div className={styles.settingDescription}>
|
||||
<h2 style={{ padding: '5px' }}>Only Show Cached Streams</h2>
|
||||
<p style={{ padding: '5px' }}>
|
||||
Only show streams that are cached by the enabled services.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.settingInput}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={onlyShowCachedStreams}
|
||||
onChange={(e) => setOnlyShowCachedStreams(e.target.checked)}
|
||||
// move to the right
|
||||
style={{ marginLeft: 'auto', marginRight: '20px', width: '25px', height: '25px' }}
|
||||
/>
|
||||
<div
|
||||
className={styles.section}
|
||||
style={{ marginTop: '20px', marginBottom: '0px' }}
|
||||
>
|
||||
<div className={styles.setting}>
|
||||
<div className={styles.settingDescription}>
|
||||
<h2 style={{ padding: '5px' }}>Only Show Cached Streams</h2>
|
||||
<p style={{ padding: '5px' }}>
|
||||
Only show streams that are cached by the enabled services.
|
||||
</p>
|
||||
</div>
|
||||
<div className={styles.settingInput}>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={onlyShowCachedStreams}
|
||||
onChange={(e) => setOnlyShowCachedStreams(e.target.checked)}
|
||||
// move to the right
|
||||
style={{
|
||||
marginLeft: 'auto',
|
||||
marginRight: '20px',
|
||||
width: '25px',
|
||||
height: '25px',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<h2 style={{ padding: '5px' }}>Addons</h2>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -20,12 +16,8 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
],
|
||||
"@aiostreams/config": [
|
||||
"../addon/src/config"
|
||||
]
|
||||
"@/*": ["./src/*"],
|
||||
"@aiostreams/config": ["../addon/src/config"]
|
||||
},
|
||||
"noEmit": false,
|
||||
"isolatedModules": true
|
||||
@@ -38,13 +30,6 @@
|
||||
"path": "../addon"
|
||||
}
|
||||
],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
@@ -125,4 +125,3 @@ export interface AddonDetail {
|
||||
type: 'text' | 'checkbox';
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { BaseWrapper } from './base';
|
||||
import { Config, ParsedNameData, ParsedStream, StreamRequest } from '@aiostreams/types';
|
||||
import {
|
||||
Config,
|
||||
ParsedNameData,
|
||||
ParsedStream,
|
||||
StreamRequest,
|
||||
} from '@aiostreams/types';
|
||||
import { parseFilename } from '@aiostreams/parser';
|
||||
|
||||
interface TorboxStream {
|
||||
@@ -93,9 +98,13 @@ export class Torbox extends BaseWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTorboxStreams(config: Config, streamRequest: StreamRequest): Promise<ParsedStream[]> {
|
||||
|
||||
const torboxService = config.services.find((service) => service.id === 'torbox');
|
||||
export async function getTorboxStreams(
|
||||
config: Config,
|
||||
streamRequest: StreamRequest
|
||||
): Promise<ParsedStream[]> {
|
||||
const torboxService = config.services.find(
|
||||
(service) => service.id === 'torbox'
|
||||
);
|
||||
if (!torboxService) {
|
||||
throw new Error('Torbox service not found');
|
||||
}
|
||||
@@ -107,4 +116,4 @@ export async function getTorboxStreams(config: Config, streamRequest: StreamRequ
|
||||
|
||||
const torbox = new Torbox(torboxApiKey);
|
||||
return await torbox.getParsedStreams(streamRequest);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ export class Torrentio extends BaseWrapper {
|
||||
private readonly name: string = 'Torrentio';
|
||||
|
||||
constructor(configString: string | null, overrideUrl: string | null) {
|
||||
|
||||
if (overrideUrl && overrideUrl.endsWith('/manifest.json')) {
|
||||
overrideUrl = overrideUrl.replace('/manifest.json', '/');
|
||||
}
|
||||
|
||||
let url = overrideUrl ? overrideUrl : 'https://torrentio.strem.fun/' + (configString ? configString + '/' : '');
|
||||
let url = overrideUrl
|
||||
? overrideUrl
|
||||
: 'https://torrentio.strem.fun/' +
|
||||
(configString ? configString + '/' : '');
|
||||
|
||||
super('Torrentio', url);
|
||||
}
|
||||
@@ -64,7 +66,11 @@ export class Torrentio extends BaseWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTorrentioStreams(config: Config, torrentioOptions: { [key: string]: string }, streamRequest: StreamRequest): Promise<ParsedStream[]> {
|
||||
export async function getTorrentioStreams(
|
||||
config: Config,
|
||||
torrentioOptions: { [key: string]: string },
|
||||
streamRequest: StreamRequest
|
||||
): Promise<ParsedStream[]> {
|
||||
const supportedServices: string[] = [
|
||||
'realdebrid',
|
||||
'alldebrid',
|
||||
@@ -75,7 +81,7 @@ export async function getTorrentioStreams(config: Config, torrentioOptions: { [k
|
||||
'debridlink',
|
||||
];
|
||||
const parsedStreams: ParsedStream[] = [];
|
||||
|
||||
|
||||
// If overrideUrl is provided, use it to get streams and skip all other steps
|
||||
if (torrentioOptions.overrideUrl) {
|
||||
const torrentio = new Torrentio(null, torrentioOptions.overrideUrl);
|
||||
@@ -87,7 +93,6 @@ export async function getTorrentioStreams(config: Config, torrentioOptions: { [k
|
||||
supportedServices.includes(service.id)
|
||||
);
|
||||
|
||||
|
||||
// if no usable services found, use torrentio without any configuration
|
||||
if (usableServices.length < 0) {
|
||||
const torrentio = new Torrentio(null, null);
|
||||
@@ -96,9 +101,14 @@ export async function getTorrentioStreams(config: Config, torrentioOptions: { [k
|
||||
|
||||
// otherwise, depending on the configuration, create multiple instances of torrentio or use a single instance with all services
|
||||
|
||||
const getServicePair = (serviceId: string, credentials: { [key: string]: string }) => {
|
||||
return serviceId === 'putio' ? `${serviceId}=${credentials.clientId}@${credentials.token}` : `${serviceId}=${credentials.apiKey}`;
|
||||
}
|
||||
const getServicePair = (
|
||||
serviceId: string,
|
||||
credentials: { [key: string]: string }
|
||||
) => {
|
||||
return serviceId === 'putio'
|
||||
? `${serviceId}=${credentials.clientId}@${credentials.token}`
|
||||
: `${serviceId}=${credentials.apiKey}`;
|
||||
};
|
||||
|
||||
if (torrentioOptions.useMultipleInstances) {
|
||||
for (const service of usableServices) {
|
||||
@@ -123,8 +133,4 @@ export async function getTorrentioStreams(config: Config, torrentioOptions: { [k
|
||||
const torrentio = new Torrentio(configString, null);
|
||||
return await torrentio.getParsedStreams(streamRequest);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user