mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat: add torrent galaxy builtin addon
This commit is contained in:
+21
-1
@@ -103,6 +103,26 @@ BUILTIN_PROWLARR_SEARCH_TIMEOUT=
|
||||
BUILTIN_PROWLARR_SEARCH_CACHE_TTL=604800
|
||||
BUILTIN_PROWLARR_INDEXERS_CACHE_TTL=1209600
|
||||
|
||||
|
||||
# --- Knaben ----
|
||||
# BUILTIN_DEFAULT_KNABEN_TIMEOUT=
|
||||
# BUILTIN_KNABEN_SEARCH_TIMEOUT=30000,
|
||||
# BUILTIN_KNABEN_SEARCH_CACHE_TTL=604800
|
||||
|
||||
|
||||
# --- Torrent Galaxy ---
|
||||
# BUILTIN_TORRENT_GALAXY_URL=https://torrentgalaxy.space
|
||||
# Default timeout of the addon in the marketplace
|
||||
# BUILTIN_DEFAULT_TORRENT_GALAXY_TIMEOUT=
|
||||
# The timeout for requests to TGx
|
||||
# BUILTIN_TORRENT_GALAXY_SEARCH_TIMEOUT=30000
|
||||
# How long each search is cached for.
|
||||
# BUILTIN_TORRENT_GALAXY_SEARCH_CACHE_TTL=604800
|
||||
# The maximum number of pages to fetch.
|
||||
# BUILTIN_TORRENT_GALAXY_PAGE_LIMIT=5
|
||||
|
||||
|
||||
|
||||
# ---- Jackett
|
||||
# Optionally provide a default Jackett URL and API Key here. Users cannot see the values set here.
|
||||
# BUILTIN_JACKETT_URL=
|
||||
@@ -111,7 +131,7 @@ BUILTIN_PROWLARR_INDEXERS_CACHE_TTL=1209600
|
||||
# ---- NZBHydra2 ------
|
||||
# Optionally provide a default NZBHydra URL and API Key here. Users cannot see the values set here.
|
||||
# BUILTIN_NZBHYDRA_URL=
|
||||
# BUILDIN_NZBHYDRA_API_KEY=
|
||||
# BUILTIN_NZBHYDRA_API_KEY=
|
||||
|
||||
# ---- Stremio GDrive -----
|
||||
# Client ID and Secret generated following this guide: https://guides.viren070.me/stremio/addons/stremio-gdrive
|
||||
|
||||
@@ -4,3 +4,4 @@ export * from './torznab/index.js';
|
||||
export * from './newznab/index.js';
|
||||
export * from './prowlarr/index.js';
|
||||
export * from './knaben/index.js';
|
||||
export * from './torrent-galaxy/index.js';
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import {
|
||||
BaseDebridAddon,
|
||||
BaseDebridConfigSchema,
|
||||
SearchMetadata,
|
||||
} from '../base/debrid.js';
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
createLogger,
|
||||
getTimeTakenSincePoint,
|
||||
ParsedId,
|
||||
} from '../../utils/index.js';
|
||||
import TorrentGalaxyAPI, { TorrentGalaxyCategory } from './api.js';
|
||||
import { NZB, UnprocessedTorrent } from '../../debrid/utils.js';
|
||||
import {
|
||||
extractTrackersFromMagnet,
|
||||
validateInfoHash,
|
||||
} from '../utils/debrid.js';
|
||||
import { Env } from '../../utils/env.js';
|
||||
|
||||
const logger = createLogger('torrent-galaxy');
|
||||
|
||||
export const TorrentGalaxyAddonConfigSchema = BaseDebridConfigSchema;
|
||||
|
||||
export type TorrentGalaxyAddonConfig = z.infer<
|
||||
typeof TorrentGalaxyAddonConfigSchema
|
||||
>;
|
||||
|
||||
const WHITELISTED_CATEGORIES = [
|
||||
TorrentGalaxyCategory.Anime,
|
||||
TorrentGalaxyCategory.TV,
|
||||
TorrentGalaxyCategory.Movies,
|
||||
];
|
||||
|
||||
export class TorrentGalaxyAddon extends BaseDebridAddon<TorrentGalaxyAddonConfig> {
|
||||
readonly id = 'torrent-galaxy';
|
||||
readonly name = 'Torrent Galaxy';
|
||||
readonly version = '1.0.0';
|
||||
readonly logger = logger;
|
||||
readonly api: TorrentGalaxyAPI;
|
||||
|
||||
constructor(userData: TorrentGalaxyAddonConfig, clientIp?: string) {
|
||||
super(userData, TorrentGalaxyAddonConfigSchema, clientIp);
|
||||
this.api = new TorrentGalaxyAPI();
|
||||
}
|
||||
|
||||
protected async _searchNzbs(
|
||||
parsedId: ParsedId,
|
||||
metadata: SearchMetadata
|
||||
): Promise<NZB[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
protected async _searchTorrents(
|
||||
parsedId: ParsedId,
|
||||
metadata: SearchMetadata
|
||||
): Promise<UnprocessedTorrent[]> {
|
||||
if (!metadata.primaryTitle) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const queries = this.buildQueries(parsedId, metadata);
|
||||
if (metadata.imdbId) {
|
||||
queries.push(metadata.imdbId);
|
||||
}
|
||||
|
||||
if (queries.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
logger.info(`Performing torrent galaxy search`, { queries });
|
||||
|
||||
const searchPromises = queries.map(async (q) => {
|
||||
const start = Date.now();
|
||||
|
||||
// First fetch to get total and page size
|
||||
logger.debug(`Fetching first page for query "${q}"`);
|
||||
const firstPageResponse = await this.api.search({
|
||||
query: q,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
const { total, pageSize } = firstPageResponse;
|
||||
let allResults = [...firstPageResponse.results];
|
||||
|
||||
// Calculate required pages
|
||||
const totalPages = Math.min(
|
||||
Math.ceil(total / pageSize),
|
||||
Env.BUILTIN_TORRENT_GALAXY_PAGE_LIMIT
|
||||
);
|
||||
|
||||
if (totalPages <= 1) {
|
||||
logger.info(
|
||||
`Torrent Galaxy search for ${q} took ${getTimeTakenSincePoint(start)}`,
|
||||
{
|
||||
results: allResults.length,
|
||||
pages: 1,
|
||||
}
|
||||
);
|
||||
return allResults;
|
||||
}
|
||||
|
||||
// Create array of page numbers to fetch (skip page 1 as we already have it)
|
||||
const pageNumbers = Array.from(
|
||||
{ length: totalPages - 1 },
|
||||
(_, i) => i + 2
|
||||
);
|
||||
|
||||
logger.debug(
|
||||
`Fetching ${pageNumbers.length} additional pages in parallel for query "${q}"`
|
||||
);
|
||||
|
||||
// Fetch all remaining pages in parallel
|
||||
const pagePromises = pageNumbers.map(async (pageNum) => {
|
||||
const { results } = await this.api.search({
|
||||
query: q,
|
||||
page: pageNum,
|
||||
});
|
||||
logger.debug(`Fetched page ${pageNum} for query "${q}"`, {
|
||||
newResults: results.length,
|
||||
});
|
||||
return results;
|
||||
});
|
||||
|
||||
const remainingResults = await Promise.all(pagePromises);
|
||||
allResults.push(...remainingResults.flat());
|
||||
|
||||
logger.info(
|
||||
`Torrent Galaxy search for ${q} took ${getTimeTakenSincePoint(start)}`,
|
||||
{
|
||||
results: allResults.length,
|
||||
pages: totalPages,
|
||||
}
|
||||
);
|
||||
return allResults;
|
||||
});
|
||||
|
||||
const allResults = await Promise.all(searchPromises);
|
||||
const results = allResults
|
||||
.flat()
|
||||
.filter(
|
||||
(result) =>
|
||||
WHITELISTED_CATEGORIES.some(
|
||||
(category) => result.category === category
|
||||
) ||
|
||||
(metadata.imdbId && result.imdbId
|
||||
? result.imdbId === metadata.imdbId
|
||||
: true)
|
||||
);
|
||||
|
||||
const seenTorrents = new Set<string>();
|
||||
const torrents: UnprocessedTorrent[] = [];
|
||||
for (const result of results) {
|
||||
const hash = validateInfoHash(result.hash);
|
||||
if (!hash) {
|
||||
logger.warn(
|
||||
`TorrentGalaxy search hit has no hash: ${JSON.stringify(result)}`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
const downloadUrl = `https://itorrents.org/${hash.toUpperCase()}.torrent?title=${result.name}`;
|
||||
if (seenTorrents.has(hash)) {
|
||||
continue;
|
||||
}
|
||||
seenTorrents.add(hash);
|
||||
|
||||
torrents.push({
|
||||
hash,
|
||||
downloadUrl,
|
||||
sources: [],
|
||||
indexer: `TGx | ${result.user}`,
|
||||
seeders: result.seeders,
|
||||
title: result.name,
|
||||
size: result.size,
|
||||
type: 'torrent',
|
||||
});
|
||||
}
|
||||
return torrents;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
import { Cache } from '../../utils/cache.js';
|
||||
import { Env } from '../../utils/env.js';
|
||||
import { formatZodError, makeRequest } from '../../utils/index.js';
|
||||
import { createLogger } from '../../utils/index.js';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
const logger = createLogger('torrent-galaxy');
|
||||
|
||||
enum TorrentGalaxyCategory {
|
||||
Movies = 'Movies',
|
||||
TV = 'TV',
|
||||
Anime = 'Anime',
|
||||
}
|
||||
|
||||
const TorrentGalaxySearchResultSchema = z
|
||||
.looseObject({
|
||||
pk: z.string(), // post key
|
||||
n: z.string(), // name
|
||||
a: z.number(), // unix timestamp i.e. age
|
||||
c: z.string(), // category e.g. Movies
|
||||
s: z.number(), // size
|
||||
t: z.url().nullable(), // poster URL
|
||||
u: z.string(), // user
|
||||
se: z.number(), // seeders
|
||||
le: z.number(), // leechers
|
||||
i: z.string().nullable(), // imdb id,
|
||||
h: z.string().transform((h) => h.toLowerCase()), // hash
|
||||
tg: z.array(z.string()), // tags.
|
||||
})
|
||||
.transform((data) => ({
|
||||
postKey: data.pk,
|
||||
name: data.n,
|
||||
age: data.a,
|
||||
category: data.c,
|
||||
size: data.s,
|
||||
posterUrl: data.t,
|
||||
user: data.u,
|
||||
seeders: data.se,
|
||||
leechers: data.le,
|
||||
imdbId: data.i,
|
||||
hash: data.h,
|
||||
tags: data.tg,
|
||||
}));
|
||||
|
||||
type TorrentGalaxySearchResult = z.infer<
|
||||
typeof TorrentGalaxySearchResultSchema
|
||||
>;
|
||||
|
||||
const TorrentGalaxySearchResponse = z
|
||||
.object({
|
||||
page_size: z.number(),
|
||||
count: z.number(),
|
||||
total: z.number(),
|
||||
results: z.array(TorrentGalaxySearchResultSchema),
|
||||
})
|
||||
.transform((data) => ({
|
||||
pageSize: data.page_size,
|
||||
count: data.count,
|
||||
total: data.total,
|
||||
results: data.results,
|
||||
}));
|
||||
|
||||
type TorrentGalaxySearchResponse = z.infer<typeof TorrentGalaxySearchResponse>;
|
||||
|
||||
const TorrentGalaxySearchOptions = z.object({
|
||||
query: z.string(),
|
||||
page: z.number().default(1),
|
||||
});
|
||||
|
||||
type TorrentGalaxySearchOptions = z.infer<typeof TorrentGalaxySearchOptions>;
|
||||
|
||||
const API_BASE_URL = Env.BUILTIN_TORRENT_GALAXY_URL;
|
||||
|
||||
class TorrentGalaxyAPI {
|
||||
private headers: Record<string, string>;
|
||||
|
||||
private readonly searchCache = Cache.getInstance<string, any>(
|
||||
'torrent-galaxy:search'
|
||||
);
|
||||
|
||||
constructor() {
|
||||
this.headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
Accept: 'application/json',
|
||||
};
|
||||
}
|
||||
|
||||
async search(
|
||||
options: TorrentGalaxySearchOptions
|
||||
): Promise<TorrentGalaxySearchResponse> {
|
||||
let queryParams = new URLSearchParams();
|
||||
if (options.page) {
|
||||
queryParams.set('page', options.page.toString());
|
||||
}
|
||||
return this.searchCache.wrap(
|
||||
() =>
|
||||
this.request<TorrentGalaxySearchResponse>(
|
||||
`/get-posts/keywords:${encodeURIComponent(options.query)}:format:json`,
|
||||
{
|
||||
schema: TorrentGalaxySearchResponse,
|
||||
timeout: Env.BUILTIN_TORRENT_GALAXY_SEARCH_TIMEOUT,
|
||||
queryParams,
|
||||
}
|
||||
),
|
||||
`${JSON.stringify(options)}`,
|
||||
Env.BUILTIN_TORRENT_GALAXY_SEARCH_CACHE_TTL
|
||||
);
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
endpoint: string,
|
||||
options: {
|
||||
schema: z.ZodSchema<T>;
|
||||
body?: unknown;
|
||||
method?: string;
|
||||
timeout?: number;
|
||||
queryParams?: URLSearchParams;
|
||||
}
|
||||
): Promise<T> {
|
||||
const { schema, body, method = 'GET' } = options;
|
||||
let path = '';
|
||||
if (endpoint) {
|
||||
path = `/${endpoint.startsWith('/') ? endpoint.slice(1) : endpoint}`;
|
||||
}
|
||||
const url = new URL(path, API_BASE_URL);
|
||||
if (options.queryParams) {
|
||||
url.search = options.queryParams.toString();
|
||||
}
|
||||
|
||||
logger.debug(`Making ${method} request to ${path}`);
|
||||
|
||||
try {
|
||||
const response = await makeRequest(url.toString(), {
|
||||
method,
|
||||
headers: this.headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
timeout: options.timeout ?? Env.MAX_TIMEOUT,
|
||||
});
|
||||
|
||||
const data = (await response.json()) as unknown;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Torrent Galaxy API error (${response.status}): ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return schema.parse(data);
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Failed to parse Torrent Galaxy API response: ${formatZodError(error as z.ZodError)}`
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Request to ${path} failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
throw error instanceof Error
|
||||
? error
|
||||
: new Error('Unknown error occurred');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { TorrentGalaxyCategory };
|
||||
export type {
|
||||
TorrentGalaxySearchOptions,
|
||||
TorrentGalaxySearchResponse,
|
||||
TorrentGalaxySearchResult,
|
||||
};
|
||||
export default TorrentGalaxyAPI;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './addon.js';
|
||||
@@ -15,5 +15,6 @@ export {
|
||||
NewznabAddon,
|
||||
ProwlarrAddon,
|
||||
KnabenAddon,
|
||||
TorrentGalaxyAddon,
|
||||
} from './builtins/index.js';
|
||||
export { PresetManager } from './presets/index.js';
|
||||
|
||||
@@ -64,7 +64,7 @@ import { NZBHydraPreset } from './nzbhydra.js';
|
||||
import { KnabenPreset } from './knaben.js';
|
||||
import { BitmagnetPreset } from './bitmagnet.js';
|
||||
import { SootioPreset } from './sootio.js';
|
||||
|
||||
import { TorrentGalaxyPreset } from './torrentGalaxy.js';
|
||||
let PRESET_LIST: string[] = [
|
||||
'custom',
|
||||
'torznab',
|
||||
@@ -78,6 +78,7 @@ let PRESET_LIST: string[] = [
|
||||
'sootio',
|
||||
'zilean',
|
||||
'knaben',
|
||||
'torrent-galaxy',
|
||||
Env.BUILTIN_BITMAGNET_URL ? 'bitmagnet' : '',
|
||||
'animetosho',
|
||||
'prowlarr',
|
||||
@@ -276,6 +277,8 @@ export class PresetManager {
|
||||
return BitmagnetPreset;
|
||||
case 'sootio':
|
||||
return SootioPreset;
|
||||
case 'torrent-galaxy':
|
||||
return TorrentGalaxyPreset;
|
||||
default:
|
||||
throw new Error(`Preset ${id} not found`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Option, UserData } from '../db/index.js';
|
||||
import { Env, constants } from '../utils/index.js';
|
||||
import { StremThruPreset } from './stremthru.js';
|
||||
import { TorznabPreset } from './torznab.js';
|
||||
|
||||
export class TorrentGalaxyPreset extends TorznabPreset {
|
||||
static override get METADATA() {
|
||||
const supportedResources = [constants.STREAM_RESOURCE];
|
||||
const options: Option[] = [
|
||||
{
|
||||
id: 'name',
|
||||
name: 'Name',
|
||||
description: 'What to call this addon',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: 'TorrentGalaxy',
|
||||
},
|
||||
{
|
||||
id: 'timeout',
|
||||
name: 'Timeout',
|
||||
description: 'The timeout for this addon',
|
||||
type: 'number',
|
||||
required: true,
|
||||
default:
|
||||
Env.BUILTIN_DEFAULT_TORRENT_GALAXY_TIMEOUT || Env.DEFAULT_TIMEOUT,
|
||||
},
|
||||
{
|
||||
id: 'services',
|
||||
name: 'Services',
|
||||
description:
|
||||
'Optionally override the services that are used. If not specified, then the services that are enabled and supported will be used.',
|
||||
type: 'multi-select',
|
||||
required: false,
|
||||
showInNoobMode: false,
|
||||
options: StremThruPreset.supportedServices.map((service) => ({
|
||||
value: service,
|
||||
label: constants.SERVICE_DETAILS[service].name,
|
||||
})),
|
||||
default: undefined,
|
||||
emptyIsUndefined: true,
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
ID: 'torrent-galaxy',
|
||||
NAME: 'TorrentGalaxy',
|
||||
LOGO: '',
|
||||
URL: `${Env.INTERNAL_URL}/builtins/torrent-galaxy`,
|
||||
TIMEOUT:
|
||||
Env.BUILTIN_DEFAULT_TORRENT_GALAXY_TIMEOUT || Env.DEFAULT_TIMEOUT,
|
||||
USER_AGENT: Env.DEFAULT_USER_AGENT,
|
||||
SUPPORTED_SERVICES: StremThruPreset.supportedServices,
|
||||
DESCRIPTION:
|
||||
'Directly search TorrentGalaxy for results with your services.',
|
||||
OPTIONS: options,
|
||||
SUPPORTED_STREAM_TYPES: [constants.DEBRID_STREAM_TYPE],
|
||||
SUPPORTED_RESOURCES: supportedResources,
|
||||
BUILTIN: true,
|
||||
};
|
||||
}
|
||||
|
||||
protected static override generateManifestUrl(
|
||||
userData: UserData,
|
||||
services: constants.ServiceId[],
|
||||
options: Record<string, any>
|
||||
): string {
|
||||
return `${Env.INTERNAL_URL}/builtins/torrent-galaxy/${this.base64EncodeJSON(
|
||||
this.getBaseConfig(userData, services)
|
||||
)}/manifest.json`;
|
||||
}
|
||||
}
|
||||
@@ -1769,6 +1769,26 @@ export const Env = cleanEnv(process.env, {
|
||||
desc: 'Builtin Knaben Search cache TTL',
|
||||
}),
|
||||
|
||||
BUILTIN_TORRENT_GALAXY_URL: url({
|
||||
default: 'https://torrentgalaxy.space',
|
||||
desc: 'Builtin Torrent Galaxy URL',
|
||||
}),
|
||||
BUILTIN_DEFAULT_TORRENT_GALAXY_TIMEOUT: num({
|
||||
default: undefined,
|
||||
desc: 'Builtin Torrent Galaxy timeout',
|
||||
}),
|
||||
BUILTIN_TORRENT_GALAXY_SEARCH_TIMEOUT: num({
|
||||
default: 30000, // 30 seconds
|
||||
desc: 'Builtin Torrent Galaxy Search timeout',
|
||||
}),
|
||||
BUILTIN_TORRENT_GALAXY_SEARCH_CACHE_TTL: num({
|
||||
default: 7 * 24 * 60 * 60, // 7 days
|
||||
desc: 'Builtin Torrent Galaxy Search cache TTL',
|
||||
}),
|
||||
BUILTIN_TORRENT_GALAXY_PAGE_LIMIT: num({
|
||||
default: 5,
|
||||
desc: 'The maximum number of pages to fetch.',
|
||||
}),
|
||||
// Rate limiting settings
|
||||
DISABLE_RATE_LIMITS: bool({
|
||||
default: false,
|
||||
|
||||
@@ -45,6 +45,8 @@ const moduleMap: { [key: string]: string } = {
|
||||
newznab: '🔍 NEWZNAB',
|
||||
'metadata-service': '🔍 METADATA',
|
||||
torrent: '👤 TORRENT',
|
||||
knaben: '🔍 KNABEN',
|
||||
'torrent-galaxy': '🌐 TGx',
|
||||
};
|
||||
|
||||
// Define colors for each log level using full names
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
newznab,
|
||||
prowlarr,
|
||||
knaben,
|
||||
torrentGalaxy,
|
||||
} from './routes/builtins/index.js';
|
||||
import {
|
||||
ipMiddleware,
|
||||
@@ -132,6 +133,7 @@ builtinsRouter.use('/torznab', torznab);
|
||||
builtinsRouter.use('/newznab', newznab);
|
||||
builtinsRouter.use('/prowlarr', prowlarr);
|
||||
builtinsRouter.use('/knaben', knaben);
|
||||
builtinsRouter.use('/torrent-galaxy', torrentGalaxy);
|
||||
app.use('/builtins', builtinsRouter);
|
||||
|
||||
app.get(
|
||||
|
||||
@@ -4,3 +4,4 @@ export { default as torznab } from './torznab.js';
|
||||
export { default as newznab } from './newznab.js';
|
||||
export { default as prowlarr } from './prowlarr.js';
|
||||
export { default as knaben } from './knaben.js';
|
||||
export { default as torrentGalaxy } from './torrent-galaxy.js';
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Router, Request, Response, NextFunction } from 'express';
|
||||
import {
|
||||
AIOStreams,
|
||||
AIOStreamResponse,
|
||||
TorrentGalaxyAddon,
|
||||
} from '@aiostreams/core';
|
||||
import { createLogger } from '@aiostreams/core';
|
||||
const router: Router = Router();
|
||||
|
||||
const logger = createLogger('server');
|
||||
|
||||
router.get(
|
||||
'/:encodedConfig/manifest.json',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { encodedConfig } = req.params;
|
||||
const config = encodedConfig
|
||||
? JSON.parse(Buffer.from(encodedConfig, 'base64').toString('utf-8'))
|
||||
: undefined;
|
||||
|
||||
try {
|
||||
const manifest = new TorrentGalaxyAddon(config, req.userIp).getManifest();
|
||||
res.json(manifest);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:encodedConfig/stream/:type/:id.json',
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { encodedConfig, type, id } = req.params;
|
||||
const config = JSON.parse(
|
||||
Buffer.from(encodedConfig, 'base64').toString('utf-8')
|
||||
);
|
||||
|
||||
try {
|
||||
const addon = new TorrentGalaxyAddon(config, req.userIp);
|
||||
const streams = await addon.getStreams(type, id);
|
||||
res.json({
|
||||
streams: streams,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user