From 1b765fe8570baed9eb028a6776e1d8e5639fa672 Mon Sep 17 00:00:00 2001 From: Radiquum Date: Sat, 15 Mar 2025 15:41:08 +0500 Subject: [PATCH] feat: add AniLibria parsing --- .../ReleasePlayer/EpisodeSelector.tsx | 3 + .../ReleasePlayer/ReleasePlayerCustom.tsx | 193 +- .../ReleasePlayer/SourceSelector.tsx | 71 + .../ReleasePlayer/VoiceoverSelector.tsx | 88 + app/pages/Release.tsx | 2 +- package-lock.json | 3625 +---------------- package.json | 4 +- 7 files changed, 370 insertions(+), 3616 deletions(-) create mode 100644 app/components/ReleasePlayer/EpisodeSelector.tsx create mode 100644 app/components/ReleasePlayer/SourceSelector.tsx create mode 100644 app/components/ReleasePlayer/VoiceoverSelector.tsx diff --git a/app/components/ReleasePlayer/EpisodeSelector.tsx b/app/components/ReleasePlayer/EpisodeSelector.tsx new file mode 100644 index 0000000..80ac9ca --- /dev/null +++ b/app/components/ReleasePlayer/EpisodeSelector.tsx @@ -0,0 +1,3 @@ +export const EpisodeSelector = () => { + return
EPISODES
+} diff --git a/app/components/ReleasePlayer/ReleasePlayerCustom.tsx b/app/components/ReleasePlayer/ReleasePlayerCustom.tsx index 2964477..a32deeb 100644 --- a/app/components/ReleasePlayer/ReleasePlayerCustom.tsx +++ b/app/components/ReleasePlayer/ReleasePlayerCustom.tsx @@ -1,11 +1,21 @@ "use client"; import { Card } from "flowbite-react"; -import Player from "next-video/player"; import { useEffect, useState } from "react"; import { ENDPOINTS } from "#/api/config"; -export const ReleasePlayerCustom = (props: { id: number }) => { +import { VoiceoverSelector } from "./VoiceoverSelector"; +import { SourceSelector } from "./SourceSelector"; +import { EpisodeSelector } from "./EpisodeSelector"; +import { Spinner } from "../Spinner/Spinner"; + +import HlsVideo from "hls-video-element/react"; +import MediaThemeSutro from "player.style/sutro/react"; + +export const ReleasePlayerCustom = (props: { + id: number; + token: string | null; +}) => { const [voiceover, setVoiceover] = useState({ selected: null, available: null, @@ -18,10 +28,18 @@ export const ReleasePlayerCustom = (props: { id: number }) => { selected: null, available: null, }); - const [playerSrc, SetPlayerSrc] = useState(null); + const [playerProps, SetPlayerProps] = useState({ + src: null, + poster: null, + useCustom: false, + }); const _fetchVoiceover = async (release_id: number) => { - const response = await fetch(`${ENDPOINTS.release.episode}/${release_id}`); + let url = `${ENDPOINTS.release.episode}/${release_id}`; + if (props.token) { + url += `?token=${props.token}`; + } + const response = await fetch(url); const data = await response.json(); return data; }; @@ -58,18 +76,21 @@ export const ReleasePlayerCustom = (props: { id: number }) => { if (urlParamsMatch.length == 0) { alert("Failed to get urlParams"); return; - }; + } - const urlParamsStr = urlParamsMatch[0].replace("var urlParams = '", "").replace("';", ""); + const urlParamsStr = urlParamsMatch[0] + .replace("var urlParams = '", "") + .replace("';", ""); const urlParams = JSON.parse(urlParamsStr); - const urlStr = url.replace("https://kodik.info/", "") - const type = urlStr.split("/")[0] - const id = urlStr.split("/")[1] - const hash = urlStr.split("/")[2] + const urlStr = url.replace("https://kodik.info/", ""); + const type = urlStr.split("/")[0]; + const id = urlStr.split("/")[1]; + const hash = urlStr.split("/")[2]; const responseMan = await fetch( - `/api/proxy/${encodeURIComponent("https://kodik.info/ftor")}?isNotAnixart=true`, { + `/api/proxy/${encodeURIComponent("https://kodik.info/ftor")}?isNotAnixart=true`, + { method: "POST", headers: { "Content-Type": "application/json", @@ -86,35 +107,159 @@ export const ReleasePlayerCustom = (props: { id: number }) => { ref_sign: urlParams.ref_sign, bad_user: false, cdn_is_working: true, - info: {} + info: {}, }), } ); const dataMan = await responseMan.json(); let manifest = `https:${dataMan.links["360"][0].src.replace("360.mp4:hls:", "")}`; - return manifest; + let poster = `https:${dataMan.links["360"][0].src.replace("360.mp4:hls:manifest.m3u8", "thumb001.jpg")}`; + return { manifest, poster }; + }; + + const _fetchAnilibriaManifest = async (url: string) => { + const id = url.split("?id=")[1].split("&ep=")[0]; + + const response = await fetch(`https://api.anilibria.tv/v3/title?id=${id}`); + const data = await response.json(); + + const host = `https://${data.player.host}`; + const ep = data.player.list[episode.selected.position]; + + const blobTxt = `#EXTM3U\n${ep.hls.sd && `#EXT-X-STREAM-INF:RESOLUTION=854x480,BANDWIDTH=596000\n${host}${ep.hls.sd}\n`}${ep.hls.hd && `#EXT-X-STREAM-INF:RESOLUTION=1280x720,BANDWIDTH=1280000\n${host}${ep.hls.hd}\n`}${ep.hls.fhd && `#EXT-X-STREAM-INF:RESOLUTION=1920x1080,BANDWIDTH=2560000\n${host}${ep.hls.fhd}\n`}`; + const blob = new Blob([blobTxt], { type: "application/x-mpegURL" }); + + let file = new File([blobTxt], "manifest.m3u8", { + type: "application/x-mpegURL", + }); + let manifest = URL.createObjectURL(file); + let poster = `https://anixart.libria.fun${ep.preview}`; + return { manifest, poster }; }; useEffect(() => { const __getInfo = async () => { const vo = await _fetchVoiceover(props.id); - const src = await _fetchSource(props.id, vo.types[0].id); - const episodes = await _fetchEpisode( - props.id, - vo.types[0].id, - src.sources[0].id - ); - const manifest = await _fetchKodikManifest(episodes.episodes[0].url); - SetPlayerSrc(manifest); + setVoiceover({ + selected: vo.types[0], + available: vo.types, + }); }; __getInfo(); }, []); + useEffect(() => { + const __getInfo = async () => { + const src = await _fetchSource(props.id, voiceover.selected.id); + setSource({ + selected: src.sources[0], + available: src.sources, + }); + }; + if (voiceover.selected) { + __getInfo(); + } + }, [voiceover.selected]); + + useEffect(() => { + const __getInfo = async () => { + const episodes = await _fetchEpisode( + props.id, + voiceover.selected.id, + source.selected.id + ); + setEpisode({ + selected: episodes.episodes[0], + available: episodes.episodes, + }); + }; + if (source.selected) { + __getInfo(); + } + }, [source.selected]); + + useEffect(() => { + const __getInfo = async () => { + if (source.selected.name == "Kodik") { + const { manifest, poster } = await _fetchKodikManifest( + episode.selected.url + ); + SetPlayerProps({ + src: manifest, + poster: poster, + useCustom: true, + }); + return; + } + if (source.selected.name == "Libria") { + const { manifest, poster } = await _fetchAnilibriaManifest( + episode.selected.url + ); + SetPlayerProps({ + src: `${manifest}`, + poster: poster, + useCustom: true, + }); + return; + } + SetPlayerProps({ + src: episode.selected.url, + poster: null, + useCustom: false, + }); + }; + if (episode.selected) { + __getInfo(); + } + }, [episode.selected]); + return ( - {/* @ts-ignore */} - {!playerSrc ?

Loading...

: } -

ReleasePlayerCustom

+ {( + !voiceover.selected || + !source.selected || + !episode.selected || + !playerProps.src + ) ? +
+ +
+ :
+
+ + +
+ { + playerProps.useCustom ? + + + + // @ts-ignore + // + :