chore: rename handler to source

This commit is contained in:
WebStreamr 2025-06-12 21:05:24 +00:00
parent 013fb4084f
commit b4b8581383
No known key found for this signature in database
45 changed files with 108 additions and 108 deletions

View file

@ -1,18 +1,18 @@
import { Request, Response, Router } from 'express';
import { landingTemplate } from '../landingTemplate';
import { buildManifest, getDefaultConfig } from '../utils';
import { Handler } from '../handler';
import { Source } from '../source';
import { Config } from '../types';
export class ConfigureController {
public readonly router: Router;
private readonly handlers: Handler[];
private readonly sources: Source[];
constructor(handlers: Handler[]) {
constructor(sources: Source[]) {
this.router = Router();
this.handlers = handlers;
this.sources = sources;
this.router.get('/configure', this.getConfigure.bind(this));
this.router.get('/:config/configure', this.getConfigure.bind(this));
@ -21,7 +21,7 @@ export class ConfigureController {
private readonly getConfigure = (req: Request, res: Response) => {
const config: Config = JSON.parse(req.params['config'] || JSON.stringify(getDefaultConfig()));
const manifest = buildManifest(this.handlers, config);
const manifest = buildManifest(this.sources, config);
res.setHeader('content-type', 'text/html');
res.send(landingTemplate(manifest));

View file

@ -1,17 +1,17 @@
import { Request, Response, Router } from 'express';
import { buildManifest, getDefaultConfig } from '../utils';
import { Handler } from '../handler';
import { Source } from '../source';
import { Config } from '../types';
export class ManifestController {
public readonly router: Router;
private readonly handlers: Handler[];
private readonly sources: Source[];
constructor(handlers: Handler[]) {
constructor(sources: Source[]) {
this.router = Router();
this.handlers = handlers;
this.sources = sources;
this.router.get('/manifest.json', this.getManifest.bind(this));
this.router.get('/:config/manifest.json', this.getManifest.bind(this));
@ -20,7 +20,7 @@ export class ManifestController {
private readonly getManifest = (req: Request, res: Response) => {
const config: Config = JSON.parse(req.params['config'] || JSON.stringify(getDefaultConfig()));
const manifest = buildManifest(this.handlers, config);
const manifest = buildManifest(this.sources, config);
res.setHeader('Content-Type', 'application/json');
res.send(manifest);

View file

@ -1,6 +1,6 @@
import { Request, Response, Router } from 'express';
import winston from 'winston';
import { Handler } from '../handler';
import { Source } from '../source';
import { Config, Context } from '../types';
import { envIsProd, getDefaultConfig, ImdbId, StreamResolver } from '../utils';
import { ContentType } from 'stremio-addon-sdk';
@ -9,14 +9,14 @@ export class StreamController {
public readonly router: Router;
private readonly logger: winston.Logger;
private readonly handlers: Handler[];
private readonly sources: Source[];
private readonly streamResolver: StreamResolver;
constructor(logger: winston.Logger, handlers: Handler[], streams: StreamResolver) {
constructor(logger: winston.Logger, sources: Source[], streams: StreamResolver) {
this.router = Router();
this.logger = logger;
this.handlers = handlers;
this.sources = sources;
this.streamResolver = streams;
this.router.get('/:config/stream/:type/:id.json', this.getStream.bind(this));
@ -35,9 +35,9 @@ export class StreamController {
this.logger.info(`Search stream for type "${type}" and id "${id}" for ip ${ctx.ip}`, ctx);
const handlers = this.handlers.filter(handler => handler.countryCodes.filter(countryCode => countryCode in ctx.config).length);
const sources = this.sources.filter(handler => handler.countryCodes.filter(countryCode => countryCode in ctx.config).length);
const { streams, ttl } = await this.streamResolver.resolve(ctx, handlers, type, ImdbId.fromString(id));
const { streams, ttl } = await this.streamResolver.resolve(ctx, sources, type, ImdbId.fromString(id));
if (ttl && envIsProd()) {
res.setHeader('Cache-Control', `max-age=${ttl / 1000}, public`);

View file

@ -1,7 +1,7 @@
import express, { NextFunction, Request, Response } from 'express';
import { v4 as uuidv4 } from 'uuid';
import winston from 'winston';
import { CineHDPlus, Frembed, FrenchCloud, Handler, KinoGer, MeineCloud, MostraGuarda, Soaper, StreamKiste, VerHdLink, VidSrc } from './handler';
import { CineHDPlus, Frembed, FrenchCloud, Source, KinoGer, MeineCloud, MostraGuarda, Soaper, StreamKiste, VerHdLink, VidSrc } from './source';
import { createExtractors, ExtractorRegistry } from './extractor';
import { ConfigureController, ManifestController, StreamController } from './controller';
import { envGet, envIsProd, Fetcher, StreamResolver, tmdbFetch, TmdbId } from './utils';
@ -19,7 +19,7 @@ const logger = winston.createLogger({
const fetcher = new Fetcher(logger);
const handlers: Handler[] = [
const sources: Source[] = [
// EN
new Soaper(fetcher),
new VidSrc(fetcher),
@ -54,12 +54,12 @@ addon.use((_req: Request, res: Response, next: NextFunction) => {
next();
});
addon.use('/', (new ConfigureController(handlers)).router);
addon.use('/', (new ManifestController(handlers)).router);
addon.use('/', (new ConfigureController(sources)).router);
addon.use('/', (new ManifestController(sources)).router);
const extractorRegistry = new ExtractorRegistry(logger, createExtractors(fetcher));
const streamResolver = new StreamResolver(logger, extractorRegistry);
addon.use('/', (new StreamController(logger, handlers, streamResolver)).router);
addon.use('/', (new StreamController(logger, sources, streamResolver)).router);
addon.get('/', (_req: Request, res: Response) => {
res.redirect('/configure');
@ -86,7 +86,7 @@ const cacheWarmup = async () => {
}
});
for (const id of movieIds) {
await streamResolver.resolve(ctx, handlers, 'movie', new TmdbId(id, undefined, undefined));
await streamResolver.resolve(ctx, sources, 'movie', new TmdbId(id, undefined, undefined));
}
logger.info(`warmed up cache with ${movieIds.length} movies`, ctx);
@ -100,7 +100,7 @@ const cacheWarmup = async () => {
}
});
for (const id of tvShowIds) {
await streamResolver.resolve(ctx, handlers, 'series', new TmdbId(id, 1, 1));
await streamResolver.resolve(ctx, sources, 'series', new TmdbId(id, 1, 1));
}
logger.info(`warmed up cache with ${tvShowIds.length} tv shows`, ctx);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id, ImdbId } from '../utils';
import { Context, CountryCode } from '../types';
export class CineHDPlus implements Handler {
export class CineHDPlus implements Source {
readonly id = 'cinehdplus';
readonly label = 'CineHDPlus';
@ -19,7 +19,7 @@ export class CineHDPlus implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const seriesPageUrl = await this.fetchSeriesPageUrl(ctx, imdbId);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id, ImdbId } from '../utils';
import { Context, CountryCode } from '../types';
export class Eurostreaming implements Handler {
export class Eurostreaming implements Source {
readonly id = 'eurostreaming';
readonly label = 'Eurostreaming';
@ -19,7 +19,7 @@ export class Eurostreaming implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const seriesPageUrl = await this.fetchSeriesPageUrl(ctx, imdbId);

View file

@ -1,9 +1,9 @@
import { ContentType } from 'stremio-addon-sdk';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getTmdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class Frembed implements Handler {
export class Frembed implements Source {
readonly id = 'frembed';
readonly label = 'Frembed';
@ -18,7 +18,7 @@ export class Frembed implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const tmdbId = await getTmdbId(ctx, this.fetcher, id);
const apiUrl = new URL(`https://frembed.space/api/series?id=${tmdbId.id}&sa=${tmdbId.season}&epi=${tmdbId.episode}&idType=tmdb`);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class FrenchCloud implements Handler {
export class FrenchCloud implements Source {
readonly id = 'frenchcloud';
readonly label = 'FrenchCloud';
@ -19,7 +19,7 @@ export class FrenchCloud implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const pageUrl = new URL(`https://frenchcloud.cam/movie/${imdbId.id}`);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getTmdbId, getTmdbMovieDetails, getTmdbTvDetails, Id, TmdbId } from '../utils';
import { Context, CountryCode } from '../types';
export class KinoGer implements Handler {
export class KinoGer implements Source {
readonly id = 'kinoger';
readonly label = 'KinoGer';
@ -21,7 +21,7 @@ export class KinoGer implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const tmdbId = await getTmdbId(ctx, this.fetcher, id);
const [keyword, year] = await this.getKeywordAndYear(ctx, tmdbId);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class MeineCloud implements Handler {
export class MeineCloud implements Source {
readonly id = 'meinecloud';
readonly label = 'MeineCloud';
@ -19,7 +19,7 @@ export class MeineCloud implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const pageUrl = new URL(`https://meinecloud.click/movie/${imdbId.id}`);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class MostraGuarda implements Handler {
export class MostraGuarda implements Source {
readonly id = 'mostraguarda';
readonly label = 'MostraGuarda';
@ -19,7 +19,7 @@ export class MostraGuarda implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const pageUrl = new URL(`https://mostraguarda.stream/movie/${imdbId.id}`);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getTmdbId, getTmdbMovieDetails, getTmdbTvDetails, Id, TmdbId } from '../utils';
import { Context, CountryCode } from '../types';
export class Soaper implements Handler {
export class Soaper implements Source {
readonly id = 'soaper';
readonly label = 'Soaper';
@ -21,7 +21,7 @@ export class Soaper implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const tmdbId = await getTmdbId(ctx, this.fetcher, id);
const [keyword, year, hrefPrefix] = await this.getKeywordYearAndHrefPrefix(ctx, tmdbId);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { ImdbId, Fetcher, getImdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class StreamKiste implements Handler {
export class StreamKiste implements Source {
readonly id = 'streamkiste';
readonly label = 'StreamKiste';
@ -19,7 +19,7 @@ export class StreamKiste implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const seriesPageUrl = await this.fetchSeriesPageUrl(ctx, imdbId);

View file

@ -1,10 +1,10 @@
import { ContentType } from 'stremio-addon-sdk';
import * as cheerio from 'cheerio';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class VerHdLink implements Handler {
export class VerHdLink implements Source {
readonly id = 'verhdlink';
readonly label = 'VerHdLink';
@ -19,7 +19,7 @@ export class VerHdLink implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const pageUrl = new URL(`https://verhdlink.cam/movie/${imdbId.id}`);

View file

@ -1,9 +1,9 @@
import { ContentType } from 'stremio-addon-sdk';
import { Handler, HandleResult } from './types';
import { Source, SourceResult } from './types';
import { Fetcher, getImdbId, Id } from '../utils';
import { Context, CountryCode } from '../types';
export class VidSrc implements Handler {
export class VidSrc implements Source {
readonly id = 'vidsrc';
readonly label = 'VidSrc';
@ -20,7 +20,7 @@ export class VidSrc implements Handler {
this.fetcher = fetcher;
}
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<HandleResult[]> => {
readonly handle = async (ctx: Context, _type: string, id: Id): Promise<SourceResult[]> => {
const imdbId = await getImdbId(ctx, this.fetcher, id);
const url = imdbId.season

View file

@ -2,14 +2,14 @@ import { ContentType } from 'stremio-addon-sdk';
import { Context, CountryCode } from '../types';
import { Id } from '../utils';
export interface HandleResult {
export interface SourceResult {
countryCode: CountryCode;
referer?: URL;
title?: string;
url: URL;
}
export interface Handler {
export interface Source {
readonly id: string;
readonly label: string;
@ -18,5 +18,5 @@ export interface Handler {
readonly countryCodes: CountryCode[];
readonly handle: (ctx: Context, type: ContentType, id: Id) => Promise<(HandleResult[])>;
readonly handle: (ctx: Context, type: ContentType, id: Id) => Promise<(SourceResult[])>;
}

View file

@ -2,7 +2,7 @@ import { ContentType } from 'stremio-addon-sdk';
import winston from 'winston';
import { createExtractors, Extractor, ExtractorRegistry } from '../extractor';
import { StreamResolver } from './StreamResolver';
import { Handler, HandleResult, MeineCloud, MostraGuarda } from '../handler';
import { Source, SourceResult, MeineCloud, MostraGuarda } from '../source';
import { Fetcher } from './Fetcher';
import { BlockedReason, Context, CountryCode, TIMEOUT, UrlResult } from '../types';
import { BlockedError, HttpError, NotFoundError, QueueIsFullError } from '../error';
@ -19,7 +19,7 @@ const meineCloud = new MeineCloud(fetcher);
const mostraGuarda = new MostraGuarda(fetcher);
describe('resolve', () => {
test('returns info as stream if no handlers were configured', async () => {
test('returns info as stream if no sources were configured', async () => {
const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher)));
const streams = await streamResolver.resolve(ctx, [], 'movie', new ImdbId('tt123456789', undefined, undefined));
@ -27,7 +27,7 @@ describe('resolve', () => {
expect(streams).toMatchSnapshot();
});
test('returns handler errors as stream', async () => {
test('returns source errors as stream', async () => {
const fetcherSpy = jest.spyOn(fetcher, 'text').mockRejectedValue('ups, an error occurred.');
const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher)));
@ -38,7 +38,7 @@ describe('resolve', () => {
fetcherSpy.mockRestore();
});
test('returns empty array if no handler found anything', async () => {
test('returns empty array if no source found anything', async () => {
const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher)));
const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'movie', new ImdbId('tt12345678', undefined, undefined));
@ -46,7 +46,7 @@ describe('resolve', () => {
expect(streams).toMatchSnapshot();
});
test('returns empty array if no handler supported the type', async () => {
test('returns empty array if no source supported the type', async () => {
const streamResolver = new StreamResolver(logger, new ExtractorRegistry(logger, createExtractors(fetcher)));
const streams = await streamResolver.resolve(ctx, [meineCloud, mostraGuarda], 'series', new ImdbId('tt12345678', 1, 1));
@ -63,7 +63,7 @@ describe('resolve', () => {
});
test('adds error info', async () => {
class MockHandler implements Handler {
class MockHandler implements Source {
readonly id = 'mockhandler';
readonly label = 'MockHandler';
@ -72,7 +72,7 @@ describe('resolve', () => {
readonly countryCodes: CountryCode[] = [CountryCode.de];
readonly handle = async (): Promise<HandleResult[]> => {
readonly handle = async (): Promise<SourceResult[]> => {
return [{ countryCode: CountryCode.de, url: new URL('https://example.com') }];
};
}
@ -163,7 +163,7 @@ describe('resolve', () => {
});
test('ignores not found errors', async () => {
const mockHandler: Handler = {
const mockHandler: Source = {
id: 'mockhandler',
label: 'MockHandler',
contentTypes: ['movie'],

View file

@ -2,7 +2,7 @@ import { ContentType, Stream } from 'stremio-addon-sdk';
import winston from 'winston';
import bytes from 'bytes';
import { Context, TIMEOUT, UrlResult } from '../types';
import { Handler } from '../handler';
import { Source } from '../source';
import { BlockedError, HttpError, NotFoundError, QueueIsFullError } from '../error';
import { flagFromCountryCode, languageFromCountryCode } from './language';
import { envGetAppName } from './env';
@ -23,13 +23,13 @@ export class StreamResolver {
this.extractorRegistry = extractorRegistry;
}
readonly resolve = async (ctx: Context, handlers: Handler[], type: ContentType, id: Id): Promise<ResolveResponse> => {
if (handlers.length === 0) {
readonly resolve = async (ctx: Context, sources: Source[], type: ContentType, id: Id): Promise<ResolveResponse> => {
if (sources.length === 0) {
return {
streams: [
{
name: 'WebStreamr',
title: '⚠️ No handlers found. Please re-configure the plugin.',
title: '⚠️ No sources found. Please re-configure the plugin.',
ytId: 'E4WlUXrJgy4',
},
],
@ -40,7 +40,7 @@ export class StreamResolver {
let handlerErrorOccurred = false;
const urlResults: UrlResult[] = [];
const handlerPromises = handlers.map(async (handler) => {
const handlerPromises = sources.map(async (handler) => {
if (!handler.contentTypes.includes(type)) {
return;
}

View file

@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`resolve adds error info 1`] = `
{
@ -117,39 +117,26 @@ exports[`resolve ignores not found errors 1`] = `
}
`;
exports[`resolve returns empty array if no handler found anything 1`] = `
exports[`resolve returns empty array if no source found anything 1`] = `
{
"streams": [],
"ttl": 900000,
}
`;
exports[`resolve returns empty array if no handler supported the type 1`] = `
exports[`resolve returns empty array if no source supported the type 1`] = `
{
"streams": [],
"ttl": 900000,
}
`;
exports[`resolve returns handler errors as stream 1`] = `
exports[`resolve returns info as stream if no sources were configured 1`] = `
{
"streams": [
{
"name": "WebStreamr",
"title": "🔗 MeineCloud
❌ Request failed. Request-id: id.",
"ytId": "E4WlUXrJgy4",
},
],
}
`;
exports[`resolve returns info as stream if no handlers were configured 1`] = `
{
"streams": [
{
"name": "WebStreamr",
"title": "⚠️ No handlers found. Please re-configure the plugin.",
"title": "⚠️ No sources found. Please re-configure the plugin.",
"ytId": "E4WlUXrJgy4",
},
],
@ -263,3 +250,16 @@ exports[`resolve returns sorted results 1`] = `
"ttl": 900000,
}
`;
exports[`resolve returns source errors as stream 1`] = `
{
"streams": [
{
"name": "WebStreamr",
"title": "🔗 MeineCloud
❌ Request failed. Request-id: id.",
"ytId": "E4WlUXrJgy4",
},
],
}
`;

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`buildManifest has checked handler with appropriate config 1`] = `
exports[`buildManifest has checked source with appropriate config 1`] = `
[
{
"default": "checked",
@ -26,7 +26,7 @@ exports[`buildManifest has checked handler with appropriate config 1`] = `
]
`;
exports[`buildManifest has unchecked handler without a config 1`] = `
exports[`buildManifest has unchecked source without a config 1`] = `
[
{
"key": "de",

View file

@ -1,5 +1,5 @@
import { buildManifest } from './manifest';
import { StreamKiste, MeineCloud, VerHdLink } from '../handler';
import { StreamKiste, MeineCloud, VerHdLink } from '../source';
import { Fetcher } from './Fetcher';
jest.mock('../utils/Fetcher');
@ -7,25 +7,25 @@ jest.mock('../utils/Fetcher');
const fetcher = new Fetcher();
describe('buildManifest', () => {
test('has unchecked handler without a config', () => {
const handlers = [
test('has unchecked source without a config', () => {
const sources = [
new VerHdLink(fetcher),
new StreamKiste(fetcher),
new MeineCloud(fetcher),
];
const manifest = buildManifest(handlers, {});
const manifest = buildManifest(sources, {});
expect(manifest.config).toMatchSnapshot();
});
test('has checked handler with appropriate config', () => {
const handlers = [
test('has checked source with appropriate config', () => {
const sources = [
new VerHdLink(fetcher),
new StreamKiste(fetcher),
new MeineCloud(fetcher),
];
const manifest = buildManifest(handlers, { de: 'on' });
const manifest = buildManifest(sources, { de: 'on' });
expect(manifest.config).toMatchSnapshot();
});

View file

@ -1,11 +1,11 @@
import { Handler } from '../handler';
import { Source } from '../source';
import { Config, CountryCode, ManifestWithConfig } from '../types';
import { envGetAppId, envGetAppName } from './env';
import { flagFromCountryCode, languageFromCountryCode } from './language';
const typedEntries = <T extends object>(obj: T): [keyof T, T[keyof T]][] => (Object.entries(obj) as [keyof T, T[keyof T]][]);
export const buildManifest = (handlers: Handler[], config: Config): ManifestWithConfig => {
export const buildManifest = (sources: Source[], config: Config): ManifestWithConfig => {
const manifest: ManifestWithConfig = {
id: envGetAppId(),
version: '0.28.0', // x-release-please-version
@ -33,19 +33,19 @@ export const buildManifest = (handlers: Handler[], config: Config): ManifestWith
},
};
const countryCodeHandlers: Partial<Record<CountryCode, Handler[]>> = {};
handlers.forEach((handler) => {
handler.countryCodes.forEach(countryCode => countryCodeHandlers[countryCode] = [...(countryCodeHandlers[countryCode] ?? []), handler]);
const countryCodeSources: Partial<Record<CountryCode, Source[]>> = {};
sources.forEach((handler) => {
handler.countryCodes.forEach(countryCode => countryCodeSources[countryCode] = [...(countryCodeSources[countryCode] ?? []), handler]);
});
const sortedLanguageHandlers = typedEntries(countryCodeHandlers)
const sortedLanguageSources = typedEntries(countryCodeSources)
.sort(([countryCodeA], [countryCodeB]) => countryCodeA.localeCompare(countryCodeB));
for (const [countryCode, handlers] of sortedLanguageHandlers) {
for (const [countryCode, sources] of sortedLanguageSources) {
manifest.config.push({
key: countryCode,
type: 'checkbox',
title: `${languageFromCountryCode(countryCode)} ${flagFromCountryCode(countryCode)} (${(handlers as Handler[]).map(handler => handler.label).join(', ')})`,
title: `${languageFromCountryCode(countryCode)} ${flagFromCountryCode(countryCode)} (${(sources as Source[]).map(handler => handler.label).join(', ')})`,
...(countryCode in config && { default: 'checked' }),
});
}