mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-24 19:11:49 +01:00
add id's to portables for better seasons
This commit is contained in:
parent
9b47f81afb
commit
570ca14905
@ -4,7 +4,7 @@ import React, { Fragment } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
|
||||
export interface OptionItem {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
@ -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}
|
||||
</>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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
|
||||
/>
|
||||
);
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})),
|
||||
})),
|
||||
|
@ -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 "";
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user