mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
feat(mock-data): share poster catalog across mocks
This commit is contained in:
1 parent
51c0d54208
commit
0b2c475b6c
13 files changed
+201
-217
No files matched your search
@@ -39,6 +39,7 @@ Then in IPTVnator, add a new Stalker portal:
|
||||
| `00:1A:79:00:00:04` | **is-series** | 60% of VOD items have `is_series=1` — tests the Ministra lazy-season flow |
|
||||
| `00:1A:79:00:00:05` | **embedded-series** | 50% of VOD items have embedded `series[]` arrays — tests the embedded series flow |
|
||||
| `00:1A:79:00:00:06` | **legacy-pagination** | No `get_all_channels` support — tests the paginated `get_ordered_list` crawl fallback for the full ITV channel list |
|
||||
| `00:1A:79:00:00:07` | **marketing-demo** | 35 original poster movies with the newest 20 first — safe for screenshots and marketing |
|
||||
| `<any other MAC>` | **auto** | MAC bytes used as seed → deterministic unique dataset |
|
||||
|
||||
## Configuration
|
||||
@@ -74,7 +75,12 @@ All endpoints are served at `GET /portal.php?action=<action>&...` matching the r
|
||||
|
||||
## Cover Images
|
||||
|
||||
Cover images and logos use [Picsum Photos](https://picsum.photos) (e.g. `https://picsum.photos/seed/{id}/300/200`). These are real images served from a CDN — no local setup required, but an internet connection is needed for images to display.
|
||||
Generated scenarios use [Picsum Photos](https://picsum.photos) for cover images
|
||||
and logos, so they need an internet connection to display artwork. The
|
||||
`marketing-demo` scenario instead uses the committed, screenshot-safe poster
|
||||
catalog shared with the Xtream mock. Stalker serves those PNGs itself from
|
||||
`/assets/marketing/poster/<slug>.png`, so screenshots remain deterministic and
|
||||
offline once the repository is checked out.
|
||||
|
||||
## Stream URLs
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"continuous": true,
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "pnpm tsx apps/stalker-mock-server/src/main.ts",
|
||||
"command": "pnpm tsx --tsconfig tsconfig.base.json apps/stalker-mock-server/src/main.ts",
|
||||
"cwd": "{workspaceRoot}",
|
||||
"color": true,
|
||||
"env": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"continuous": true,
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "pnpm tsx watch apps/stalker-mock-server/src/main.ts",
|
||||
"command": "pnpm tsx watch --tsconfig tsconfig.base.json apps/stalker-mock-server/src/main.ts",
|
||||
"cwd": "{workspaceRoot}",
|
||||
"color": true,
|
||||
"env": {
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
MarketingMovieCategoryKey,
|
||||
POSTER_SHOWCASE_MOVIES,
|
||||
} from '@iptvnator/shared/marketing-fixtures';
|
||||
import { ScenarioConfig } from './scenarios.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -115,6 +119,8 @@ export interface GeneratedPortalData {
|
||||
channels: Map<string, RawChannel[]>; // categoryId -> channels
|
||||
radio: Map<string, RawRadioStation[]>; // categoryId -> radio stations
|
||||
vod: Map<string, RawVodItem[]>; // categoryId -> items
|
||||
/** Preserves the provider-neutral fixture order for the "*" VOD listing. */
|
||||
vodOrder?: RawVodItem[];
|
||||
series: Map<string, RawSeriesItem[]>; // categoryId -> items
|
||||
seasons: Map<string, RawSeason[]>; // seriesItemId -> seasons
|
||||
epg: Map<string, RawEpgProgram[]>; // channelId -> programs
|
||||
@@ -146,6 +152,58 @@ function logoUrl(seed: string): string {
|
||||
return `https://picsum.photos/seed/logo-${seed}/100/100`;
|
||||
}
|
||||
|
||||
const STALKER_MARKETING_VOD_CATEGORIES: RawCategory[] = [
|
||||
{ id: '2901', title: 'Action & Mystery', alias: 'action_mystery' },
|
||||
{ id: '2902', title: 'Cosmic & Future Worlds', alias: 'future_worlds' },
|
||||
{ id: '2903', title: 'Family & Comedy', alias: 'family_comedy' },
|
||||
{
|
||||
id: '2904',
|
||||
title: 'Documentary & Drama',
|
||||
alias: 'documentary_drama',
|
||||
},
|
||||
];
|
||||
|
||||
const STALKER_MARKETING_VOD_CATEGORY_IDS: Record<
|
||||
MarketingMovieCategoryKey,
|
||||
string
|
||||
> = {
|
||||
'action-mystery': '2901',
|
||||
'future-fantasy': '2902',
|
||||
'family-comedy': '2903',
|
||||
'drama-documentary': '2904',
|
||||
};
|
||||
|
||||
function generateMarketingVodItems(): RawVodItem[] {
|
||||
const assetBaseUrl = `http://localhost:${process.env['PORT'] ?? '3210'}/assets/marketing/poster`;
|
||||
|
||||
return POSTER_SHOWCASE_MOVIES.map((movie, index) => {
|
||||
const id = String(29_000 + index);
|
||||
const posterUrl = `${assetBaseUrl}/${movie.slug}.png`;
|
||||
const rating = movie.rating.toFixed(1);
|
||||
|
||||
return {
|
||||
id,
|
||||
name: movie.name,
|
||||
o_name: movie.name,
|
||||
title: movie.name,
|
||||
cmd: `ffrt4://vod/${id}/index.m3u8`,
|
||||
screenshot_uri: posterUrl,
|
||||
cover: posterUrl,
|
||||
description: `${movie.tagline} ${movie.description}`,
|
||||
actors: movie.actors,
|
||||
director: movie.director,
|
||||
year: String(movie.year),
|
||||
genre: movie.genre,
|
||||
genres_str: movie.genre,
|
||||
rating_imdb: rating,
|
||||
rating_kinopoisk: rating,
|
||||
category_id: STALKER_MARKETING_VOD_CATEGORY_IDS[movie.categoryKey],
|
||||
is_series: 0,
|
||||
has_files: 1,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Generator
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -205,20 +263,39 @@ export function generatePortalData(config: ScenarioConfig): GeneratedPortalData
|
||||
}
|
||||
|
||||
// ------ VOD categories + items ------
|
||||
data.vodCategories = generateCategories('vod', config.categoryCount.vod);
|
||||
let vodIndex = 0;
|
||||
for (const cat of data.vodCategories) {
|
||||
const items = generateVodItems(
|
||||
cat.id,
|
||||
config.itemsPerCategory,
|
||||
vodIndex,
|
||||
config.isSeriesFraction,
|
||||
config.embeddedSeriesFraction,
|
||||
config.seasonsPerSeries,
|
||||
config.episodesPerSeason
|
||||
if (config.marketingFixture) {
|
||||
data.vodCategories = STALKER_MARKETING_VOD_CATEGORIES.map(
|
||||
(category) => ({ ...category })
|
||||
);
|
||||
data.vod.set(cat.id, items);
|
||||
vodIndex += config.itemsPerCategory;
|
||||
const marketingVodItems = generateMarketingVodItems();
|
||||
data.vodOrder = marketingVodItems;
|
||||
for (const category of data.vodCategories) {
|
||||
data.vod.set(
|
||||
category.id,
|
||||
marketingVodItems.filter(
|
||||
(item) => item.category_id === category.id
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
data.vodCategories = generateCategories(
|
||||
'vod',
|
||||
config.categoryCount.vod
|
||||
);
|
||||
let vodIndex = 0;
|
||||
for (const cat of data.vodCategories) {
|
||||
const items = generateVodItems(
|
||||
cat.id,
|
||||
config.itemsPerCategory,
|
||||
vodIndex,
|
||||
config.isSeriesFraction,
|
||||
config.embeddedSeriesFraction,
|
||||
config.seasonsPerSeries,
|
||||
config.episodesPerSeason
|
||||
);
|
||||
data.vod.set(cat.id, items);
|
||||
vodIndex += config.itemsPerCategory;
|
||||
}
|
||||
}
|
||||
|
||||
// ------ Series categories + items ------
|
||||
|
||||
@@ -68,8 +68,12 @@ export function handleGetOrderedList(req: Request, res: Response): void {
|
||||
} else {
|
||||
// vod (default)
|
||||
if (categoryId === '*') {
|
||||
for (const items of data.vod.values()) {
|
||||
allItems.push(...items);
|
||||
if (data.vodOrder) {
|
||||
allItems.push(...data.vodOrder);
|
||||
} else {
|
||||
for (const items of data.vod.values()) {
|
||||
allItems.push(...items);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allItems = data.vod.get(categoryId) ?? [];
|
||||
|
||||
@@ -21,6 +21,8 @@ export interface ScenarioConfig {
|
||||
* `get_ordered_list` crawling (legacy portals).
|
||||
*/
|
||||
supportsGetAllChannels?: boolean;
|
||||
/** Replace generated VOD with the shared screenshot-safe poster catalog. */
|
||||
marketingFixture?: true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +109,18 @@ export const SCENARIOS: Record<string, ScenarioConfig> = {
|
||||
embeddedSeriesFraction: 0,
|
||||
supportsGetAllChannels: false,
|
||||
},
|
||||
'00:1a:79:00:00:07': {
|
||||
name: 'marketing-demo',
|
||||
description: 'Screenshot-safe portal with 35 original poster movies',
|
||||
seed: 7007,
|
||||
categoryCount: { itv: 4, radio: 4, vod: 4, series: 4 },
|
||||
itemsPerCategory: 6,
|
||||
seasonsPerSeries: 2,
|
||||
episodesPerSeason: 5,
|
||||
isSeriesFraction: 0,
|
||||
embeddedSeriesFraction: 0,
|
||||
marketingFixture: true,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import http from 'http';
|
||||
import { join } from 'node:path';
|
||||
import express, { Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import portalRouter from './app/routes/portal.route.js';
|
||||
@@ -8,6 +9,10 @@ import { SCENARIOS } from './app/scenarios.js';
|
||||
|
||||
const PORT = parseInt(process.env['PORT'] ?? '3210', 10);
|
||||
const app = express();
|
||||
const MARKETING_POSTER_DIRECTORY = join(
|
||||
process.cwd(),
|
||||
'apps/xtream-mock-server/public/marketing/poster'
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Middleware
|
||||
@@ -35,6 +40,16 @@ app.use((req, _res, next) => {
|
||||
// Routes
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Serve the shared screenshot-safe poster catalog directly from this process.
|
||||
app.use(
|
||||
'/assets/marketing/poster',
|
||||
express.static(MARKETING_POSTER_DIRECTORY, {
|
||||
fallthrough: false,
|
||||
immutable: true,
|
||||
maxAge: '1y',
|
||||
})
|
||||
);
|
||||
|
||||
// Stalker portal.php endpoint (direct portal protocol, Electron mode)
|
||||
app.use('/portal.php', portalRouter);
|
||||
|
||||
|
||||
@@ -153,11 +153,12 @@ application code.
|
||||
|
||||
## Release Demo Artwork
|
||||
|
||||
The `marketing:marketing` fixture uses 45 original fictional titles for release
|
||||
The `marketing:marketing` fixture uses 65 original fictional titles for release
|
||||
screenshots. Its core 30-title artwork pack includes matched posters and
|
||||
backdrops; 15 additional movie-poster showcase titles use approved local poster
|
||||
PNGs and the deterministic SVG fallback for missing backdrops. Assets are served
|
||||
from:
|
||||
backdrops; 35 additional movie-poster showcase titles use approved local poster
|
||||
PNGs and the deterministic SVG fallback for missing backdrops. The newest 20
|
||||
showcase movies are ordered first so they appear immediately in screenshot
|
||||
catalog grids. Assets are served from:
|
||||
|
||||
```text
|
||||
apps/xtream-mock-server/public/marketing/{poster,backdrop}/
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"continuous": true,
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "pnpm tsx apps/xtream-mock-server/src/main.ts",
|
||||
"command": "pnpm tsx --tsconfig tsconfig.base.json apps/xtream-mock-server/src/main.ts",
|
||||
"cwd": "{workspaceRoot}",
|
||||
"color": true,
|
||||
"env": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"continuous": true,
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "pnpm tsx watch apps/xtream-mock-server/src/main.ts",
|
||||
"command": "pnpm tsx watch --tsconfig tsconfig.base.json apps/xtream-mock-server/src/main.ts",
|
||||
"cwd": "{workspaceRoot}",
|
||||
"color": true,
|
||||
"env": {
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
import {
|
||||
MarketingMovieCategoryKey,
|
||||
MarketingMovieFixture,
|
||||
POSTER_SHOWCASE_MOVIES as SHARED_POSTER_SHOWCASE_MOVIES,
|
||||
} from '@iptvnator/shared/marketing-fixtures';
|
||||
import { RawCategory } from './categories.generator.js';
|
||||
import {
|
||||
buildEpgListing,
|
||||
@@ -83,6 +88,27 @@ const VOD_CATEGORIES: RawCategory[] = [
|
||||
{ category_id: '5204', category_name: 'Documentary & Drama', parent_id: 0 },
|
||||
];
|
||||
|
||||
const XTREAM_VOD_CATEGORY_IDS: Record<MarketingMovieCategoryKey, string> = {
|
||||
'action-mystery': '5201',
|
||||
'future-fantasy': '5202',
|
||||
'family-comedy': '5203',
|
||||
'drama-documentary': '5204',
|
||||
};
|
||||
|
||||
function toMarketingMovie(movie: MarketingMovieFixture): MarketingMovie {
|
||||
return {
|
||||
actors: movie.actors,
|
||||
categoryId: XTREAM_VOD_CATEGORY_IDS[movie.categoryKey],
|
||||
description: movie.description,
|
||||
director: movie.director,
|
||||
genre: movie.genre,
|
||||
name: movie.name,
|
||||
rating: movie.rating,
|
||||
tagline: movie.tagline,
|
||||
year: movie.year,
|
||||
};
|
||||
}
|
||||
|
||||
const SERIES_CATEGORIES: RawCategory[] = [
|
||||
{ category_id: '5301', category_name: 'Urban Drama', parent_id: 0 },
|
||||
{ category_id: '5302', category_name: 'Speculative Series', parent_id: 0 },
|
||||
@@ -352,192 +378,12 @@ const GENERATED_ARTWORK_MOVIES: MarketingMovie[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const POSTER_SHOWCASE_MOVIES: MarketingMovie[] = [
|
||||
{
|
||||
categoryId: '5204',
|
||||
name: 'Orchard Walls',
|
||||
description:
|
||||
'A quiet prison gardener and a new volunteer rebuild an abandoned orchard while both reconsider the lives waiting beyond its walls.',
|
||||
director: 'Elena March',
|
||||
actors: 'Darius Cole, Mina Hart, Ruth Bell, Owen Pike',
|
||||
genre: 'Human Drama',
|
||||
rating: 8.4,
|
||||
tagline: 'Some walls are grown, not built.',
|
||||
year: 2025,
|
||||
},
|
||||
{
|
||||
categoryId: '5203',
|
||||
name: 'Blue Current',
|
||||
description:
|
||||
'A young reef fish and an anxious lantern companion cross a changing ocean current to guide their family home.',
|
||||
director: 'Talia North',
|
||||
actors: 'Lumi Vale, Finn Reed, Orla Moss, Kit Arden',
|
||||
genre: 'Animated Family Adventure',
|
||||
rating: 8.6,
|
||||
tagline: 'The smallest light can find the way.',
|
||||
year: 2026,
|
||||
},
|
||||
{
|
||||
categoryId: '5201',
|
||||
name: 'Signal Nine',
|
||||
description:
|
||||
'A transit reporter and a railway investigator trace a repeating emergency signal through a city that insists nothing happened.',
|
||||
director: 'Mara Venn',
|
||||
actors: 'Nadia Cross, Elias Rowe, June Mercer, Tomas Vale',
|
||||
genre: 'Urban Conspiracy Thriller',
|
||||
rating: 8.1,
|
||||
tagline: 'The ninth signal was never sent.',
|
||||
year: 2026,
|
||||
},
|
||||
{
|
||||
categoryId: '5204',
|
||||
name: 'Checkout at Noon',
|
||||
description:
|
||||
'An exhausted hotel manager has one morning to settle a cheerful traveler and a formidable guest into the same impossible room.',
|
||||
director: 'Milo Hart',
|
||||
actors: 'Adrian Moss, Clara Wynn, Evelyn Shore, Ben Vale',
|
||||
genre: 'Ensemble Comedy',
|
||||
rating: 7.7,
|
||||
tagline: 'Every guest has a different plan.',
|
||||
year: 2025,
|
||||
},
|
||||
{
|
||||
categoryId: '5202',
|
||||
name: 'Vesper Crown',
|
||||
description:
|
||||
'A royal archivist crosses a silent salt kingdom to recover a broken crown before its seven pieces choose a new ruler.',
|
||||
director: 'Iris March',
|
||||
actors: 'Selah Quinn, Rowan Pike, Mira Holt, Theo Arden',
|
||||
genre: 'Fantasy Adventure',
|
||||
rating: 8.3,
|
||||
tagline: 'Seven pieces remember the throne.',
|
||||
year: 2026,
|
||||
},
|
||||
{
|
||||
categoryId: '5201',
|
||||
name: 'Last Detour',
|
||||
description:
|
||||
'A veteran mechanic and a bicycle courier take one mountain road too far and discover why every map avoids it.',
|
||||
director: 'Noah Rook',
|
||||
actors: 'Tessa Marr, Leo Venn, Omar Pike, Nina Cross',
|
||||
genre: 'Road Action Thriller',
|
||||
rating: 7.9,
|
||||
tagline: 'The long way back is the only way out.',
|
||||
year: 2025,
|
||||
},
|
||||
{
|
||||
categoryId: '5204',
|
||||
name: 'Borrowed Summer',
|
||||
description:
|
||||
'Two former friends meet beside a lakeside bus route and spend one final season deciding which memories still belong to them.',
|
||||
director: 'Lina Shore',
|
||||
actors: 'Mara Wynn, Julian Hart, Elise Rowe, Finn Cole',
|
||||
genre: 'Romantic Drama',
|
||||
rating: 8.0,
|
||||
tagline: 'Some seasons are only passing through.',
|
||||
year: 2024,
|
||||
},
|
||||
{
|
||||
categoryId: '5201',
|
||||
name: 'Under the Floor',
|
||||
description:
|
||||
'A tenant renovating an empty family home follows a red thread beneath the floorboards and into a history no room remembers.',
|
||||
director: 'Oren Vale',
|
||||
actors: 'Rhea Moss, Daniel Cross, Mina Shore, Asa Bell',
|
||||
genre: 'Supernatural Horror',
|
||||
rating: 7.8,
|
||||
tagline: 'The house kept one thing hidden.',
|
||||
year: 2025,
|
||||
},
|
||||
{
|
||||
categoryId: '5202',
|
||||
name: 'Red Winter',
|
||||
description:
|
||||
'A planetary botanist crosses a frozen red plain with the last living samples after her remote habitat falls silent.',
|
||||
director: 'Vera Holt',
|
||||
actors: 'Nora Venn, Kai March, Elias Moss, Ruth Arden',
|
||||
genre: 'Science Fiction Survival',
|
||||
rating: 8.5,
|
||||
tagline: 'Life travels light.',
|
||||
year: 2026,
|
||||
},
|
||||
{
|
||||
categoryId: '5203',
|
||||
name: 'Saturday Champions',
|
||||
description:
|
||||
'Six neighborhood footballers with mismatched kits train for the one match their muddy community field has been waiting for.',
|
||||
director: 'Tomas Hart',
|
||||
actors: 'Milo Quinn, Ari Bell, Leni Moss, Kit Rowe',
|
||||
genre: 'Family Sports Comedy',
|
||||
rating: 7.9,
|
||||
tagline: 'The score is not the whole story.',
|
||||
year: 2025,
|
||||
},
|
||||
{
|
||||
categoryId: '5204',
|
||||
name: 'The Silent Verdict',
|
||||
description:
|
||||
'A public defender receives one sealed envelope after an empty courtroom closes and must decide whether justice can survive the truth.',
|
||||
director: 'June Marr',
|
||||
actors: 'Amara Voss, Theo Reed, Elias Hart, Cora Wynn',
|
||||
genre: 'Courtroom Drama',
|
||||
rating: 8.2,
|
||||
tagline: 'The final evidence said nothing.',
|
||||
year: 2026,
|
||||
},
|
||||
{
|
||||
categoryId: '5201',
|
||||
name: 'Low Tide Letters',
|
||||
description:
|
||||
'An aging postal worker crosses the tidal flats to deliver a final bundle of letters to a village missing from every new map.',
|
||||
director: 'Owen Shore',
|
||||
actors: 'Bram North, Iris Cole, Lena March, Rafi Bell',
|
||||
genre: 'Coastal Mystery',
|
||||
rating: 8.1,
|
||||
tagline: 'The sea returns every address.',
|
||||
year: 2024,
|
||||
},
|
||||
{
|
||||
categoryId: '5204',
|
||||
name: 'Salt & Cedar',
|
||||
description:
|
||||
'Two estranged siblings reopen their family restaurant and discover that the old recipes settle fewer arguments than they start.',
|
||||
director: 'Cora Vale',
|
||||
actors: 'Mina Wynn, Adrian Rowe, Ruth Moss, Nico Hart',
|
||||
genre: 'Culinary Dramedy',
|
||||
rating: 7.8,
|
||||
tagline: 'Every table remembers.',
|
||||
year: 2025,
|
||||
},
|
||||
{
|
||||
categoryId: '5201',
|
||||
name: 'Brass Horizon',
|
||||
description:
|
||||
'A desert aircraft engineer races an experimental survey plane ahead of a dust front that has erased every route home.',
|
||||
director: 'Ren Arden',
|
||||
actors: 'Talia Cross, Owen Pike, Mara Bell, Jules North',
|
||||
genre: 'Retro Aviation Adventure',
|
||||
rating: 8.0,
|
||||
tagline: 'Build what the horizon cannot stop.',
|
||||
year: 2023,
|
||||
},
|
||||
{
|
||||
categoryId: '5203',
|
||||
name: 'Moonlight Delivery',
|
||||
description:
|
||||
'A young night courier climbs a winding miniature city to deliver three glowing parcels before the moon disappears.',
|
||||
director: 'Lumi Hart',
|
||||
actors: 'Kit Vale, Orla Reed, Finn Moss, Ari Shore',
|
||||
genre: 'Animated Family Adventure',
|
||||
rating: 8.4,
|
||||
tagline: 'Every doorstep needs a little light.',
|
||||
year: 2026,
|
||||
},
|
||||
];
|
||||
const POSTER_SHOWCASE_MOVIES: MarketingMovie[] =
|
||||
SHARED_POSTER_SHOWCASE_MOVIES.map(toMarketingMovie);
|
||||
|
||||
const MOVIES: MarketingMovie[] = [
|
||||
...GENERATED_ARTWORK_MOVIES,
|
||||
...POSTER_SHOWCASE_MOVIES,
|
||||
...GENERATED_ARTWORK_MOVIES,
|
||||
];
|
||||
|
||||
const SERIES: MarketingSeries[] = [
|
||||
|
||||
@@ -14,6 +14,7 @@ The mock server enables:
|
||||
1. **Local development** without access to a real Stalker portal
|
||||
2. **Playwright E2E testing** with predictable, deterministic data
|
||||
3. **Scenario-based testing** via predefined MAC addresses that map to specific data shapes
|
||||
4. **Screenshot-safe marketing capture** with committed fictional posters shared with the Xtream mock
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
@@ -56,6 +57,11 @@ faker.seed(config.seed) // scenario seed; unknown MACs: macToSeed(mac)
|
||||
│ ├── is_series=1 items (fraction, Ministra flow)
|
||||
│ └── embedded series[] items (fraction)
|
||||
│
|
||||
├── marketingFixture? → shared curated VOD catalog
|
||||
│ ├── @iptvnator/shared/marketing-fixtures
|
||||
│ ├── vod Map<categoryId, item[]>
|
||||
│ └── vodOrder[] preserves screenshot catalog order
|
||||
│
|
||||
└── generateCategories('series', N) → seriesCategories[]
|
||||
└── generateSeriesItems() → series Map<categoryId, item[]>
|
||||
└── generateSeasons() → seasons Map<seriesItemId, season[]>
|
||||
@@ -219,6 +225,7 @@ interface ScenarioConfig {
|
||||
embeddedSeriesFraction: number; // 0–1: fraction of VOD with embedded series[]
|
||||
supportsGetAllChannels?: boolean; // default true; false mimics legacy portals
|
||||
// without the ITV get_all_channels action
|
||||
marketingFixture?: true; // replace generated VOD with shared posters
|
||||
}
|
||||
```
|
||||
|
||||
@@ -230,6 +237,13 @@ supporting scenarios, `get_all_channels` (`get-all-channels.handler.ts`,
|
||||
`{ js: { data, total_items } }` response, excluding channels from censored
|
||||
(adult) genres.
|
||||
|
||||
The `marketing-demo` scenario (`00:1A:79:00:00:07`) replaces faker-generated
|
||||
VOD with the 35-movie provider-neutral showcase catalog from
|
||||
`@iptvnator/shared/marketing-fixtures`. Its newest 20 movies are returned first
|
||||
for the wildcard VOD listing. Poster URLs point back to the Stalker process at
|
||||
`/assets/marketing/poster/<slug>.png`; `main.ts` serves the committed PNG
|
||||
directory directly, so the Xtream server does not need to be running.
|
||||
|
||||
### Adding a New Scenario
|
||||
|
||||
1. Add an entry to the `SCENARIOS` map in `src/app/scenarios.ts`.
|
||||
|
||||
@@ -273,11 +273,14 @@ labels instead of trusting provider-local strings.
|
||||
|
||||
This scenario is reserved for release screenshots and marketing materials:
|
||||
|
||||
- VOD and series data use 45 curated fictional titles instead of faker-generated
|
||||
- VOD and series data use 65 curated fictional titles instead of faker-generated
|
||||
titles or real media metadata
|
||||
- the core 30-title generated artwork pack keeps matched posters and backdrops;
|
||||
15 additional movie-poster showcase titles use approved local poster PNGs and
|
||||
the deterministic SVG fallback for their missing backdrops
|
||||
35 additional movie-poster showcase titles use approved local poster PNGs and
|
||||
the deterministic SVG fallback for their missing backdrops; the newest 20
|
||||
appear first in the VOD catalog
|
||||
- showcase metadata comes from the provider-neutral
|
||||
`@iptvnator/shared/marketing-fixtures` catalog shared with the Stalker mock
|
||||
- posters and backdrops are served from committed PNG files in
|
||||
`apps/xtream-mock-server/public/marketing/{poster,backdrop}/`
|
||||
- `tools/release/generate-marketing-artwork.ts` generates the PNGs with
|
||||
|
||||
+4
-4
@@ -57,10 +57,10 @@
|
||||
"serve:website": "nx serve website",
|
||||
"build:website": "nx build website",
|
||||
"i18n:check": "node tools/i18n/check-drift.mjs",
|
||||
"release:artwork:dry-run": "tsx tools/release/generate-marketing-artwork.ts --dry-run",
|
||||
"release:artwork:manifest": "tsx tools/release/generate-marketing-artwork.ts --manifest",
|
||||
"release:artwork:generate": "tsx tools/release/generate-marketing-artwork.ts --generate",
|
||||
"release:artwork:validate": "tsx tools/release/generate-marketing-artwork.ts --validate",
|
||||
"release:artwork:dry-run": "tsx --tsconfig tsconfig.base.json tools/release/generate-marketing-artwork.ts --dry-run",
|
||||
"release:artwork:manifest": "tsx --tsconfig tsconfig.base.json tools/release/generate-marketing-artwork.ts --manifest",
|
||||
"release:artwork:generate": "tsx --tsconfig tsconfig.base.json tools/release/generate-marketing-artwork.ts --generate",
|
||||
"release:artwork:validate": "tsx --tsconfig tsconfig.base.json tools/release/generate-marketing-artwork.ts --validate",
|
||||
"release:notes:validate": "node tools/release/build-release-notes.mjs --validate",
|
||||
"release:notes:github": "node tools/release/build-release-notes.mjs --format github",
|
||||
"release:notes:changelog": "node tools/release/build-release-notes.mjs --format changelog",
|
||||
|
||||
@@ -126,7 +126,11 @@ async function ensureXtreamMockServer(): Promise<ChildProcess | undefined> {
|
||||
|
||||
const child = spawn(
|
||||
path.join(workspaceRoot, 'node_modules/.bin/tsx'),
|
||||
['apps/xtream-mock-server/src/main.ts'],
|
||||
[
|
||||
'--tsconfig',
|
||||
'tsconfig.base.json',
|
||||
'apps/xtream-mock-server/src/main.ts',
|
||||
],
|
||||
{
|
||||
cwd: workspaceRoot,
|
||||
env: {
|
||||
@@ -515,7 +519,7 @@ async function openXtreamVodDetails(page: Page): Promise<void> {
|
||||
const xtreamId = requireCapturedPlaylistId('xtreams');
|
||||
|
||||
await clickHrefSuffix(page, `/workspace/xtreams/${xtreamId}/vod`);
|
||||
await clickCategoryByName(page, 'Hero Premieres');
|
||||
await clickCategoryByName(page, 'Action & Mystery');
|
||||
await clickFirstGridCard(page);
|
||||
await page.waitForURL(/\/workspace\/xtreams\/[^/]+\/vod\/[^/]+\/[^/]+/, {
|
||||
timeout: 30_000,
|
||||
@@ -531,7 +535,7 @@ async function openXtreamSeriesSeason(page: Page): Promise<void> {
|
||||
const xtreamId = requireCapturedPlaylistId('xtreams');
|
||||
|
||||
await clickHrefSuffix(page, `/workspace/xtreams/${xtreamId}/series`);
|
||||
await clickCategoryByName(page, 'Hero Teams');
|
||||
await clickCategoryByName(page, 'Urban Drama');
|
||||
await clickFirstGridCard(page);
|
||||
await page.waitForURL(/\/workspace\/xtreams\/[^/]+\/series\/[^/]+\/[^/]+/, {
|
||||
timeout: 30_000,
|
||||
|
||||
Reference in new issue
Block a user