mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
@@ -11,6 +11,7 @@ import {
|
||||
getOrionStreams,
|
||||
getPeerflixStreams,
|
||||
getStremioJackettStreams,
|
||||
getStremThruStoreStreams,
|
||||
getTorboxStreams,
|
||||
getTorrentioStreams,
|
||||
} from '@aiostreams/wrappers';
|
||||
@@ -1080,6 +1081,14 @@ export class AIOStreams {
|
||||
addonId
|
||||
);
|
||||
}
|
||||
case 'stremthru-store': {
|
||||
return await getStremThruStoreStreams(
|
||||
this.config,
|
||||
addon.options,
|
||||
streamRequest,
|
||||
addonId
|
||||
);
|
||||
}
|
||||
case 'dmm-cast': {
|
||||
return await getDMMCastStreams(
|
||||
this.config,
|
||||
|
||||
@@ -687,6 +687,61 @@ export const addonDetails: AddonDetail[] = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'StremThru Store',
|
||||
id: 'stremthru-store',
|
||||
requiresService: true,
|
||||
supportedServices: [
|
||||
'torbox',
|
||||
'easydebrid',
|
||||
'realdebrid',
|
||||
'debridlink',
|
||||
'alldebrid',
|
||||
'premiumize',
|
||||
'offcloud',
|
||||
'pikpak',
|
||||
],
|
||||
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: 'torbox', label: 'Torbox' },
|
||||
{ value: 'easydebrid', label: 'EasyDebrid' },
|
||||
{ value: 'realdebrid', label: 'Real Debrid' },
|
||||
{ value: 'debridlink', label: 'Debrid Link' },
|
||||
{ value: 'alldebrid', label: 'All Debrid' },
|
||||
{ value: 'premiumize', label: 'Premiumize' },
|
||||
{ value: 'offcloud', label: 'Offcloud' },
|
||||
{ value: 'pikpak', label: 'PikPak' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'overrideName',
|
||||
required: false,
|
||||
label: 'Override Addon Name',
|
||||
description:
|
||||
"Override the name of the addon that shows up in the results. Leave it empty to use the default name of 'StremThru Store'.",
|
||||
type: 'text',
|
||||
},
|
||||
{
|
||||
id: 'indexerTimeout',
|
||||
required: false,
|
||||
label: 'Override Indexer Timeout',
|
||||
description:
|
||||
'The timeout for fetching streams from the StremThru Store addon in milliseconds. This is the time in milliseconds that the addon will wait for a response from StremThru Store before timing out. Leave it empty to use the recommended timeout.',
|
||||
type: 'number',
|
||||
constraints: {
|
||||
min: Settings.MIN_TIMEOUT,
|
||||
max: Settings.MAX_TIMEOUT,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'DMM Cast',
|
||||
id: 'dmm-cast',
|
||||
|
||||
@@ -237,6 +237,14 @@ export class Settings {
|
||||
? parseInt(process.env.DEFAULT_DEBRIDIO_TIMEOUT)
|
||||
: undefined;
|
||||
|
||||
public static readonly STREMTHRU_STORE_URL =
|
||||
process.env.STREMTHRU_STORE_URL ||
|
||||
'https://stremthru.elfhosted.com/stremio/store/';
|
||||
public static readonly DEFAULT_STREMTHRU_STORE_TIMEOUT = process.env
|
||||
.DEFAULT_STREMTHRU_STORE_TIMEOUT
|
||||
? parseInt(process.env.DEFAULT_STREMTHRU_STORE_TIMEOUT)
|
||||
: undefined;
|
||||
|
||||
public static readonly DEFAULT_DMM_CAST_TIMEOUT = process.env
|
||||
.DEFAULT_DMM_CAST_TIMEOUT
|
||||
? parseInt(process.env.DEFAULT_DMM_CAST_TIMEOUT)
|
||||
|
||||
@@ -12,3 +12,4 @@ export * from './orion';
|
||||
export * from './peerflix';
|
||||
export * from './dmmCast';
|
||||
export * from './stremio-jackett';
|
||||
export * from './stremthruStore';
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
import { AddonDetail, ParseResult, StreamRequest } from '@aiostreams/types';
|
||||
import { ParsedStream, Config } from '@aiostreams/types';
|
||||
import { BaseWrapper } from './base';
|
||||
import { addonDetails, createLogger } from '@aiostreams/utils';
|
||||
import { Settings } from '@aiostreams/utils';
|
||||
import { Stream } from 'stream';
|
||||
|
||||
const logger = createLogger('wrappers');
|
||||
|
||||
export class StremThruStore extends BaseWrapper {
|
||||
constructor(
|
||||
configString: string | null,
|
||||
overrideUrl: string | null,
|
||||
addonName: string = 'ST Store',
|
||||
addonId: string,
|
||||
userConfig: Config,
|
||||
indexerTimeout?: number
|
||||
) {
|
||||
let url = overrideUrl
|
||||
? overrideUrl
|
||||
: Settings.STREMTHRU_STORE_URL + (configString ? configString + '/' : '');
|
||||
|
||||
super(
|
||||
addonName,
|
||||
url,
|
||||
addonId,
|
||||
userConfig,
|
||||
indexerTimeout || Settings.DEFAULT_STREMTHRU_STORE_TIMEOUT
|
||||
);
|
||||
}
|
||||
|
||||
protected parseStream(stream: { [key: string]: string }): ParseResult {
|
||||
const parsedResult = super.parseStream(stream);
|
||||
if (parsedResult.type === 'stream' && parsedResult.result.provider?.id) {
|
||||
parsedResult.result.provider = {
|
||||
...parsedResult.result.provider,
|
||||
cached: true,
|
||||
};
|
||||
|
||||
// all st store results are "personal" streams.
|
||||
parsedResult.result.personal = true;
|
||||
// ST store results use a cogwheel emoji (⚙️) for the release group, this is mistakenly identified as an indexer.
|
||||
// remove it (personal results don't have an indexer anyway)
|
||||
parsedResult.result.indexers = undefined;
|
||||
}
|
||||
return parsedResult;
|
||||
}
|
||||
}
|
||||
export async function getStremThruStoreStreams(
|
||||
config: Config,
|
||||
stremthruStoreOptions: {
|
||||
prioritiseDebrid?: string;
|
||||
overrideUrl?: string;
|
||||
indexerTimeout?: string;
|
||||
overrideName?: string;
|
||||
},
|
||||
streamRequest: StreamRequest,
|
||||
addonId: string
|
||||
): Promise<{ addonStreams: ParsedStream[]; addonErrors: string[] }> {
|
||||
const supportedServices: string[] =
|
||||
addonDetails.find((addon: AddonDetail) => addon.id === 'stremthru-store')
|
||||
?.supportedServices || [];
|
||||
const parsedStreams: ParsedStream[] = [];
|
||||
const indexerTimeout = stremthruStoreOptions.indexerTimeout
|
||||
? parseInt(stremthruStoreOptions.indexerTimeout)
|
||||
: undefined;
|
||||
|
||||
// If overrideUrl is provided, use it to get streams and skip all other steps
|
||||
if (stremthruStoreOptions.overrideUrl) {
|
||||
const stremthruStore = new StremThruStore(
|
||||
null,
|
||||
stremthruStoreOptions.overrideUrl as string,
|
||||
stremthruStoreOptions.overrideName,
|
||||
addonId,
|
||||
config,
|
||||
indexerTimeout
|
||||
);
|
||||
return await stremthruStore.getParsedStreams(streamRequest);
|
||||
}
|
||||
|
||||
// find all usable and enabled services
|
||||
const usableServices = config.services.filter(
|
||||
(service) => supportedServices.includes(service.id) && service.enabled
|
||||
);
|
||||
|
||||
// if no usable services found, raise error
|
||||
if (usableServices.length < 1) {
|
||||
throw new Error('No supported service(s) enabled');
|
||||
}
|
||||
|
||||
// otherwise, depending on the configuration, create multiple instances of StremThru Store or use a single instance with the prioritised service
|
||||
|
||||
if (
|
||||
stremthruStoreOptions.prioritiseDebrid &&
|
||||
!supportedServices.includes(stremthruStoreOptions.prioritiseDebrid)
|
||||
) {
|
||||
throw new Error('Invalid debrid service');
|
||||
}
|
||||
|
||||
const formServiceCredentialsString = (
|
||||
service: string,
|
||||
credentials: { [key: string]: string }
|
||||
) => {
|
||||
if (service === 'pikpak' || service === 'offcloud') {
|
||||
if (!credentials.email || !credentials.password) {
|
||||
throw new Error(
|
||||
`Credentials for ${service} are not valid. Please check your configuration. Email and password are required.`
|
||||
);
|
||||
}
|
||||
return `${credentials.email}:${credentials.password}`;
|
||||
}
|
||||
if (!credentials.apiKey) {
|
||||
throw new Error(`API Key is missing for ${service}`);
|
||||
}
|
||||
return credentials.apiKey;
|
||||
};
|
||||
|
||||
if (stremthruStoreOptions.prioritiseDebrid) {
|
||||
const debridService = usableServices.find(
|
||||
(service) => service.id === stremthruStoreOptions.prioritiseDebrid
|
||||
);
|
||||
if (!debridService) {
|
||||
throw new Error(
|
||||
'Debrid service not found for ' + stremthruStoreOptions.prioritiseDebrid
|
||||
);
|
||||
}
|
||||
const storeToken = formServiceCredentialsString(
|
||||
debridService.id,
|
||||
debridService.credentials
|
||||
);
|
||||
|
||||
const stremthruStore = new StremThruStore(
|
||||
getConfigString(stremthruStoreOptions.prioritiseDebrid, storeToken),
|
||||
null,
|
||||
stremthruStoreOptions.overrideName,
|
||||
addonId,
|
||||
config,
|
||||
indexerTimeout
|
||||
);
|
||||
|
||||
return await stremthruStore.getParsedStreams(streamRequest);
|
||||
}
|
||||
|
||||
// if no prioritised service is provided, create a stremthru instance for each service
|
||||
const servicesToUse = usableServices.filter((service) => service.enabled);
|
||||
if (servicesToUse.length < 1) {
|
||||
throw new Error('No supported service(s) enabled');
|
||||
}
|
||||
const errorMessages: string[] = [];
|
||||
const streamPromises = servicesToUse.map(async (service) => {
|
||||
logger.info(`Getting StremThru Store streams for ${service.id}`, {
|
||||
func: 'stremthru-store',
|
||||
});
|
||||
const stremthruStore = new StremThruStore(
|
||||
getConfigString(
|
||||
service.id,
|
||||
formServiceCredentialsString(service.id, service.credentials)
|
||||
),
|
||||
null,
|
||||
stremthruStoreOptions.overrideName,
|
||||
addonId,
|
||||
config,
|
||||
indexerTimeout
|
||||
);
|
||||
return stremthruStore.getParsedStreams(streamRequest);
|
||||
});
|
||||
|
||||
const results = await Promise.allSettled(streamPromises);
|
||||
results.forEach((result) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
const streams = result.value;
|
||||
parsedStreams.push(...streams.addonStreams);
|
||||
errorMessages.push(...streams.addonErrors);
|
||||
} else {
|
||||
errorMessages.push(result.reason.message);
|
||||
}
|
||||
});
|
||||
|
||||
return { addonStreams: parsedStreams, addonErrors: errorMessages };
|
||||
}
|
||||
|
||||
function getConfigString(storeName: string, storeToken: string) {
|
||||
return Buffer.from(
|
||||
JSON.stringify({
|
||||
store_name: storeName,
|
||||
store_token: storeToken,
|
||||
})
|
||||
).toString('base64');
|
||||
}
|
||||
Reference in New Issue
Block a user