add id's to portables for better seasons

This commit is contained in:
mrjvs 2022-03-13 16:55:59 +01:00
parent 9b47f81afb
commit 570ca14905
15 changed files with 118 additions and 74 deletions

View file

@ -4,7 +4,7 @@ import React, { Fragment } from "react";
import { Listbox, Transition } from "@headlessui/react";
export interface OptionItem {
id: number;
id: string;
name: string;
}

View file

@ -1,4 +1,4 @@
import { Dropdown } from "components/Dropdown";
import { Dropdown, OptionItem } from "components/Dropdown";
import { WatchedEpisode } from "components/media/WatchedEpisodeButton";
import { useLoading } from "hooks/useLoading";
import { serializePortableMedia } from "hooks/usePortableMedia";
@ -6,6 +6,7 @@ import {
convertMediaToPortable,
MWMedia,
MWMediaSeasons,
MWMediaSeason,
MWPortableMedia,
} from "providers";
import { getSeasonDataFromMedia } from "providers/methods/seasons";
@ -22,8 +23,8 @@ export function Seasons(props: SeasonsProps) {
);
const history = useHistory();
const [seasons, setSeasons] = useState<MWMediaSeasons>({ seasons: [] });
const seasonSelected = props.media.season as number;
const episodeSelected = props.media.episode as number;
const seasonSelected = props.media.seasonId as string;
const episodeSelected = props.media.episodeId as string;
useEffect(() => {
(async () => {
@ -32,10 +33,10 @@ export function Seasons(props: SeasonsProps) {
})();
}, [searchSeasons, props.media]);
function navigateToSeasonAndEpisode(season: number, episode: number) {
function navigateToSeasonAndEpisode(seasonId: string, episodeId: string) {
const newMedia: MWMedia = { ...props.media };
newMedia.episode = episode;
newMedia.season = season;
newMedia.episodeId = episodeId;
newMedia.seasonId = seasonId;
history.replace(
`/media/${newMedia.mediaType}/${serializePortableMedia(
convertMediaToPortable(newMedia)
@ -43,15 +44,17 @@ export function Seasons(props: SeasonsProps) {
);
}
const options = seasons.seasons.map((season) => ({
id: season.seasonNumber,
name: `Season ${season.seasonNumber}`,
}));
const mapSeason = (season: MWMediaSeason) => ({
id: season.id,
name: season.title || `Season ${season.sort}`,
});
const selectedItem = {
id: seasonSelected,
name: `Season ${seasonSelected}`,
};
const options = seasons.seasons.map(mapSeason);
const foundSeason = seasons.seasons.find(
(season) => season.id === seasonSelected
);
const selectedItem = foundSeason ? mapSeason(foundSeason) : null;
return (
<>
@ -60,29 +63,31 @@ export function Seasons(props: SeasonsProps) {
{success && seasons.seasons.length ? (
<>
<Dropdown
selectedItem={selectedItem}
selectedItem={selectedItem as OptionItem}
options={options}
setSelectedItem={(seasonItem) =>
navigateToSeasonAndEpisode(
seasonItem.id,
seasons.seasons[seasonItem.id]?.episodes[0].episodeNumber
seasons.seasons.find((s) => s.id === seasonItem.id)?.episodes[0]
.id as string
)
}
/>
{seasons.seasons[seasonSelected]?.episodes.map((v) => (
<WatchedEpisode
key={v.episodeNumber}
media={{
...props.media,
episode: v.episodeNumber,
season: seasonSelected,
}}
active={v.episodeNumber === episodeSelected}
onClick={() =>
navigateToSeasonAndEpisode(seasonSelected, v.episodeNumber)
}
/>
))}
{seasons.seasons
.find((s) => s.id === seasonSelected)
?.episodes.map((v) => (
<WatchedEpisode
key={v.id}
media={{
...props.media,
seriesData: seasons,
episodeId: v.id,
seasonId: seasonSelected,
}}
active={v.id === episodeSelected}
onClick={() => navigateToSeasonAndEpisode(seasonSelected, v.id)}
/>
))}
</>
) : null}
</>

View file

@ -1,5 +1,6 @@
import {
convertMediaToPortable,
getEpisodeFromMedia,
getProviderFromId,
MWMediaMeta,
MWMediaType,
@ -53,9 +54,9 @@ function MediaCardContent({
<div className="flex-1">
<h1 className="mb-1 font-bold text-white">
{media.title}
{series ? (
{series && media.seasonId && media.episodeId ? (
<span className="text-denim-700 ml-2 text-xs">
S{media.season} E{media.episode}
S{media.seasonId} E{media.episodeId}
</span>
) : null}
</h1>

View file

@ -1,9 +1,9 @@
import { MWMediaMeta } from "providers";
import { getEpisodeFromMedia, MWMedia } from "providers";
import { useWatchedContext, getWatchedFromPortable } from "state/watched";
import { Episode } from "./EpisodeButton";
export interface WatchedEpisodeProps {
media: MWMediaMeta;
media: MWMedia;
onClick?: () => void;
active?: boolean;
}
@ -11,12 +11,13 @@ export interface WatchedEpisodeProps {
export function WatchedEpisode(props: WatchedEpisodeProps) {
const { watched } = useWatchedContext();
const foundWatched = getWatchedFromPortable(watched.items, props.media);
const episode = getEpisodeFromMedia(props.media);
const watchedPercentage = (foundWatched && foundWatched.percentage) || 0;
return (
<Episode
progress={watchedPercentage}
episodeNumber={props.media.episode ?? 1}
episodeNumber={episode?.episode?.sort ?? 1}
active={props.active}
onClick={props.onClick}
/>

View file

@ -16,7 +16,7 @@ export function WatchedMediaCard(props: WatchedMediaCardProps) {
<MediaCard
watchedPercentage={watchedPercentage}
media={props.media}
series={props.series && props.media.episode !== undefined}
series={props.series && props.media.episodeId !== undefined}
linkable
/>
);

View file

@ -14,8 +14,8 @@ export function convertMediaToPortable(media: MWMedia): MWPortableMedia {
mediaId: media.mediaId,
providerId: media.providerId,
mediaType: media.mediaType,
episode: media.episode,
season: media.season,
episodeId: media.episodeId,
seasonId: media.seasonId,
};
}

View file

@ -53,7 +53,7 @@ export const theFlixScraper: MWMediaProvider = {
if (media.mediaType === MWMediaType.MOVIE) {
url = `${CORS_PROXY_URL}https://theflix.to/movie/${media.mediaId}?movieInfo=${media.mediaId}`;
} else if (media.mediaType === MWMediaType.SERIES) {
url = `${CORS_PROXY_URL}https://theflix.to/tv-show/${media.mediaId}/season-${media.season}/episode-${media.episode}`;
url = `${CORS_PROXY_URL}https://theflix.to/tv-show/${media.mediaId}/season-${media.seasonId}/episode-${media.episodeId}`;
}
const res = await fetch(url).then((d) => d.text());
@ -75,7 +75,7 @@ export const theFlixScraper: MWMediaProvider = {
async getSeasonDataFromMedia(
media: MWPortableMedia
): Promise<MWMediaSeasons> {
const url = `${CORS_PROXY_URL}https://theflix.to/tv-show/${media.mediaId}/season-${media.season}/episode-${media.episode}`;
const url = `${CORS_PROXY_URL}https://theflix.to/tv-show/${media.mediaId}/season-${media.seasonId}/episode-${media.episodeId}`;
const res = await fetch(url).then((d) => d.text());
const node: Element = Array.from(
@ -87,10 +87,14 @@ export const theFlixScraper: MWMediaProvider = {
const data = JSON.parse(node.innerHTML).props.pageProps.selectedTv.seasons;
return {
seasons: data.map((d: any) => ({
seasonNumber: d.seasonNumber === 0 ? 999 : d.seasonNumber,
sort: d.seasonNumber === 0 ? 999 : d.seasonNumber,
id: d.seasonNumber.toString(),
type: d.seasonNumber === 0 ? "special" : "season",
title: d.seasonNumber === 0 ? "Specials" : undefined,
episodes: d.episodes.map((e: any) => ({
title: e.name,
sort: e.episodeNumber,
id: e.episodeNumber.toString(),
episodeNumber: e.episodeNumber,
})),
})),

View file

@ -6,7 +6,7 @@ const getTheFlixUrl = (media: MWPortableMedia, params?: URLSearchParams) => {
return `https://theflix.to/movie/${media.mediaId}?${params}`;
}
if (media.mediaType === MWMediaType.SERIES) {
return `https://theflix.to/tv-show/${media.mediaId}/season-${media.season}/episode-${media.episode}`;
return `https://theflix.to/tv-show/${media.mediaId}/season-${media.seasonId}/episode-${media.episodeId}`;
}
return "";

View file

@ -38,11 +38,11 @@ export function turnDataIntoMedia(data: any): MWProviderMediaResult {
title: data.name,
year: new Date(data.releaseDate).getFullYear().toString(),
seasonCount: data.numberOfSeasons,
episode: data.lastReleasedEpisode
? data.lastReleasedEpisode.episodeNumber
episodeId: data.lastReleasedEpisode
? data.lastReleasedEpisode.episodeNumber.toString()
: null,
season: data.lastReleasedEpisode
? data.lastReleasedEpisode.seasonNumber
seasonId: data.lastReleasedEpisode
? data.lastReleasedEpisode.seasonNumber.toString()
: null,
};
}

View file

@ -1,4 +1,5 @@
import { MWMediaType, MWMediaProviderMetadata } from "providers";
import { MWMedia, MWMediaEpisode, MWMediaSeason } from "providers/types";
import { mediaProviders, mediaProvidersUnchecked } from "./providers";
/*
@ -38,3 +39,27 @@ export function getProviderMetadata(id: string): MWMediaProviderMetadata {
provider,
};
}
/*
** get episode and season from media
*/
export function getEpisodeFromMedia(
media: MWMedia
): { season: MWMediaSeason; episode: MWMediaEpisode } | null {
if (
media.seasonId === undefined ||
media.episodeId === undefined ||
media.seriesData === undefined
) {
return null;
}
const season = media.seriesData.seasons.find((v) => v.id === media.seasonId);
if (!season) return null;
const episode = season?.episodes.find((v) => v.id === media.episodeId);
if (!episode) return null;
return {
season,
episode,
};
}

View file

@ -28,10 +28,8 @@ export async function getSeasonDataFromMedia(
}
const seasonData = await provider.getSeasonDataFromMedia(media);
seasonData.seasons.sort((a, b) => a.seasonNumber - b.seasonNumber);
seasonData.seasons.forEach((s) =>
s.episodes.sort((a, b) => a.episodeNumber - b.episodeNumber)
);
seasonData.seasons.sort((a, b) => a.sort - b.sort);
seasonData.seasons.forEach((s) => s.episodes.sort((a, b) => a.sort - b.sort));
// cache it
seasonCache.set(media, seasonData, 60 * 60); // cache it for an hour

View file

@ -8,8 +8,8 @@ export interface MWPortableMedia {
mediaId: string;
mediaType: MWMediaType;
providerId: string;
season?: number;
episode?: number;
seasonId?: string;
episodeId?: string;
}
export type MWMediaStreamType = "m3u8" | "mp4";
@ -24,15 +24,20 @@ export interface MWMediaMeta extends MWPortableMedia {
seasonCount?: number;
}
export interface MWMediaEpisode {
sort: number;
id: string;
title: string;
}
export interface MWMediaSeason {
sort: number;
id: string;
title?: string;
type: "season" | "special";
episodes: MWMediaEpisode[];
}
export interface MWMediaSeasons {
seasons: {
seasonNumber: number;
type: "season" | "special";
episodes: {
title: string;
episodeNumber: number;
}[];
}[];
seasons: MWMediaSeason[];
}
export interface MWMedia extends MWMediaMeta {

View file

@ -23,8 +23,8 @@ export function WrapProvider(
// consult cache first
const output = contentCache.get(media);
if (output) {
output.season = media.season;
output.episode = media.episode;
output.seasonId = media.seasonId;
output.episodeId = media.episodeId;
return output;
}

View file

@ -35,8 +35,8 @@ function getBookmarkIndexFromMedia(
(v) =>
v.mediaId === media.mediaId &&
v.providerId === media.providerId &&
v.episode === media.episode &&
v.season === media.season
v.episodeId === media.episodeId &&
v.seasonId === media.seasonId
);
return a;
}
@ -75,8 +75,8 @@ export function BookmarkContextProvider(props: { children: ReactNode }) {
providerId: media.providerId,
title: media.title,
year: media.year,
episode: media.episode,
season: media.season,
episodeId: media.episodeId,
seasonId: media.seasonId,
};
data.bookmarks.push(item);
}

View file

@ -1,4 +1,9 @@
import { MWMediaMeta, getProviderMetadata, MWMediaType } from "providers";
import {
MWMediaMeta,
getProviderMetadata,
MWMediaType,
getEpisodeFromMedia,
} from "providers";
import React, {
createContext,
ReactNode,
@ -32,8 +37,8 @@ export function getWatchedFromPortable(
(v) =>
v.mediaId === media.mediaId &&
v.providerId === media.providerId &&
v.episode === media.episode &&
v.season === media.season
v.episodeId === media.episodeId &&
v.seasonId === media.seasonId
);
}
@ -84,8 +89,8 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
year: media.year,
percentage: 0,
progress: 0,
episode: media.episode,
season: media.season,
episodeId: media.episodeId,
seasonId: media.seasonId,
};
data.items.push(item);
}
@ -112,8 +117,8 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
) {
const key = `${item.mediaType}-${item.mediaId}`;
const current: [number, number] = [
item.season ?? -1,
item.episode ?? -1,
item.episodeId ? parseInt(item.episodeId, 10) : -1,
item.seasonId ? parseInt(item.seasonId, 10) : -1,
];
let existing = highestEpisode[key];
if (!existing) {