monkastremio / packages /wrappers /src /stremthruStore.ts
brunner56's picture
implement app
0bfe2e3
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,
Settings.DEFAULT_STREMTHRU_STORE_USER_AGENT
? { 'User-Agent': Settings.DEFAULT_STREMTHRU_STORE_USER_AGENT }
: undefined
);
}
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;
if (
Settings.FORCE_STREMTHRU_STORE_HOSTNAME !== undefined ||
Settings.FORCE_STREMTHRU_STORE_PORT !== undefined ||
Settings.FORCE_STREMTHRU_STORE_PROTOCOL !== undefined
) {
// modify the URL according to settings, needed when using a local URL for requests but a public stream URL is needed.
const url = new URL(parsedResult.result.url || '');
if (Settings.FORCE_STREMTHRU_STORE_PROTOCOL !== undefined) {
url.protocol = Settings.FORCE_STREMTHRU_STORE_PROTOCOL;
}
if (Settings.FORCE_STREMTHRU_STORE_PORT !== undefined) {
url.port = Settings.FORCE_STREMTHRU_STORE_PORT.toString();
}
if (Settings.FORCE_STREMTHRU_STORE_HOSTNAME !== undefined) {
url.hostname = Settings.FORCE_STREMTHRU_STORE_HOSTNAME;
}
parsedResult.result.url = url.toString();
}
}
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');
}