refactor(routes): use router via controllers
This commit is contained in:
parent
ce6aca5c77
commit
15a53de504
6 changed files with 172 additions and 118 deletions
|
|
@ -9,6 +9,9 @@ const config: Config = {
|
|||
'!<rootDir>/src/landingTemplate.ts',
|
||||
],
|
||||
coverageDirectory: '<rootDir>/coverage',
|
||||
coveragePathIgnorePatterns: [
|
||||
'/src/controller/',
|
||||
],
|
||||
coverageProvider: 'babel',
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
|
|
|
|||
29
src/controller/ConfigureController.ts
Normal file
29
src/controller/ConfigureController.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Request, Response, Router } from 'express';
|
||||
import { landingTemplate } from '../landingTemplate';
|
||||
import { buildManifest } from '../utils';
|
||||
import { Handler } from '../handler';
|
||||
import { Config } from '../types';
|
||||
|
||||
export class ConfigureController {
|
||||
public readonly router: Router;
|
||||
|
||||
private readonly handlers: Handler[];
|
||||
|
||||
constructor(handlers: Handler[]) {
|
||||
this.router = Router();
|
||||
|
||||
this.handlers = handlers;
|
||||
|
||||
this.router.get('/configure', this.getConfigure.bind(this));
|
||||
this.router.get('/:config/configure', this.getConfigure.bind(this));
|
||||
}
|
||||
|
||||
private readonly getConfigure = (req: Request, res: Response) => {
|
||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
||||
|
||||
const manifest = buildManifest(this.handlers, config);
|
||||
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.send(landingTemplate(manifest));
|
||||
};
|
||||
}
|
||||
28
src/controller/ManifestController.ts
Normal file
28
src/controller/ManifestController.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { Request, Response, Router } from 'express';
|
||||
import { buildManifest } from '../utils';
|
||||
import { Handler } from '../handler';
|
||||
import { Config } from '../types';
|
||||
|
||||
export class ManifestController {
|
||||
public readonly router: Router;
|
||||
|
||||
private readonly handlers: Handler[];
|
||||
|
||||
constructor(handlers: Handler[]) {
|
||||
this.router = Router();
|
||||
|
||||
this.handlers = handlers;
|
||||
|
||||
this.router.get('/manifest.json', this.getManifest.bind(this));
|
||||
this.router.get('/:config/manifest.json', this.getManifest.bind(this));
|
||||
}
|
||||
|
||||
private readonly getManifest = (req: Request, res: Response) => {
|
||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
||||
|
||||
const manifest = buildManifest(this.handlers, config);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(manifest);
|
||||
};
|
||||
}
|
||||
100
src/controller/StreamController.ts
Normal file
100
src/controller/StreamController.ts
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { Request, Response, Router } from 'express';
|
||||
import { Handler } from '../handler';
|
||||
import { Config, UrlResult } from '../types';
|
||||
import bytes from 'bytes';
|
||||
import { flag } from 'country-emoji';
|
||||
import winston from 'winston';
|
||||
|
||||
export class StreamController {
|
||||
public readonly router: Router;
|
||||
|
||||
private readonly logger: winston.Logger;
|
||||
private readonly handlers: Handler[];
|
||||
|
||||
constructor(logger: winston.Logger, handlers: Handler[]) {
|
||||
this.router = Router();
|
||||
|
||||
this.logger = logger;
|
||||
this.handlers = handlers;
|
||||
|
||||
this.router.get('/:config/stream/:type/:id.json', this.getStream.bind(this));
|
||||
}
|
||||
|
||||
private readonly getStream = async (req: Request, res: Response) => {
|
||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
||||
const type: string = req.params['type'] || '';
|
||||
const id: string = req.params['id'] || '';
|
||||
|
||||
this.logger.info(`Search stream for type "${type}" and id "${id}"`);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
const selectedHandlers = this.handlers.filter(handler => handler.id in config);
|
||||
if (selectedHandlers.length === 0) {
|
||||
this.logger.info('No handlers configured, bail out');
|
||||
|
||||
res.send(JSON.stringify({
|
||||
streams: [{
|
||||
name: 'WebStreamr',
|
||||
title: '⚠️ No handlers found. Please re-configure the plugin.',
|
||||
ytId: 'E4WlUXrJgy4',
|
||||
}],
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const urlResults: UrlResult[] = [];
|
||||
const handlerPromises = selectedHandlers.map(async (handler) => {
|
||||
if (!handler.contentTypes.includes(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const handlerUrlResults = await handler.handle({ ip: req.ip as string }, id);
|
||||
this.logger.info(`${handler.id} returned ${handlerUrlResults.length} urls`);
|
||||
|
||||
urlResults.push(...handlerUrlResults);
|
||||
} catch (err) {
|
||||
this.logger.error(`${handler.id} error: ` + err);
|
||||
}
|
||||
});
|
||||
await Promise.all(handlerPromises);
|
||||
|
||||
urlResults.sort((a, b) => {
|
||||
const heightComparison = (b.height ?? 0) - (a.height ?? 0);
|
||||
if (heightComparison !== 0) {
|
||||
return heightComparison;
|
||||
}
|
||||
|
||||
return (b.bytes ?? 0) - (a.bytes ?? 0);
|
||||
});
|
||||
|
||||
this.logger.info(`Return ${urlResults.length} streams`);
|
||||
|
||||
const streams = urlResults.map((urlResult) => {
|
||||
let name = 'WebStreamr';
|
||||
if (urlResult.height) {
|
||||
name += ` ${urlResult.height}p`;
|
||||
}
|
||||
|
||||
let title = urlResult.label;
|
||||
if (urlResult.bytes) {
|
||||
title += ` | 💾 ${bytes.format(urlResult.bytes, { unitSeparator: ' ' })}`;
|
||||
}
|
||||
if (urlResult.countryCode) {
|
||||
title += ` | ${flag(urlResult.countryCode)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
url: urlResult.url.toString(),
|
||||
name,
|
||||
title,
|
||||
behaviourHints: {
|
||||
group: `webstreamr-${urlResult.sourceId}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
res.send(JSON.stringify({ streams }));
|
||||
};
|
||||
}
|
||||
3
src/controller/index.ts
Normal file
3
src/controller/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './ConfigureController';
|
||||
export * from './ManifestController';
|
||||
export * from './StreamController';
|
||||
127
src/index.ts
127
src/index.ts
|
|
@ -1,18 +1,12 @@
|
|||
import express, { NextFunction, Request, Response } from 'express';
|
||||
import makeFetchHappen from 'make-fetch-happen';
|
||||
import { flag } from 'country-emoji';
|
||||
import winston from 'winston';
|
||||
import { landingTemplate } from './landingTemplate';
|
||||
import { FrenchCloud, Handler, KinoKiste, MeineCloud, MostraGuarda, VerHdLink } from './handler';
|
||||
import { Dropload, EmbedExtractors, SuperVideo } from './embed-extractor';
|
||||
import { buildManifest, Fetcher } from './utils';
|
||||
import { Config, UrlResult } from './types';
|
||||
import { ConfigureController, ManifestController, StreamController } from './controller';
|
||||
import { Fetcher } from './utils';
|
||||
import fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import bytes from 'bytes';
|
||||
|
||||
const addon = express();
|
||||
addon.set('trust proxy', true);
|
||||
|
||||
const logger = winston.createLogger({
|
||||
transports: [
|
||||
|
|
@ -47,6 +41,9 @@ const handlers: Handler[] = [
|
|||
new VerHdLink(fetcher, embedExtractors),
|
||||
];
|
||||
|
||||
const addon = express();
|
||||
addon.set('trust proxy', true);
|
||||
|
||||
addon.use((_req: Request, res: Response, next: NextFunction) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Headers', '*');
|
||||
|
|
@ -54,120 +51,14 @@ addon.use((_req: Request, res: Response, next: NextFunction) => {
|
|||
next();
|
||||
});
|
||||
|
||||
addon.use('/', (new ConfigureController(handlers)).router);
|
||||
addon.use('/', (new ManifestController(handlers)).router);
|
||||
addon.use('/', (new StreamController(logger, handlers)).router);
|
||||
|
||||
addon.get('/', (_req: Request, res: Response) => {
|
||||
res.redirect('/configure');
|
||||
});
|
||||
|
||||
addon.get('/configure', (_req: Request, res: Response) => {
|
||||
const manifest = buildManifest(handlers, {});
|
||||
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.send(landingTemplate(manifest));
|
||||
});
|
||||
|
||||
addon.get('/:config/configure', (req: Request, res: Response) => {
|
||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
||||
|
||||
const manifest = buildManifest(handlers, config);
|
||||
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.send(landingTemplate(manifest));
|
||||
});
|
||||
|
||||
addon.get('/manifest.json', (_req: Request, res: Response) => {
|
||||
const manifest = buildManifest(handlers, {});
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(manifest);
|
||||
});
|
||||
|
||||
addon.get('/:config/manifest.json', (req: Request, res: Response) => {
|
||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
||||
|
||||
const manifest = buildManifest(handlers, config);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(manifest);
|
||||
});
|
||||
|
||||
addon.get('/:config/stream/:type/:id.json', async function (req: Request, res: Response) {
|
||||
const config: Config = JSON.parse(req.params['config'] || '{}');
|
||||
const type: string = req.params['type'] || '';
|
||||
const id: string = req.params['id'] || '';
|
||||
|
||||
logger.info(`Search stream for type "${type}" and id "${id}"`);
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
|
||||
const selectedHandlers = handlers.filter(handler => handler.id in config);
|
||||
if (selectedHandlers.length === 0) {
|
||||
logger.info('No handlers configured, bail out');
|
||||
|
||||
res.send(JSON.stringify({
|
||||
streams: [{
|
||||
name: 'WebStreamr',
|
||||
title: '⚠️ No handlers found. Please re-configure the plugin.',
|
||||
ytId: 'E4WlUXrJgy4',
|
||||
}],
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const urlResults: UrlResult[] = [];
|
||||
const handlerPromises = selectedHandlers.map(async (handler) => {
|
||||
if (!handler.contentTypes.includes(type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const handlerUrlResults = await handler.handle({ ip: req.ip as string }, id);
|
||||
logger.info(`${handler.id} returned ${handlerUrlResults.length} urls`);
|
||||
|
||||
urlResults.push(...handlerUrlResults);
|
||||
} catch (err) {
|
||||
logger.error(`${handler.id} error: ` + err);
|
||||
}
|
||||
});
|
||||
await Promise.all(handlerPromises);
|
||||
|
||||
urlResults.sort((a, b) => {
|
||||
const heightComparison = (b.height ?? 0) - (a.height ?? 0);
|
||||
if (heightComparison !== 0) {
|
||||
return heightComparison;
|
||||
}
|
||||
|
||||
return (b.bytes ?? 0) - (a.bytes ?? 0);
|
||||
});
|
||||
|
||||
logger.info(`Return ${urlResults.length} streams`);
|
||||
|
||||
const streams = urlResults.map((urlResult) => {
|
||||
let name = 'WebStreamr';
|
||||
if (urlResult.height) {
|
||||
name += ` ${urlResult.height}p`;
|
||||
}
|
||||
|
||||
let title = urlResult.label;
|
||||
if (urlResult.bytes) {
|
||||
title += ` | 💾 ${bytes.format(urlResult.bytes, { unitSeparator: ' ' })}`;
|
||||
}
|
||||
if (urlResult.countryCode) {
|
||||
title += ` | ${flag(urlResult.countryCode)}`;
|
||||
}
|
||||
|
||||
return {
|
||||
url: urlResult.url.toString(),
|
||||
name,
|
||||
title,
|
||||
behaviourHints: {
|
||||
group: `webstreamr-${urlResult.sourceId}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
res.send(JSON.stringify({ streams }));
|
||||
});
|
||||
|
||||
const port = parseInt(process.env['PORT'] || '51546');
|
||||
addon.listen(port, () => {
|
||||
logger.info(`Add-on Repository URL: http://127.0.0.1:${port}/manifest.json`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue