Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 8x 519x 175x 175x 175x 174x 8x 49x 49x 187x 187x 153x 34x 34x 34x 153x 8x 25x 12x 12x 12x 12x 8x 13x 8x 5x | // SPDX-License-Identifier: GPL-3.0-only
// Copyright (C) 2026 Max Boksem. See NOTICE for additional terms under GPLv3 section 7.
import type { Channel } from "./types";
import { withSource } from "./sources";
/** Episodes ordered by season, then episode number. */
export const sortEpisodes = (eps: Channel[]): Channel[] =>
[...eps].sort((a, b) => {
const sa = a.season ?? 0,
sb = b.season ?? 0;
if (sa !== sb) return sa - sb;
return (a.episode ?? 0) - (b.episode ?? 0);
});
/**
* One entry per season + episode. Duplicate entries (the same episode from
* other providers or categories) are folded into the first one's `sources`.
*/
export const dedupeEpisodes = (episodes: Channel[]): Channel[] => {
const seen = new Map<string, { ch: Channel; extraSources: string[] }>();
for (const ep of episodes) {
const key = `${ep.season ?? 0}x${ep.episode ?? ep.name}`;
if (!seen.has(key)) {
seen.set(key, { ch: { ...ep }, extraSources: [...ep.sources] });
} else {
const entry = seen.get(key)!;
entry.extraSources.push(ep.url, ...ep.sources);
if (!entry.ch.logoUrl && ep.logoUrl) entry.ch.logoUrl = ep.logoUrl;
}
}
return Array.from(seen.values()).map(({ ch, extraSources }) => ({
...ch,
sources: extraSources,
}));
};
/**
* Find a previously watched episode in a series' episode list, e.g. to replay
* it from watch history. Matches by id first, then by the SxxEyy in `name`
* (episode ids are index-based and can shift when the provider refreshes).
* The result carries every source of that episode, set to the one watched
* when it's still available. Returns null when the episode isn't in the list.
*/
export const findEpisode = (episodes: Channel[], id: string, name: string): Channel | null => {
const exact = episodes.find((e) => e.id === id);
const m = name.match(/S(\d{1,3})E(\d{1,3})/i);
const season = exact?.season ?? (m ? parseInt(m[1], 10) : undefined);
const episode = exact?.episode ?? (m ? parseInt(m[2], 10) : undefined);
if (season == null || episode == null) return exact ?? null;
const merged = dedupeEpisodes(episodes).find(
(e) => e.season === season && e.episode === episode
);
if (!merged) return exact ?? null;
return exact ? withSource(merged, exact.url) : merged;
};
|