mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: add comet and improve method of getting choosable addons
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
BaseWrapper,
|
||||
getCometStreams,
|
||||
getTorboxStreams,
|
||||
getTorrentioStreams,
|
||||
} from '@aiostreams/wrappers';
|
||||
@@ -315,6 +316,9 @@ export class AIOStreams {
|
||||
streamRequest
|
||||
);
|
||||
}
|
||||
case 'comet': {
|
||||
return await getCometStreams(this.config, addon.options, streamRequest);
|
||||
}
|
||||
case 'gdrive': {
|
||||
if (!addon.options.addonUrl) {
|
||||
throw new Error('The addon URL was undefined for GDrive');
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Config } from '@aiostreams/types';
|
||||
import { AddonDetail } from '@aiostreams/types';
|
||||
|
||||
export const allowedFormatters = ['gdrive', 'torrentio', 'torbox'];
|
||||
export const allowedAddons = ['torrentio', 'torbox', 'gdrive', 'custom'];
|
||||
export const allowedAddons = ['torrentio', 'comet', 'torbox', 'gdrive', 'custom'];
|
||||
|
||||
export const MAX_ADDONS = 10;
|
||||
export const MAX_SIZE = 150000000000; // 1500GB
|
||||
@@ -11,6 +11,8 @@ export const addonDetails: AddonDetail[] = [
|
||||
{
|
||||
name: 'Torrentio',
|
||||
id: 'torrentio',
|
||||
requiresService: false,
|
||||
supportedServices: ['realdebrid', 'alldebrid', 'premiumize', 'debridlink', 'torbox', 'offcloud', 'putio'],
|
||||
options: [
|
||||
{
|
||||
id: 'overrideName',
|
||||
@@ -49,9 +51,54 @@ export const addonDetails: AddonDetail[] = [
|
||||
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Comet',
|
||||
id: 'comet',
|
||||
requiresService: true,
|
||||
supportedServices: ['realdebrid', 'alldebrid', 'premiumize', 'debridlink', 'torbox'],
|
||||
options: [
|
||||
{
|
||||
id: 'prioritiseDebrid',
|
||||
required: false,
|
||||
label: 'Prioritise Debrid Service',
|
||||
description:
|
||||
'Prioritise a specific debrid service when fetching streams. This option is useful when you want to use a specific debrid service for fetching streams. By default, the addon will make a separate request for each debrid service. I highly recommend provding a value for this option as it will speed up the fetching process and remove redundant results.',
|
||||
type: 'select',
|
||||
options: [{ value: 'realdebrid', label: 'Real Debrid' }, { value: 'alldebrid', label: 'All Debrid' }, { value: 'premiumize', label: 'Premiumize' }, { value: 'debridlink', label: 'Debrid Link' }, { value: 'torbox', label: 'Torbox' }],
|
||||
},
|
||||
{
|
||||
id: 'overrideName',
|
||||
required: false,
|
||||
label: 'Override Addon Name',
|
||||
description: 'Override the name of the Comet addon that shows up in the results',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: 'overrideUrl',
|
||||
required: false,
|
||||
label: 'Override URL',
|
||||
description:
|
||||
'Override the URL used to fetch streams from the Comet addon. This option is incompatible with the prioritiseDebrid option. By default, the URL is generated based on the selected services and their credentials. Use this option to override the URL with a custom URL.',
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: 'indexerTimeout',
|
||||
required: false,
|
||||
label: 'Override Indexer Timeout',
|
||||
description: 'The timeout for fetching streams from the Comet addon. This is the time in milliseconds that the addon will wait for a response before timing out. Leave it empty to use the recommended timeout.',
|
||||
type: 'number',
|
||||
constraints: {
|
||||
min: 1000,
|
||||
max: 20000
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Torbox',
|
||||
id: 'torbox',
|
||||
requiresService: true,
|
||||
supportedServices: ['torbox'],
|
||||
options: [
|
||||
{
|
||||
id: 'indexerTimeout',
|
||||
@@ -76,6 +123,8 @@ export const addonDetails: AddonDetail[] = [
|
||||
{
|
||||
name: 'Google Drive (Viren070)',
|
||||
id: 'gdrive',
|
||||
requiresService: false,
|
||||
supportedServices: [],
|
||||
options: [
|
||||
{
|
||||
id: 'addonUrl',
|
||||
@@ -107,6 +156,8 @@ export const addonDetails: AddonDetail[] = [
|
||||
{
|
||||
name: 'Custom',
|
||||
id: 'custom',
|
||||
requiresService: false,
|
||||
supportedServices: [],
|
||||
options: [
|
||||
{
|
||||
id: 'name',
|
||||
|
||||
@@ -188,11 +188,16 @@ export default function Configure() {
|
||||
|
||||
const getChoosableAddons = () => {
|
||||
// only if torbox service is enabled we can use torbox addon
|
||||
const choosableAddons: string[] = ['torrentio', 'gdrive', 'custom'];
|
||||
if (
|
||||
services.some((service) => service.enabled && service.id === 'torbox')
|
||||
) {
|
||||
choosableAddons.push('torbox');
|
||||
const choosableAddons: string[] = [];
|
||||
for (const addon of addonDetails) {
|
||||
if (addon.requiresService) {
|
||||
// look through services and see if the ID of any service is in addon.supportedServices
|
||||
if (services.some((service) => addon.supportedServices.includes(service.id) && service.enabled && Object.keys(service.credentials).length > 0)) {
|
||||
choosableAddons.push(addon.id);
|
||||
}
|
||||
} else {
|
||||
choosableAddons.push(addon.id);
|
||||
}
|
||||
}
|
||||
return choosableAddons;
|
||||
};
|
||||
|
||||
@@ -90,6 +90,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.option select,
|
||||
.option input[type='text'],
|
||||
.option input[type='number']
|
||||
{
|
||||
|
||||
@@ -115,6 +115,23 @@ const AddonsList: React.FC<AddonsListProps> = ({
|
||||
className={styles.textInput}
|
||||
/>
|
||||
)}
|
||||
{option.type === 'select' && (
|
||||
<select
|
||||
value={addon.options[option.id] || ''}
|
||||
onChange={(e) =>
|
||||
updateOption(index, option.id, e.target.value ? e.target.value : undefined)
|
||||
}
|
||||
className={styles.textInput}
|
||||
>
|
||||
<option value={undefined}>None</option>
|
||||
{option.options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)
|
||||
}
|
||||
{option.type === 'number' && (
|
||||
<input
|
||||
type="number"
|
||||
|
||||
@@ -130,6 +130,11 @@ export interface TextOptionDetail extends BaseOptionDetail {
|
||||
type: 'text';
|
||||
}
|
||||
|
||||
export interface SelectOptionDetail extends BaseOptionDetail {
|
||||
type: 'select';
|
||||
options: { value: string, label: string }[];
|
||||
}
|
||||
|
||||
export interface CheckboxOptionDetail extends BaseOptionDetail {
|
||||
type: 'checkbox';
|
||||
}
|
||||
@@ -142,10 +147,12 @@ export interface NumberOptionDetail extends BaseOptionDetail {
|
||||
}
|
||||
}
|
||||
|
||||
export type AddonOptionDetail = TextOptionDetail | CheckboxOptionDetail | NumberOptionDetail;
|
||||
export type AddonOptionDetail = TextOptionDetail | SelectOptionDetail | CheckboxOptionDetail | NumberOptionDetail;
|
||||
|
||||
export interface AddonDetail {
|
||||
name: string;
|
||||
id: string;
|
||||
requiresService: boolean;
|
||||
supportedServices: string[];
|
||||
options?: AddonOptionDetail[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
import { ParsedNameData, StreamRequest } from '@aiostreams/types';
|
||||
import { parseFilename, extractSizeInBytes } from '@aiostreams/parser';
|
||||
import { ParsedStream, Stream, Config } from '@aiostreams/types';
|
||||
import { BaseWrapper } from './base';
|
||||
|
||||
interface CometStream extends Stream {
|
||||
torrentTitle?: string;
|
||||
torrentSize?: number;
|
||||
}
|
||||
|
||||
export class Comet extends BaseWrapper {
|
||||
constructor(configString: string | null, overrideUrl: string | null, indexerTimeout: number = 10000, addonName: string = 'Torrentio') {
|
||||
if (overrideUrl && overrideUrl.endsWith('/manifest.json')) {
|
||||
overrideUrl = overrideUrl.replace('/manifest.json', '/');
|
||||
}
|
||||
|
||||
let url = overrideUrl
|
||||
? overrideUrl
|
||||
: 'https://comet.elfhosted.com/' +
|
||||
(configString ? configString + '/' : '');
|
||||
|
||||
super(addonName, url, indexerTimeout);
|
||||
}
|
||||
|
||||
protected parseStream(stream: CometStream): ParsedStream {
|
||||
const filename = stream.behaviorHints?.filename?.trim() ||
|
||||
stream.description?.split('\n')[0] ||
|
||||
stream.torrentTitle;
|
||||
|
||||
const parsedFilename: ParsedNameData = parseFilename(filename || stream.description || '');
|
||||
const sizeInBytes = stream.torrentSize
|
||||
? stream.torrentSize
|
||||
: stream.title
|
||||
? extractSizeInBytes(stream.title, 1024)
|
||||
: undefined;
|
||||
|
||||
const debridMatch = RegExp(/^\[([a-zA-Z]{2})(\⚡)\]/).exec(
|
||||
stream.name!
|
||||
);
|
||||
const debrid = debridMatch
|
||||
? {
|
||||
provider: debridMatch[1],
|
||||
cached: debridMatch[2] === '⚡',
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const indexerMatch = RegExp(/🔎 ([a-zA-Z0-9]+)/).exec(stream.title!);
|
||||
const indexer = indexerMatch ? indexerMatch[1] : undefined;
|
||||
|
||||
return {
|
||||
...parsedFilename,
|
||||
filename,
|
||||
size: sizeInBytes,
|
||||
addonName: this.addonName,
|
||||
url: stream.url,
|
||||
externalUrl: stream.externalUrl,
|
||||
indexers: indexer,
|
||||
provider: debrid
|
||||
? {
|
||||
name: debrid.provider,
|
||||
cached: debrid.cached,
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const getCometConfig = (debridService: string, debridApiKey: string) => {
|
||||
return {
|
||||
"indexers": [
|
||||
"bitsearch",
|
||||
"eztv",
|
||||
"thepiratebay",
|
||||
"therarbg",
|
||||
"yts"
|
||||
],
|
||||
"maxResults": 0,
|
||||
"maxResultsPerResolution": 0,
|
||||
"maxSize": 0,
|
||||
"reverseResultOrder": false,
|
||||
"removeTrash": true,
|
||||
"resultFormat": [
|
||||
"All"
|
||||
],
|
||||
"resolutions": [
|
||||
"All"
|
||||
],
|
||||
"languages": [
|
||||
"All"
|
||||
],
|
||||
"debridService": debridService,
|
||||
"debridApiKey": debridApiKey,
|
||||
"stremthruUrl": "",
|
||||
"debridStreamProxyPassword": ""
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCometStreams(
|
||||
config: Config,
|
||||
cometOptions: {
|
||||
prioritiseDebrid?: string;
|
||||
overrideUrl?: string;
|
||||
indexerTimeout?: string;
|
||||
overrideName?: string;
|
||||
},
|
||||
streamRequest: StreamRequest
|
||||
): Promise<ParsedStream[]> {
|
||||
const supportedServices: string[] = [
|
||||
'realdebrid',
|
||||
'alldebrid',
|
||||
'premiumize',
|
||||
'torbox',
|
||||
'debridlink',
|
||||
];
|
||||
const parsedStreams: ParsedStream[] = [];
|
||||
const indexerTimeout = cometOptions.indexerTimeout ? parseInt(cometOptions.indexerTimeout) : undefined;
|
||||
|
||||
// If overrideUrl is provided, use it to get streams and skip all other steps
|
||||
if (cometOptions.overrideUrl) {
|
||||
const comet = new Comet(null, cometOptions.overrideUrl as string, indexerTimeout, cometOptions.overrideName);
|
||||
return comet.getParsedStreams(streamRequest);
|
||||
}
|
||||
|
||||
// find all usable services
|
||||
const usableServices = config.services.filter((service) =>
|
||||
supportedServices.includes(service.id)
|
||||
);
|
||||
|
||||
// if no usable services found, raise an error
|
||||
if (usableServices.length < 0) {
|
||||
throw new Error('No usable services found');
|
||||
}
|
||||
|
||||
|
||||
// otherwise, depending on the configuration, create multiple instances of comet or use a single instance with the prioritised service
|
||||
|
||||
if (cometOptions.prioritiseDebrid && !supportedServices.includes(cometOptions.prioritiseDebrid)) {
|
||||
throw new Error('Invalid debrid service');
|
||||
}
|
||||
|
||||
if (cometOptions.prioritiseDebrid) {
|
||||
const debridService = usableServices.find(
|
||||
(service) => service.id === cometOptions.prioritiseDebrid
|
||||
);
|
||||
if (!debridService) {
|
||||
throw new Error('Debrid service not found for ' + cometOptions.prioritiseDebrid);
|
||||
}
|
||||
if (!debridService.credentials.apiKey) {
|
||||
throw new Error('Debrid service API key not found for ' + cometOptions.prioritiseDebrid);
|
||||
}
|
||||
|
||||
// get the comet config and b64 encode it
|
||||
const cometConfig = getCometConfig(cometOptions.prioritiseDebrid, debridService.credentials.apiKey);
|
||||
const configString = Buffer.from(JSON.stringify(cometConfig)).toString('base64');
|
||||
const comet = new Comet(configString, null, indexerTimeout, cometOptions.overrideName);
|
||||
|
||||
return comet.getParsedStreams(streamRequest);
|
||||
}
|
||||
|
||||
// if no prioritised service is provided, create a comet instance for each service
|
||||
const servicesToUse = usableServices.filter((service) => service.enabled);
|
||||
if (servicesToUse.length < 1) {
|
||||
throw new Error('No supported service(s) enabled');
|
||||
}
|
||||
for (const service of servicesToUse) {
|
||||
const cometConfig = getCometConfig(service.id, service.credentials.apiKey);
|
||||
const configString = Buffer.from(JSON.stringify(cometConfig)).toString('base64');
|
||||
const comet = new Comet(configString, null, indexerTimeout, cometOptions.overrideName);
|
||||
const streams = await comet.getParsedStreams(streamRequest);
|
||||
parsedStreams.push(...streams);
|
||||
}
|
||||
|
||||
return parsedStreams;
|
||||
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './base';
|
||||
export * from './torbox';
|
||||
export * from './torrentio';
|
||||
export * from './comet';
|
||||
Reference in New Issue
Block a user