diff --git a/.env.example b/.env.example index b3184ba5..ac710883 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,6 @@ NEXTAUTH_URL="for development use http://localhost:3000/ and for production use ## NextJS PROXY_URI="This is what I use for proxying video https://github.com/chaycee/M3U8Proxy. Don't put / at the end of the url." API_URI="host your own API from this repo https://github.com/consumet/api.consumet.org. Don't put / at the end of the url." -API_KEY="this API key is used for schedules, anime and manga page. get the key from https://anify.tv/discord" DISQUS_SHORTNAME='put your disqus shortname here (optional)' # ADMIN_USERNAME="" diff --git a/.eslintrc.json b/.eslintrc.json index dbda85f6..4658cc5d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,6 +1,8 @@ { "extends": "next/core-web-vitals", + // ignore react-hooks/exhaustive-deps "rules": { + "react-hooks/exhaustive-deps": "off", "react/no-unescaped-entities": 0, "react/no-unknown-property": ["error", { "ignore": ["css"] }] } diff --git a/.gitignore b/.gitignore index 4d91deb6..1e8ff29a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ # testing /coverage -/pages/test.js +/pages/en/test.js /components/devComp # next.js diff --git a/.prettierrc.json b/.prettierrc.json index 0967ef42..08df606e 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1 +1,4 @@ -{} +{ + "bracketSpacing": true, + "printWidth": 80 +} diff --git a/README.md b/README.md index 6d3abd62..06b95b83 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,11 @@

Watch Page

- +

Normal Mode

+ +
+

Theater Mode

+

Manga Reader

@@ -54,13 +58,23 @@ ## Features -- Free ad-supported streaming service -- Anime tracking through Anilist API -- Skip OP/ED buttons -- Dub Anime support -- User-friendly interface -- Mobile-responsive design -- PWA supported +- General + - Free ad-supported streaming service + - Dub Anime support + - User-friendly interface + - Auto sync with AniList + - Add Anime/Manga to your AniList + - Scene Searching powered by [trace.moe](https://trace.moe) + - PWA supported + - Mobile responsive + - Fast page load +- Watch Page + - Player + - Autoplay next episode + - Skip op/ed button + - Theater mode + - Comment section +- Profile page to see your watch list ## To Do List @@ -116,9 +130,7 @@ NEXTAUTH_URL="for development use http://localhost:3000/ and for production use ## NextJS PROXY_URI="This is what I use for proxying video https://github.com/chaycee/M3U8Proxy. Don't put / at the end of the url." API_URI="host your own API from this repo https://github.com/consumet/api.consumet.org. Don't put / at the end of the url." -API_KEY="this API key is used for schedules, anime and manga page. get the key from https://anify.tv/discord" DISQUS_SHORTNAME='put your disqus shortname here (optional)' -# ADMIN_USERNAME="" ## Prisma DATABASE_URL="Your postgresql connection url" @@ -131,7 +143,7 @@ REDIS_URL="rediss://username:password@host:port" 5. Add this endpoint as Redirect Url on AniList Developer : ```bash -https://your-website-url/api/auth/callback/AniListProvider +https://your-website-domain/api/auth/callback/AniListProvider ``` 6. Start local server : diff --git a/components/admin/dashboard/index.js b/components/admin/dashboard/index.js index 64a1d6f7..d0c99632 100644 --- a/components/admin/dashboard/index.js +++ b/components/admin/dashboard/index.js @@ -1,4 +1,6 @@ -import React, { useState } from "react"; +import Link from "next/link"; +import React, { useEffect, useState } from "react"; +import { toast } from "sonner"; export default function AdminDashboard({ animeCount, @@ -10,13 +12,90 @@ export default function AdminDashboard({ const [selectedTime, setSelectedTime] = useState(""); const [unixTimestamp, setUnixTimestamp] = useState(null); - const handleSubmit = (e) => { + const [broadcast, setBroadcast] = useState(); + const [reportId, setReportId] = useState(); + + useEffect(() => { + async function getBroadcast() { + const res = await fetch("/api/v2/admin/broadcast", { + method: "GET", + headers: { + "Content-Type": "application/json", + "X-Broadcast-Key": "get-broadcast", + }, + }); + const data = await res.json(); + if (data) { + setBroadcast(data); + } + } + getBroadcast(); + }, []); + + const handleSubmit = async (e) => { e.preventDefault(); + let unixTime; + if (selectedTime) { - const unixTime = Math.floor(new Date(selectedTime).getTime() / 1000); + unixTime = Math.floor(new Date(selectedTime).getTime() / 1000); setUnixTimestamp(unixTime); } + + const res = await fetch("/api/v2/admin/broadcast", { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-Broadcast-Key": "get-broadcast", + }, + body: JSON.stringify({ + message, + startAt: unixTime, + show: true, + }), + }); + + const data = await res.json(); + + console.log({ message, unixTime, data }); + }; + + const handleRemove = async () => { + try { + const res = await fetch("/api/v2/admin/broadcast", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "X-Broadcast-Key": "get-broadcast", + }, + }); + const data = await res.json(); + console.log(data); + } catch (error) { + console.log(error); + } + }; + + const handleResolved = async () => { + try { + console.log(reportId); + if (!reportId) return toast.error("reportId is required"); + const res = await fetch("/api/v2/admin/bug-report", { + method: "DELETE", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + reportId, + }), + }); + const data = await res.json(); + if (res.status === 200) { + toast.success(data.message); + } + } catch (error) { + console.log(`error while resolving ${error}`); + } }; return (
@@ -39,7 +118,21 @@ export default function AdminDashboard({
-

Broadcast

+

+ Broadcast + + + + +

@@ -70,16 +163,24 @@ export default function AdminDashboard({ id="selectedTime" value={selectedTime} onChange={(e) => setSelectedTime(e.target.value)} - required className="w-full px-3 py-2 border rounded-md focus:outline-none text-black" />
- +
+ + +
{unixTimestamp && (

@@ -95,40 +196,85 @@ export default function AdminDashboard({ {report?.map((i, index) => (

- {i.desc}{" "} - {i.severity === "Low" && ( - - {/* */} - - - )} - {i.severity === "Medium" && ( - - {/* */} - - - )} - {i.severity === "High" && ( - - {/* */} - - - )} - {i.severity === "Critical" && ( - - - + + {i.desc}{" "} + + - )} + +
+ {i.severity === "Low" && ( + + {/* */} + + + )} + {i.severity === "Medium" && ( + + {/* */} + + + )} + {i.severity === "High" && ( + + {/* */} + + + )} + {i.severity === "Critical" && ( + + + + + )} + +
))}
-
a
); } diff --git a/components/admin/meta/AppendMeta.js b/components/admin/meta/AppendMeta.js index 1707ed23..e49fcad9 100644 --- a/components/admin/meta/AppendMeta.js +++ b/components/admin/meta/AppendMeta.js @@ -1,7 +1,7 @@ import Loading from "@/components/shared/loading"; import Image from "next/image"; import { useState } from "react"; -import { toast } from "react-toastify"; +import { toast } from "sonner"; // Define a function to convert the data function convertData(episodes) { @@ -217,6 +217,7 @@ export default function AppendMeta({ api }) {

query-image res.json()); - const getMap = response.find((i) => i?.map === true) || response[0]; + const getMap = response.find((i) => i?.map === true); let allProvider = response; if (getMap) { allProvider = response.filter((i) => { - if ( - i?.providerId === "gogoanime" && - i?.providerId === "9anime" && - i?.map !== true - ) { + if (i?.providerId === "gogoanime" && i?.map !== true) { return null; } return i; @@ -66,9 +62,12 @@ export default function AnimeEpisode({ fetchData(); return () => { + setCurrentPage(1); setProviders(null); setMapProviders(null); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [info.id, isDub]); const episodes = @@ -79,9 +78,7 @@ export default function AnimeEpisode({ const lastEpisodeIndex = currentPage * itemsPerPage; const firstEpisodeIndex = lastEpisodeIndex - itemsPerPage; let currentEpisodes = episodes.slice(firstEpisodeIndex, lastEpisodeIndex); - if (isDub) { - currentEpisodes = currentEpisodes.filter((i) => i.hasDub === true); - } + const totalPages = Math.ceil(episodes.length / itemsPerPage); const handleChange = (event) => { @@ -104,6 +101,7 @@ export default function AnimeEpisode({ ) { setView(3); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [providerId, episodes]); useEffect(() => { @@ -122,6 +120,7 @@ export default function AnimeEpisode({ setWatch(null); } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [episodes]); useEffect(() => { @@ -157,6 +156,7 @@ export default function AnimeEpisode({ return; } } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [providerId, artStorage, info.id, session?.user?.name]); let debounceTimeout; @@ -173,12 +173,7 @@ export default function AnimeEpisode({ ); if (!res.ok) { console.log(res); - toast.error("Something went wrong", { - position: "bottom-left", - autoClose: 3000, - hideProgressBar: true, - theme: "colored", - }); + toast.error("Something went wrong"); setProviders([]); setLoading(false); } else { @@ -213,12 +208,7 @@ export default function AnimeEpisode({ }, 1000); } catch (err) { console.log(err); - toast.error("Something went wrong", { - position: "bottom-left", - autoClose: 3000, - hideProgressBar: true, - theme: "colored", - }); + toast.error("Something went wrong"); } }; diff --git a/components/anime/mobile/topSection.js b/components/anime/mobile/topSection.js index 761a9fd0..e5f58da8 100644 --- a/components/anime/mobile/topSection.js +++ b/components/anime/mobile/topSection.js @@ -1,4 +1,9 @@ -import { PlayIcon, PlusIcon, ShareIcon } from "@heroicons/react/24/solid"; +import { + BookOpenIcon, + PlayIcon, + PlusIcon, + ShareIcon, +} from "@heroicons/react/24/solid"; import Image from "next/image"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; @@ -21,6 +26,8 @@ export default function DetailTop({ const [showAll, setShowAll] = useState(false); + const isAnime = info.type === "ANIME"; + useEffect(() => { setReadMore(false); }, [info.id]); @@ -29,7 +36,7 @@ export default function DetailTop({ try { if (navigator.share) { await navigator.share({ - title: `Watch Now - ${info?.title?.english}`, + title: `${isAnime ? "Watch" : "Read"} Now - ${info?.title?.english}`, // text: `Watch [${info?.title?.romaji}] and more on Moopa. Join us for endless anime entertainment"`, url: window.location.href, }); @@ -50,7 +57,7 @@ export default function DetailTop({
poster anime
-

- {info?.season?.toLowerCase()} {info.seasonYear} +

+ {info?.season?.toLowerCase() || getMonth(info?.startDate?.month)}{" "} + {info.seasonYear || info?.startDate?.year}

{info?.title?.romaji || info?.title?.english} @@ -87,12 +95,20 @@ export default function DetailTop({ onClick={() => router.push(watchUrl)} className={`${ !watchUrl ? "opacity-30 pointer-events-none" : "" - } w-[180px] flex-center text-lg font-karla font-semibold gap-1 border-black border-opacity-10 text-black rounded-full py-1 px-4 bg-white hover:opacity-80`} + } w-[180px] flex-center text-lg font-karla font-semibold gap-2 border-black border-opacity-10 text-black rounded-full py-1 px-4 bg-white hover:opacity-80`} > - + {isAnime ? ( + + ) : ( + + )} {progress > 0 ? ( statuses?.value === "COMPLETED" ? ( - "Rewatch" + isAnime ? ( + "Rewatch" + ) : ( + "Reread" + ) ) : !watchUrl && info?.nextAiringEpisode ? ( {convertSecondsToTime(info.nextAiringEpisode.timeUntilAiring)}{" "} @@ -100,8 +116,10 @@ export default function DetailTop({ ) : ( "Continue" ) - ) : ( + ) : isAnime ? ( "Watch Now" + ) : ( + "Read Now" )}
@@ -121,14 +139,14 @@ export default function DetailTop({ onClick={handleShareClick} > - Share Anime + Share {isAnime ? "Anime" : "Manga"} @@ -156,18 +174,24 @@ export default function DetailTop({ @@ -287,3 +313,11 @@ export default function DetailTop({
); } + +function getMonth(month) { + if (!month) return ""; + const formattedMonth = new Date(0, month).toLocaleString("default", { + month: "long", + }); + return formattedMonth; +} diff --git a/components/anime/viewMode/listMode.js b/components/anime/viewMode/listMode.js index 5beded1f..a6a1cf64 100644 --- a/components/anime/viewMode/listMode.js +++ b/components/anime/viewMode/listMode.js @@ -19,7 +19,7 @@ export default function ListMode({ href={`/en/anime/watch/${info.id}/${providerId}?id=${encodeURIComponent( episode.id )}&num=${episode.number}${dub ? `&dub=${dub}` : ""}`} - className={`flex gap-3 py-4 hover:bg-secondary/10 odd:bg-secondary/30 even:bg-primary`} + className={`flex gap-3 py-4 hover:bg-secondary odd:bg-secondary/30 even:bg-primary`} >
diff --git a/components/home/content.js b/components/home/content.js index 651d2762..678549c5 100644 --- a/components/home/content.js +++ b/components/home/content.js @@ -13,8 +13,8 @@ import { parseCookies } from "nookies"; import { ChevronLeftIcon } from "@heroicons/react/20/solid"; import { ExclamationCircleIcon, PlayIcon } from "@heroicons/react/24/solid"; import { useRouter } from "next/router"; -import { toast } from "react-toastify"; import HistoryOptions from "./content/historyOptions"; +import { toast } from "sonner"; export default function Content({ ids, @@ -24,6 +24,7 @@ export default function Content({ og, userName, setRemoved, + type = "anime", }) { const router = useRouter(); @@ -53,6 +54,7 @@ export default function Content({ } else if (lang === "id") { setLang("id"); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const [scrollLeft, setScrollLeft] = useState(false); @@ -174,14 +176,7 @@ export default function Content({ setRemoved(id || aniId); if (data?.message === "Episode deleted") { - toast.success("Episode removed from history", { - position: "bottom-right", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: true, - draggable: true, - theme: "dark", - }); + toast.success("Episode removed from history"); } } else { if (id) { @@ -259,7 +254,7 @@ export default function Content({ href={ ids === "listManga" ? `/en/manga/${anime.id}` - : `/${lang}/anime/${anime.id}` + : `/en/${type}/${anime.id}` } className="hover:scale-105 hover:shadow-lg duration-300 ease-out group relative" title={anime.title.romaji} @@ -352,7 +347,7 @@ export default function Content({ href={ ids === "listManga" ? `/en/manga/${anime.id}` - : `/en/anime/${anime.id}` + : `/en/${type.toLowerCase()}/${anime.id}` } className="w-[135px] lg:w-[185px] line-clamp-2" title={anime.title.romaji} diff --git a/components/home/genres.js b/components/home/genres.js index f054fc92..cd247ce1 100644 --- a/components/home/genres.js +++ b/components/home/genres.js @@ -47,6 +47,7 @@ export default function Genres() { } else if (lang === "id") { setLang("id"); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return (
diff --git a/components/home/schedule.js b/components/home/schedule.js index a0ab6914..bb35d088 100644 --- a/components/home/schedule.js +++ b/components/home/schedule.js @@ -48,21 +48,26 @@ export default function Schedule({ data, scheduleData, anime, update }) {

Don't miss out!

-
+
-
-

Coming Up Next!

-
+
+

+ Coming Up Next! +

+
{data.title.romaji || data.title.english || data.title.native}
-

+ {data.title.romaji || data.title.english || data.title.native} -

+
{data.bannerImage ? ( banner next anime )}
{/* Countdown Timer */} -
+
{/* Countdown Timer */}
{day} diff --git a/components/listEditor.js b/components/listEditor.js index fa249e3d..f4f46ea0 100644 --- a/components/listEditor.js +++ b/components/listEditor.js @@ -1,7 +1,7 @@ import { useState } from "react"; import Image from "next/image"; -import { toast } from "react-toastify"; import { useRouter } from "next/router"; +import { toast } from "sonner"; const ListEditor = ({ animeId, @@ -9,11 +9,12 @@ const ListEditor = ({ stats, prg, max, - image = null, + info = null, close, }) => { const [status, setStatus] = useState(stats ?? "CURRENT"); const [progress, setProgress] = useState(prg ?? 0); + const isAnime = info?.type === "ANIME"; const router = useRouter(); @@ -47,27 +48,11 @@ const ListEditor = ({ }); const { data } = await response.json(); if (data.SaveMediaListEntry === null) { - toast.error("Something went wrong", { - position: "bottom-right", - autoClose: 5000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - theme: "colored", - }); + toast.error("Something went wrong"); return; } console.log("Saved media list entry", data); - toast.success("Media list entry saved", { - position: "bottom-right", - autoClose: 5000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - theme: "dark", - }); + toast.success("Media list entry saved"); close(); setTimeout(() => { // window.location.reload(); @@ -75,15 +60,7 @@ const ListEditor = ({ }, 1000); // showAlert("Media list entry saved", "success"); } catch (error) { - toast.error("Something went wrong", { - position: "bottom-right", - autoClose: 5000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - theme: "colored", - }); + toast.error("Something went wrong"); console.error(error); } }; @@ -95,10 +72,10 @@ const ListEditor = ({
- {image && ( + {info?.bannerImage && (
image image setStatus(e.target.value)} className="rounded-sm px-2 py-1 bg-[#363642] w-[50%] sm:w-[150px] text-sm sm:text-base" > - + - +
diff --git a/components/manga/chapters.js b/components/manga/chapters.js index fd7beea3..2150686e 100644 --- a/components/manga/chapters.js +++ b/components/manga/chapters.js @@ -1,13 +1,16 @@ import Link from "next/link"; import { useState, useEffect } from "react"; -import { ChevronDownIcon } from "@heroicons/react/24/outline"; -import { setCookie } from "nookies"; +import { + ChevronDownIcon, + ChevronLeftIcon, + ChevronRightIcon, +} from "@heroicons/react/24/outline"; -const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { +const ChapterSelector = ({ chaptersData, data, setWatch, mangaId }) => { const [selectedProvider, setSelectedProvider] = useState( chaptersData[0]?.providerId || "" ); - const [selectedChapter, setSelectedChapter] = useState(""); + // const [selectedChapter, setSelectedChapter] = useState(""); const [chapters, setChapters] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [chaptersPerPage] = useState(10); @@ -16,13 +19,15 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { const selectedChapters = chaptersData.find( (c) => c.providerId === selectedProvider ); - if (selectedChapters) { - setSelectedChapter(selectedChapters); - setFirstEp(selectedChapters); - } setChapters(selectedChapters?.chapters || []); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedProvider, chaptersData]); + useEffect(() => { + setCurrentPage(1); + }, [data.id]); + // Get current posts const indexOfLastChapter = currentPage * chaptersPerPage; const indexOfFirstChapter = indexOfLastChapter - chaptersPerPage; @@ -31,24 +36,6 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { indexOfLastChapter ); - // Change page - const paginate = (pageNumber) => setCurrentPage(pageNumber); - const nextPage = () => setCurrentPage((prev) => prev + 1); - const prevPage = () => setCurrentPage((prev) => prev - 1); - - function saveManga() { - localStorage.setItem( - "manga", - JSON.stringify({ manga: selectedChapter, data: data }) - ); - setCookie(null, "manga", data.id, { - maxAge: 24 * 60 * 60, - path: "/", - }); - } - - // console.log(selectedChapter); - // Create page numbers const pageNumbers = []; for (let i = 1; i <= Math.ceil(chapters.length / chaptersPerPage); i++) { @@ -59,7 +46,7 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { const getDisplayedPageNumbers = (currentPage, totalPages, margin) => { const pageRange = [...Array(totalPages).keys()].map((i) => i + 1); - if (totalPages <= 10) { + if (totalPages <= 5) { return pageRange; } @@ -83,104 +70,147 @@ const ChapterSelector = ({ chaptersData, data, setFirstEp, userManga }) => { const displayedPageNumbers = getDisplayedPageNumbers( currentPage, pageNumbers.length, - 9 + 3 ); - // console.log(currentChapters); + useEffect(() => { + if (chapters) { + const getEpi = data?.nextAiringEpisode + ? chapters[data?.mediaListEntry?.progress] + : chapters[0]; + if (getEpi) { + const watchUrl = `/en/manga/read/${selectedProvider}?id=${mangaId}&chapterId=${encodeURIComponent( + getEpi.id + )}&anilist=${data.id}&num=${getEpi.number}`; + setWatch(watchUrl); + } else { + setWatch(null); + } + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chapters]); return ( -
-
- -
+
+
+

+ Chapters +

+
- +
-
- -
- {displayedPageNumbers.map((number, index) => - number === "..." ? ( - - ... - - ) : ( + +
+
+ {currentChapters.map((chapter, index) => { + const isRead = chapter.number <= data?.mediaListEntry?.progress; + return ( + 6 ? "" : `&anilist=${data.id}`}&num=${ + chapter.number + }`} + className={`flex gap-3 py-4 hover:bg-secondary odd:bg-secondary/30 even:bg-primary`} + > +
+ + {chapter.number} + +

+ {chapter.title || `Chapter ${chapter.number}`} +

+

+ {selectedProvider} +

+
+ + ); + })} +
+ +
+
+

+ Showing{" "} + {indexOfFirstChapter + 1} to{" "} + + {indexOfLastChapter > chapters.length + ? chapters.length + : indexOfLastChapter} + {" "} + of {chapters.length} chapters +

+
+
+
- -
-
- {currentChapters.map((chapter, index) => { - const isRead = chapter.number <= userManga?.progress; - return ( -
- + {displayedPageNumbers.map((pageNumber, index) => ( + + ))} +
+
- ); - })} + Next +
+
); diff --git a/components/manga/info/mobile/mobileButton.js b/components/manga/info/mobile/mobileButton.js deleted file mode 100644 index 0016b595..00000000 --- a/components/manga/info/mobile/mobileButton.js +++ /dev/null @@ -1,39 +0,0 @@ -import Link from "next/link"; -import AniList from "../../../media/aniList"; -import { BookOpenIcon } from "@heroicons/react/24/outline"; - -export default function MobileButton({ info, firstEp, saveManga }) { - return ( -
- - -
- -
- -
- ); -} diff --git a/components/manga/info/mobile/topMobile.js b/components/manga/info/mobile/topMobile.js deleted file mode 100644 index 2e6b23ac..00000000 --- a/components/manga/info/mobile/topMobile.js +++ /dev/null @@ -1,16 +0,0 @@ -import Image from "next/image"; - -export default function TopMobile({ info }) { - return ( -
- cover image -
-
- ); -} diff --git a/components/manga/info/topSection.js b/components/manga/info/topSection.js deleted file mode 100644 index 45d5f11b..00000000 --- a/components/manga/info/topSection.js +++ /dev/null @@ -1,107 +0,0 @@ -import Image from "next/image"; -import { BookOpenIcon } from "@heroicons/react/24/outline"; -import AniList from "../../media/aniList"; -import Link from "next/link"; -import TopMobile from "./mobile/topMobile"; -import MobileButton from "./mobile/mobileButton"; - -export default function TopSection({ info, firstEp, setCookie }) { - const slicedGenre = info.genres?.slice(0, 3); - - function saveManga() { - localStorage.setItem( - "manga", - JSON.stringify({ manga: firstEp, data: info }) - ); - - setCookie(null, "manga", info.id, { - maxAge: 24 * 60 * 60, - path: "/", - }); - } - - return ( -
- -
- cover image - -
- - -
- -
- -
-
-
-
-

- {info.title?.romaji || info.title?.english || info.title?.native} -

- - {slicedGenre && - slicedGenre.map((genre, index) => { - return ( -
- {genre} - {index < slicedGenre?.length - 1 && ( - - )} -
- ); - })} -
-
- - - -
- {/* Description */} -
-

-

-
-
-
-
- ); -} diff --git a/components/manga/leftBar.js b/components/manga/leftBar.js index 17acd55c..5a98115c 100644 --- a/components/manga/leftBar.js +++ b/components/manga/leftBar.js @@ -1,14 +1,23 @@ +import { getHeaders, getRandomId } from "@/utils/imageUtils"; import { ArrowLeftIcon } from "@heroicons/react/24/solid"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -export function LeftBar({ data, page, info, currentId, setSeekPage }) { +export function LeftBar({ + data, + page, + info, + currentId, + setSeekPage, + number, + mediaId, + providerId, +}) { const router = useRouter(); function goBack() { router.push(`/en/manga/${info.id}`); } - // console.log(info); return (
@@ -37,23 +46,27 @@ export function LeftBar({ data, page, info, currentId, setSeekPage }) {

Chapters

- {data?.chapters?.map((x) => { + {data?.chapters?.map((x, index) => { return (
6 ? "" : `&anilist=${info?.id}` + }&num=${x.number}`} className="" >

- {x.number}.{" "} - {x.title} + + {x.number || index + 1}. + {" "} + {x.title || `Chapter ${x.number || index + 1}`}

@@ -69,28 +82,37 @@ export function LeftBar({ data, page, info, currentId, setSeekPage }) {
{Array.isArray(page) ? (
- {page?.map((x) => { + {page?.map((x, index) => { return (
setSeekPage(x.index)} + onClick={() => setSeekPage(index)} > chapter image -

Page {x.index + 1}

+

Page {index + 1}

); @@ -98,7 +120,7 @@ export function LeftBar({ data, page, info, currentId, setSeekPage }) {
) : (
-

{page.error || "No Pages."}

+

{page?.error || "No Pages."}

)}
diff --git a/components/manga/mobile/bottomBar.js b/components/manga/mobile/bottomBar.js index 6493dcac..5b28de4b 100644 --- a/components/manga/mobile/bottomBar.js +++ b/components/manga/mobile/bottomBar.js @@ -1,3 +1,4 @@ +import { getHeaders } from "@/utils/imageUtils"; import { ChevronLeftIcon, ChevronRightIcon, @@ -14,12 +15,15 @@ export default function BottomBar({ nextChapter, currentPage, chapter, - page, + data, setSeekPage, setIsOpen, + number, + mangadexId, }) { const [openPage, setOpenPage] = useState(false); const router = useRouter(); + return (
6 ? "" : `&anilist=${id}`}&num=${prevChapter.number}` ) } > @@ -56,7 +62,9 @@ export default function BottomBar({ router.push( `/en/manga/read/${ chapter.providerId - }?id=${id}&chapterId=${encodeURIComponent(nextChapter)}` + }?id=${mangadexId}&chapterId=${encodeURIComponent( + nextChapter.id + )}${id > 6 ? "" : `&anilist=${id}`}&num=${nextChapter.number}` ) } > @@ -82,13 +90,14 @@ export default function BottomBar({
- {`${currentPage}/${page.length}`} + {`${currentPage}/${data?.length}`}
{openPage && (
- {Array.isArray(page) ? ( - page.map((x) => { + {Array.isArray(data) ? ( + data.map((x, index) => { + const indx = index + 1; return (
{ - setIsVisible(true); - setFade(true); - }; - - const handleHideClick = () => { - setIsVisible(false); - setFade(false); - }; - - useEffect(() => { - let lang = null; - if (!cookie) { - const cookie = parseCookies(); - lang = cookie.lang || null; - setCookies(cookie); - } - if (lang === "en" || lang === null) { - setLang("en"); - } else if (lang === "id") { - setLang("id"); - } - }, []); - return ( - <> - {!isVisible && ( - - )} - - {/* Mobile Menu */} -
- {isVisible && session && ( - - user avatar - - )} - {isVisible && ( -
-
- - - - {session ? ( - - ) : ( - - )} -
- -
- )} -
- - ); -} diff --git a/components/manga/panels/firstPanel.js b/components/manga/panels/firstPanel.js index f1ee859f..596fa581 100644 --- a/components/manga/panels/firstPanel.js +++ b/components/manga/panels/firstPanel.js @@ -4,10 +4,13 @@ import { ArrowsPointingInIcon, ChevronLeftIcon, ChevronRightIcon, + PlusIcon, + MinusIcon, } from "@heroicons/react/24/outline"; import Image from "next/image"; import { useRouter } from "next/router"; import { useAniList } from "../../../lib/anilist/useAnilist"; +import { getHeaders, getRandomId } from "@/utils/imageUtils"; export default function FirstPanel({ aniId, @@ -26,14 +29,20 @@ export default function FirstPanel({ mobileVisible, setMobileVisible, setCurrentPage, + number, + mangadexId, }) { const { markProgress } = useAniList(session); const [currentImageIndex, setCurrentImageIndex] = useState(0); const imageRefs = useRef([]); const scrollContainerRef = useRef(); + const [imageQuality, setImageQuality] = useState(80); + const router = useRouter(); + // console.log({ chapter }); + useEffect(() => { const handleScroll = () => { const scrollTop = scrollContainerRef.current.scrollTop; @@ -53,13 +62,17 @@ export default function FirstPanel({ } } - if (index === data.length - 3 && !hasRun.current) { + if (index === data?.length - 3 && !hasRun.current) { if (session) { + if (aniId?.length > 6) return; const currentChapter = chapter.chapters?.find( (x) => x.id === currentId ); if (currentChapter) { - markProgress(aniId, currentChapter.number); + const chapterNumber = + currentChapter.number ?? + chapter.chapters.indexOf(currentChapter) + 1; + markProgress(aniId, chapterNumber); console.log("marking progress"); } } @@ -82,8 +95,12 @@ export default function FirstPanel({ }); } }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, session, chapter]); + // console.log({ imageQuality }); + useEffect(() => { if (scrollContainerRef.current && seekPage !== currentImageIndex) { const targetImageRef = imageRefs.current[seekPage]; @@ -119,19 +136,26 @@ export default function FirstPanel({ {data && Array.isArray(data) && data?.length > 0 ? ( data.map((i, index) => (
(imageRefs.current[index] = el)} > {i.index} setMobileVisible(!mobileVisible)} className="w-screen lg:w-full h-auto bg-[#bbb]" /> @@ -145,6 +169,26 @@ export default function FirstPanel({ )}
+ {/* + */} {visible ? (
{`Page ${ currentImageIndex + 1 - }/${data.length}`} + }/${data?.length}`} ); } diff --git a/components/manga/panels/secondPanel.js b/components/manga/panels/secondPanel.js index 93238226..fa158b26 100644 --- a/components/manga/panels/secondPanel.js +++ b/components/manga/panels/secondPanel.js @@ -5,9 +5,11 @@ import { ArrowsPointingInIcon, } from "@heroicons/react/24/outline"; import { useAniList } from "../../../lib/anilist/useAnilist"; +import { getHeaders } from "@/utils/imageUtils"; export default function SecondPanel({ aniId, + chapterData, data, hasRun, currentChapter, @@ -17,6 +19,7 @@ export default function SecondPanel({ visible, setVisible, session, + providerId, }) { const [index, setIndex] = useState(0); const [image, setImage] = useState(null); @@ -26,6 +29,7 @@ export default function SecondPanel({ useEffect(() => { setIndex(0); setSeekPage(0); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, currentId]); const seekToIndex = (newIndex) => { @@ -41,6 +45,7 @@ export default function SecondPanel({ useEffect(() => { seekToIndex(seekPage); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [seekPage]); useEffect(() => { @@ -63,13 +68,14 @@ export default function SecondPanel({ } if (index + 1 >= image.length - 4 && !hasRun.current) { - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); - } + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; - markProgress(aniId, chapterNumber); + if (chapterNumber) { + markProgress(aniId, chapterNumber); + } hasRun.current = true; } } @@ -80,6 +86,7 @@ export default function SecondPanel({ return () => { window.removeEventListener("keydown", handleKeyDown); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [index, image]); const handleNext = () => { @@ -90,10 +97,13 @@ export default function SecondPanel({ if (index + 1 >= image.length - 4 && !hasRun.current) { console.log("marking progress"); - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; + + if (chapterNumber) { + markProgress(aniId, chapterNumber); } markProgress(aniId, chapterNumber); @@ -107,6 +117,7 @@ export default function SecondPanel({ setSeekPage(index - 2); } }; + return (
@@ -127,11 +138,17 @@ export default function SecondPanel({ className="w-1/2 h-screen object-contain" src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( image[image.length - index - 2]?.url - )}&headers=${encodeURIComponent( - JSON.stringify({ - Referer: image[image.length - index - 2]?.headers.Referer, - }) - )}`} + )}${ + image[image.length - index - 2]?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify( + image[image.length - index - 2]?.headers + ) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} alt="Manga Page" /> )} @@ -142,11 +159,15 @@ export default function SecondPanel({ className="w-1/2 h-screen object-contain" src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( image[image.length - index - 1]?.url - )}&headers=${encodeURIComponent( - JSON.stringify({ - Referer: image[image.length - index - 1]?.headers.Referer, - }) - )}`} + )}${ + image[image.length - index - 1]?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(image[image.length - index - 1]?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} alt="Manga Page" />
diff --git a/components/manga/panels/thirdPanel.js b/components/manga/panels/thirdPanel.js index d402f073..f13b49db 100644 --- a/components/manga/panels/thirdPanel.js +++ b/components/manga/panels/thirdPanel.js @@ -5,10 +5,12 @@ import { ArrowsPointingInIcon, } from "@heroicons/react/24/outline"; import { useAniList } from "../../../lib/anilist/useAnilist"; +import { getHeaders } from "@/utils/imageUtils"; export default function ThirdPanel({ aniId, data, + chapterData, hasRun, currentId, currentChapter, @@ -20,6 +22,7 @@ export default function ThirdPanel({ scaleImg, setMobileVisible, mobileVisible, + providerId, }) { const [index, setIndex] = useState(0); const [image, setImage] = useState(null); @@ -28,6 +31,7 @@ export default function ThirdPanel({ useEffect(() => { setIndex(0); setSeekPage(0); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [data, currentId]); const seekToIndex = (newIndex) => { @@ -39,6 +43,7 @@ export default function ThirdPanel({ useEffect(() => { seekToIndex(seekPage); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [seekPage]); useEffect(() => { @@ -60,13 +65,14 @@ export default function ThirdPanel({ setSeekPage(index + 1); } if (index + 1 >= image.length - 2 && !hasRun.current) { - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); - } + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; - markProgress(aniId, chapterNumber); + if (chapterNumber) { + markProgress(aniId, chapterNumber); + } hasRun.current = true; } } @@ -77,6 +83,8 @@ export default function ThirdPanel({ return () => { window.removeEventListener("keydown", handleKeyDown); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [index, image]); const handleNext = () => { @@ -85,13 +93,15 @@ export default function ThirdPanel({ setSeekPage(index + 1); } if (index + 1 >= image.length - 2 && !hasRun.current) { - let chapterNumber = currentChapter?.number; - if (chapterNumber % 1 !== 0) { - // If it's a decimal, round it - chapterNumber = Math.round(chapterNumber); + const current = chapterData.chapters?.find( + (x) => x.id === currentChapter.id + ); + const chapterNumber = chapterData.chapters.indexOf(current) + 1; + + if (chapterNumber) { + markProgress(aniId, chapterNumber); } - markProgress(aniId, chapterNumber); hasRun.current = true; } }; @@ -119,11 +129,15 @@ export default function ThirdPanel({ onClick={() => setMobileVisible(!mobileVisible)} src={`https://api.consumet.org/utils/image-proxy?url=${encodeURIComponent( image[image.length - index - 1]?.url - )}&headers=${encodeURIComponent( - JSON.stringify({ - Referer: image[image.length - index - 1]?.headers.Referer, - }) - )}`} + )}${ + image[image.length - index - 1]?.headers?.Referer + ? `&headers=${encodeURIComponent( + JSON.stringify(image[image.length - index - 1]?.headers) + )}` + : `&headers=${encodeURIComponent( + JSON.stringify(getHeaders(providerId)) + )}` + }`} alt="Manga Page" style={{ transform: `scale(${scaleImg})`, diff --git a/components/manga/rightBar.js b/components/manga/rightBar.js index 82d577d8..9672fc43 100644 --- a/components/manga/rightBar.js +++ b/components/manga/rightBar.js @@ -4,16 +4,15 @@ import { } from "@heroicons/react/24/outline"; import { useEffect, useState } from "react"; import { useAniList } from "../../lib/anilist/useAnilist"; -import { toast } from "react-toastify"; import AniList from "../media/aniList"; import { signIn } from "next-auth/react"; +import { toast } from "sonner"; export default function RightBar({ id, hasRun, session, data, - error, currentChapter, paddingX, setPaddingX, @@ -47,19 +46,13 @@ export default function RightBar({ markProgress(id, progress, status, volumeProgress); hasRun.current = true; } else { - toast.error("Progress must be a whole number!", { - position: "bottom-right", - autoClose: 5000, - hideProgressBar: true, - closeOnClick: false, - pauseOnHover: true, - draggable: true, - theme: "colored", - }); + toast.error("Progress must be a whole number!"); } } }; + // console.log({ id }); + const changeMode = (e) => { setLayout(Number(e.target.value)); // console.log(e.target.value); @@ -129,63 +122,72 @@ export default function RightBar({
+ {/*
+

Set Quality

+
*/}

Tracking

{session ? ( -
-
- -
- - + id?.length > 6 ? ( +

+ Not available on AniList +

+ ) : ( +
+
+ +
+ + +
+
+ + setProgress(e.target.value)} + className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" + /> +
+
+ + setVolumeProgress(e.target.value)} + className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" + /> +
+
-
- - setProgress(e.target.value)} - className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" - /> -
-
- - setVolumeProgress(e.target.value)} - className="w-full px-2 py-1 rounded-md bg-[#161617] text-sm" - /> -
- -
+ ) ) : (
-
+
- + {type.toLowerCase()}
+
setQuery(event.target.value)} diff --git a/components/secret.js b/components/secret.js new file mode 100644 index 00000000..782fcf50 --- /dev/null +++ b/components/secret.js @@ -0,0 +1,36 @@ +import { useEffect, useState } from "react"; + +export default function SecretPage({ cheatCode, onCheatCodeEntered }) { + const [typedCode, setTypedCode] = useState(""); + const [timer, setTimer] = useState(null); + + const handleKeyPress = (e) => { + const newTypedCode = typedCode + e.key; + + if (newTypedCode === cheatCode) { + onCheatCodeEntered(); + setTypedCode(""); + } else { + setTypedCode(newTypedCode); + + // Reset the timer if the user stops typing for 2 seconds + clearTimeout(timer); + const newTimer = setTimeout(() => { + setTypedCode(""); + }, 2000); + setTimer(newTimer); + } + }; + + useEffect(() => { + window.addEventListener("keydown", handleKeyPress); + + return () => { + window.removeEventListener("keydown", handleKeyPress); + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [typedCode]); + + return; +} diff --git a/components/shared/NavBar.js b/components/shared/NavBar.js index 7bbd6171..034a06b4 100644 --- a/components/shared/NavBar.js +++ b/components/shared/NavBar.js @@ -56,7 +56,7 @@ export function NewNavbar({ shrink ? "py-1" : `${paddingY}` }` : `${paddingY}` - } transition-all duration-200 ease-linear`} + } transition-all duration-200 ease-linear`} >
+ = scrollP + 80 @@ -196,7 +197,7 @@ export function NewNavbar({ // title={sessions ? "Go to Profile" : "Login With AniList"} > */} {session ? ( -
+
diff --git a/components/shared/bugReport.js b/components/shared/bugReport.js index 9b990168..f6bd9f14 100644 --- a/components/shared/bugReport.js +++ b/components/shared/bugReport.js @@ -1,7 +1,7 @@ import { Fragment, useState } from "react"; import { Dialog, Listbox, Transition } from "@headlessui/react"; import { CheckIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; -import { toast } from "react-toastify"; +import { toast } from "sonner"; const severityOptions = [ { id: 1, name: "Low" }, @@ -42,17 +42,11 @@ const BugReportForm = ({ isOpen, setIsOpen }) => { }); const json = await res.json(); - toast.success(json.message, { - hideProgressBar: true, - theme: "colored", - }); + toast.success(json.message); closeModal(); } catch (err) { console.log(err); - toast.error("Something went wrong: " + err.message, { - hideProgressBar: true, - theme: "colored", - }); + toast.error("Something went wrong: " + err.message); } }; diff --git a/components/shared/footer.js b/components/shared/footer.js index 91af5a8a..0e19f130 100644 --- a/components/shared/footer.js +++ b/components/shared/footer.js @@ -28,6 +28,8 @@ function Footer() { setLang("id"); setChecked(true); } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function switchLang() { diff --git a/components/watch/player/artplayer.js b/components/watch/player/artplayer.js index 4ae8aa1c..666c1035 100644 --- a/components/watch/player/artplayer.js +++ b/components/watch/player/artplayer.js @@ -46,7 +46,7 @@ export default function NewPlayer({ customType: { m3u8: playM3u8, }, - ...(provider === "zoro" && { + ...(subtitles?.length > 0 && { subtitle: { url: `${defSub}`, // type: "vtt", @@ -131,7 +131,7 @@ export default function NewPlayer({ return item.html; }, }, - provider === "zoro" && { + subtitles?.length > 0 && { html: "Subtitles", icon: '', width: 300, @@ -261,7 +261,7 @@ export default function NewPlayer({ index: 11, position: "right", tooltip: "Theater (t)", - html: '

', + html: '', click: function (...args) { setPlayerState((prev) => ({ ...prev, @@ -379,6 +379,8 @@ export default function NewPlayer({ art.destroy(false); } }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return
; diff --git a/components/watch/player/component/controls/subtitle.js b/components/watch/player/component/controls/subtitle.js deleted file mode 100644 index 02075f7d..00000000 --- a/components/watch/player/component/controls/subtitle.js +++ /dev/null @@ -1,3 +0,0 @@ -import { useState } from "react"; - -export default function getSubtitles() {} diff --git a/components/watch/player/playerComponent.js b/components/watch/player/playerComponent.js index 37c58101..665919ba 100644 --- a/components/watch/player/playerComponent.js +++ b/components/watch/player/playerComponent.js @@ -4,6 +4,7 @@ import { icons } from "./component/overlay"; import { useWatchProvider } from "@/lib/context/watchPageProvider"; import { useRouter } from "next/router"; import { useAniList } from "@/lib/anilist/useAnilist"; +import Loading from "@/components/shared/loading"; export function calculateAspectRatio(width, height) { const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b)); @@ -74,20 +75,18 @@ export default function PlayerComponent({ setResolution(resol); } - if (provider === "zoro") { - const size = fontSize.map((i) => { - const isDefault = !sub ? i.html === "Small" : i.html === sub?.html; - return { - ...(isDefault && { default: true }), - html: i.html, - size: i.size, - }; - }); + const size = fontSize.map((i) => { + const isDefault = !sub ? i.html === "Small" : i.html === sub?.html; + return { + ...(isDefault && { default: true }), + html: i.html, + size: i.size, + }; + }); - const defSize = size?.find((i) => i?.default === true); - setDefSize(defSize); - setSubSize(size); - } + const defSize = size?.find((i) => i?.default === true); + setDefSize(defSize); + setSubSize(size); async function compiler() { try { @@ -114,19 +113,26 @@ export default function PlayerComponent({ setUrl(defSource.url); } - if (provider === "zoro") { - const subtitle = data?.subtitles - .filter((subtitle) => subtitle.lang !== "Thumbnails") - .map((subtitle) => { - const isEnglish = subtitle.lang === "English"; - return { - ...(isEnglish && { default: true }), - url: subtitle.url, - html: `${subtitle.lang}`, - }; - }); + const subtitle = data?.subtitles + ?.filter( + (subtitle) => + subtitle.lang !== "Thumbnails" && subtitle.lang !== "thumbnails" + ) + ?.map((subtitle) => { + const isEnglish = + subtitle.lang === "English" || + subtitle.lang === "English / English (US)"; + return { + ...(isEnglish && { default: true }), + url: subtitle.url, + html: `${subtitle.lang}`, + }; + }); - const defSub = data?.subtitles.find((i) => i.lang === "English"); + if (subtitle) { + const defSub = data?.subtitles.find( + (i) => i.lang === "English" || i.lang === "English / English (US)" + ); setDefSub(defSub?.url); @@ -162,6 +168,8 @@ export default function PlayerComponent({ setSubtitle([]); setLoading(true); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [provider, data]); /** @@ -171,6 +179,17 @@ export default function PlayerComponent({ art.on("ready", () => { const autoplay = localStorage.getItem("autoplay_video") || false; + // check media queries for mobile devices + const isMobile = window.matchMedia("(max-width: 768px)").matches; + + // console.log(art.fullscreen); + + if (isMobile) { + art.controls.remove("theater-button"); + // art.controls.remove("fast-rewind"); + // art.controls.remove("fast-forward"); + } + if (autoplay === "true" || autoplay === true) { if (playerState.currentTime === 0) { art.play(); @@ -465,10 +484,13 @@ export default function PlayerComponent({ style={{ aspectRatio: aspectRatio }} >
+ {!data?.error && !url && ( +
+ +
+ )} {!error ? ( - !loading && - track && - url && ( + !loading && track && url && !data?.error ? ( + ) : ( +

+ {data?.status === 404 && "Not Found"} +
+ {data?.error} +

) ) : (

diff --git a/components/watch/player/utils/getZoroSource.js b/components/watch/player/utils/getZoroSource.js deleted file mode 100644 index e69de29b..00000000 diff --git a/components/watch/secondary/episodeLists.js b/components/watch/secondary/episodeLists.js index 41f1a765..485b43ee 100644 --- a/components/watch/secondary/episodeLists.js +++ b/components/watch/secondary/episodeLists.js @@ -1,6 +1,8 @@ import Skeleton from "react-loading-skeleton"; import Image from "next/image"; import Link from "next/link"; +import { ChevronDownIcon } from "@heroicons/react/24/outline"; +import { useRouter } from "next/router"; export default function EpisodeLists({ info, @@ -9,13 +11,56 @@ export default function EpisodeLists({ watchId, episode, artStorage, + track, dub, }) { const progress = info.mediaListEntry?.progress; + const router = useRouter(); + return (

-

Up Next

+
+ + {episode && ( +
+ + +
+ )} +
{episode && episode.length > 0 ? ( map?.some( diff --git a/lib/Artplayer.js b/lib/Artplayer.js deleted file mode 100644 index 48da24d0..00000000 --- a/lib/Artplayer.js +++ /dev/null @@ -1,290 +0,0 @@ -import { useEffect, useRef } from "react"; -import Artplayer from "artplayer"; -import Hls from "hls.js"; -import { useRouter } from "next/router"; - -export default function Player({ - option, - res, - quality, - subSize, - subtitles, - provider, - getInstance, - id, - track, - // socket - // isPlay, - // watchdata, - // room, - autoplay, - setautoplay, - ...rest -}) { - const artRef = useRef(); - - const router = useRouter(); - - function playM3u8(video, url, art) { - if (Hls.isSupported()) { - if (art.hls) art.hls.destroy(); - const hls = new Hls(); - hls.loadSource(url); - hls.attachMedia(video); - art.hls = hls; - art.on("destroy", () => hls.destroy()); - } else if (video.canPlayType("application/vnd.apple.mpegurl")) { - video.src = url; - } else { - art.notice.show = "Unsupported playback format: m3u8"; - } - } - - useEffect(() => { - const art = new Artplayer({ - ...option, - container: artRef.current, - type: "m3u8", - customType: { - m3u8: playM3u8, - }, - fullscreen: true, - hotkey: true, - lock: true, - setting: true, - playbackRate: true, - autoOrientation: true, - pip: true, - theme: "#f97316", - controls: [ - { - index: 10, - name: "fast-rewind", - position: "left", - html: '', - tooltip: "Backward 5s", - click: function () { - art.backward = 5; - }, - }, - { - index: 11, - name: "fast-forward", - position: "left", - html: '', - tooltip: "Forward 5s", - click: function () { - art.forward = 5; - }, - }, - ], - settings: [ - { - html: "Autoplay Next", - // icon: '', - tooltip: "ON/OFF", - switch: localStorage.getItem("autoplay") === "true" ? true : false, - onSwitch: function (item) { - setautoplay(!item.switch); - localStorage.setItem("autoplay", !item.switch); - return !item.switch; - }, - }, - provider === "zoro" && { - html: "Subtitles", - icon: '', - width: 300, - tooltip: "Settings", - selector: [ - { - html: "Display", - icon: '', - tooltip: "Show", - switch: true, - onSwitch: function (item) { - item.tooltip = item.switch ? "Hide" : "Show"; - art.subtitle.show = !item.switch; - return !item.switch; - }, - }, - { - html: "Font Size", - icon: '', - selector: subSize, - onSelect: function (item) { - if (item.html === "Small") { - art.subtitle.style({ fontSize: "16px" }); - localStorage.setItem( - "subSize", - JSON.stringify({ - size: "16px", - html: "Small", - }) - ); - } else if (item.html === "Medium") { - art.subtitle.style({ fontSize: "36px" }); - localStorage.setItem( - "subSize", - JSON.stringify({ - size: "36px", - html: "Medium", - }) - ); - } else if (item.html === "Large") { - art.subtitle.style({ fontSize: "56px" }); - localStorage.setItem( - "subSize", - JSON.stringify({ - size: "56px", - html: "Large", - }) - ); - } - }, - }, - { - html: "Language", - icon: '', - tooltip: "English", - selector: [...subtitles], - onSelect: function (item) { - art.subtitle.switch(item.url, { - name: item.html, - }); - return item.html; - }, - }, - { - html: "Font Family", - tooltip: localStorage.getItem("font") - ? localStorage.getItem("font") - : "Arial", - selector: [ - { html: "Arial" }, - { html: "Comic Sans MS" }, - { html: "Verdana" }, - { html: "Tahoma" }, - { html: "Trebuchet MS" }, - { html: "Times New Roman" }, - { html: "Georgia" }, - { html: "Impact " }, - { html: "Andalé Mono" }, - { html: "Palatino" }, - { html: "Baskerville" }, - { html: "Garamond" }, - { html: "Courier New" }, - { html: "Brush Script MT" }, - ], - onSelect: function (item) { - art.subtitle.style({ fontFamily: item.html }); - localStorage.setItem("font", item.html); - return item.html; - }, - }, - { - html: "Font Shadow", - tooltip: localStorage.getItem("subShadow") - ? JSON.parse(localStorage.getItem("subShadow")).shadow - : "Default", - selector: [ - { html: "None", value: "none" }, - { - html: "Uniform", - value: - "2px 2px 0px #000, -2px -2px 0px #000, 2px -2px 0px #000, -2px 2px 0px #000", - }, - { html: "Raised", value: "-1px 2px 3px rgba(0, 0, 0, 1)" }, - { html: "Depressed", value: "-2px -3px 3px rgba(0, 0, 0, 1)" }, - { html: "Glow", value: "0 0 10px rgba(0, 0, 0, 0.8)" }, - { - html: "Block", - value: - "-3px 3px 4px rgba(0, 0, 0, 1),2px 2px 4px rgba(0, 0, 0, 1),1px -1px 3px rgba(0, 0, 0, 1),-3px -2px 4px rgba(0, 0, 0, 1)", - }, - ], - onSelect: function (item) { - art.subtitle.style({ textShadow: item.value }); - localStorage.setItem( - "subShadow", - JSON.stringify({ shadow: item.html, value: item.value }) - ); - return item.html; - }, - }, - ], - }, - provider === "gogoanime" && { - html: "Quality", - width: 150, - tooltip: `${res}`, - selector: quality, - onSelect: function (item) { - art.switchQuality(item.url, item.html); - localStorage.setItem("quality", item.html); - return item.html; - }, - }, - ].filter(Boolean), - }); - - if ("mediaSession" in navigator) { - art.on("video:timeupdate", () => { - const session = navigator.mediaSession; - if (!session) return; - session.setPositionState({ - duration: art.duration, - playbackRate: art.playbackRate, - position: art.currentTime, - }); - }); - - navigator.mediaSession.setActionHandler("play", () => { - art.play(); - }); - - navigator.mediaSession.setActionHandler("pause", () => { - art.pause(); - }); - - navigator.mediaSession.setActionHandler("previoustrack", () => { - if (track?.prev) { - router.push( - `/en/anime/watch/${id}/${provider}?id=${encodeURIComponent( - track?.prev?.id - )}&num=${track?.prev?.number}` - ); - } - }); - - navigator.mediaSession.setActionHandler("nexttrack", () => { - if (track?.next) { - router.push( - `/en/anime/watch/${id}/${provider}?id=${encodeURIComponent( - track?.next?.id - )}&num=${track?.next?.number}` - ); - } - }); - } - - art.events.proxy(document, "keydown", (event) => { - if (event.key === "f" || event.key === "F") { - art.fullscreen = !art.fullscreen; - } - }); - - // artInstanceRef.current = art; - - if (getInstance && typeof getInstance === "function") { - getInstance(art); - } - - return () => { - if (art && art.destroy) { - art.destroy(false); - } - }; - }, []); - - return
; -} diff --git a/lib/anify/getMangaId.js b/lib/anify/getMangaId.js new file mode 100644 index 00000000..e18da65a --- /dev/null +++ b/lib/anify/getMangaId.js @@ -0,0 +1,40 @@ +import axios from "axios"; + +export async function fetchInfo(romaji, english, native) { + try { + const { data: getManga } = await axios.get( + `https://api.anify.tv/search-advanced?query=${ + english || romaji + }&type=manga` + ); + + const findManga = getManga.find( + (manga) => + manga.title.romaji === romaji || + manga.title.english === english || + manga.title.native === native + ); + + if (!findManga) { + return null; + } + + return { id: findManga.id }; + } catch (error) { + console.error("Error fetching data:", error); + return null; + } +} + +export default async function getMangaId(romaji, english, native) { + try { + const data = await fetchInfo(romaji, english, native); + if (data) { + return data; + } else { + return { message: "Schedule not found" }; + } + } catch (error) { + return { error }; + } +} diff --git a/lib/anify/page.js b/lib/anify/page.js index 65ed309f..0f0bb938 100644 --- a/lib/anify/page.js +++ b/lib/anify/page.js @@ -1,10 +1,10 @@ import { redis } from "../redis"; // Function to fetch new data -async function fetchData(id, providerId, chapterId, key) { +async function fetchData(id, chapterNumber, providerId, chapterId, key) { try { const res = await fetch( - `https://api.anify.tv/pages?id=${id}&providerId=${providerId}&readId=${chapterId}&apikey=${key}` + `https://api.anify.tv/pages/${id}/${chapterNumber}/${providerId}/${chapterId}&apikey=${key}` ); const data = await res.json(); return data; @@ -16,6 +16,7 @@ async function fetchData(id, providerId, chapterId, key) { export default async function getAnifyPage( mediaId, + chapterNumber, providerId, chapterId, key @@ -28,7 +29,13 @@ export default async function getAnifyPage( if (cached) { return JSON.parse(cached); } else { - const data = await fetchData(mediaId, providerId, chapterId, key); + const data = await fetchData( + mediaId, + chapterNumber, + providerId, + chapterId, + key + ); if (!data.error) { if (redis) { await redis.set(chapterId, JSON.stringify(data), "EX", 60 * 10); diff --git a/lib/anilist/aniAdvanceSearch.js b/lib/anilist/aniAdvanceSearch.js index 02a5c53e..cf344b04 100644 --- a/lib/anilist/aniAdvanceSearch.js +++ b/lib/anilist/aniAdvanceSearch.js @@ -23,37 +23,104 @@ export async function aniAdvanceSearch({ return result; }, {}); - const response = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: advanceSearchQuery, - variables: { - ...(search && { - search: search, - ...(!sort && { sort: "SEARCH_MATCH" }), - }), - ...(type && { type: type }), - ...(seasonYear && { seasonYear: seasonYear }), - ...(season && { - season: season, - ...(!seasonYear && { seasonYear: new Date().getFullYear() }), - }), - ...(categorizedGenres && { ...categorizedGenres }), - ...(format && { format: format }), - // ...(genres && { genres: genres }), - // ...(tags && { tags: tags }), - ...(perPage && { perPage: perPage }), - ...(sort && { sort: sort }), + if (type === "MANGA") { + const response = await fetch("https://api.anify.tv/search-advanced", { + method: "POST", + body: JSON.stringify({ + type: "manga", + genres: categorizedGenres, + ...(search && { query: search }), ...(page && { page: page }), + ...(perPage && { perPage: perPage }), + ...(format && { format: format }), + ...(seasonYear && { year: seasonYear }), + ...(type && { type: type }), + }), + }); + + const data = await response.json(); + return { + pageInfo: { + hasNextPage: data.length >= (perPage ?? 20), + currentPage: page, + lastPage: Math.ceil(data.length / (perPage ?? 20)), + perPage: perPage ?? 20, + total: data.length, + }, + media: data.map((item) => ({ + averageScore: item.averageRating, + bannerImage: item.bannerImage, + chapters: item.totalChapters, + coverImage: { + color: item.color, + extraLarge: item.coverImage, + large: item.coverImage, + }, + description: item.description, + duration: item.duration ?? null, + endDate: { + day: null, + month: null, + year: null, + }, + mappings: item.mappings, + format: item.format, + genres: item.genres, + id: item.id, + isAdult: false, + mediaListEntry: null, + nextAiringEpisode: null, + popularity: item.averagePopularity, + season: null, + seasonYear: item.year, + startDate: { + day: null, + month: null, + year: item.year, + }, + status: item.status, + studios: { edges: [] }, + title: { + userPreferred: + item.title.english ?? item.title.romaji ?? item.title.native, + }, + type: item.type, + volumes: item.totalVolumes ?? null, + })), + }; + } else { + const response = await fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", }, - }), - }); + body: JSON.stringify({ + query: advanceSearchQuery, + variables: { + ...(search && { + search: search, + ...(!sort && { sort: "SEARCH_MATCH" }), + }), + ...(type && { type: type }), + ...(seasonYear && { seasonYear: seasonYear }), + ...(season && { + season: season, + ...(!seasonYear && { seasonYear: new Date().getFullYear() }), + }), + ...(categorizedGenres && { ...categorizedGenres }), + ...(format && { format: format }), + // ...(genres && { genres: genres }), + // ...(tags && { tags: tags }), + ...(perPage && { perPage: perPage }), + ...(sort && { sort: sort }), + ...(page && { page: page }), + }, + }), + }); - const datas = await response.json(); - // console.log(datas); - const data = datas.data.Page; - return data; + const datas = await response.json(); + // console.log(datas); + const data = datas.data.Page; + return data; + } } diff --git a/lib/anilist/getMedia.js b/lib/anilist/getMedia.js index 66bb1b0e..2e1b0d0d 100644 --- a/lib/anilist/getMedia.js +++ b/lib/anilist/getMedia.js @@ -115,6 +115,8 @@ export default function GetMedia(session, stats) { data.data.Page.recommendations.map((i) => i.mediaRecommendation) ); }); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [username, accessToken, status?.stats]); return { anime, manga, recommendations }; diff --git a/lib/anilist/useAnilist.js b/lib/anilist/useAnilist.js index 17ab11b6..20c1964b 100644 --- a/lib/anilist/useAnilist.js +++ b/lib/anilist/useAnilist.js @@ -1,4 +1,4 @@ -import { toast } from "react-toastify"; +import { toast } from "sonner"; export const useAniList = (session) => { const accessToken = session?.user?.token; @@ -238,11 +238,6 @@ export const useAniList = (session) => { console.log(`Progress Updated: ${progress}`, status); toast.success(`Progress Updated: ${progress}`, { position: "bottom-right", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: true, - draggable: true, - theme: "dark", }); } }; diff --git a/lib/consumet/manga/getChapters.js b/lib/consumet/manga/getChapters.js new file mode 100644 index 00000000..7a19bbc5 --- /dev/null +++ b/lib/consumet/manga/getChapters.js @@ -0,0 +1,80 @@ +let API_URL; +API_URL = process.env.API_URI; +// remove / from the end of the url if it exists +if (API_URL.endsWith("/")) { + API_URL = API_URL.slice(0, -1); +} + +async function fetchInfo(id) { + try { + const providers = [ + "mangadex", + "mangahere", + "mangakakalot", + // "mangapark", + // "mangapill", + "mangasee123", + ]; + let datas = []; + + async function promiseMe(provider) { + try { + const data = await fetch( + `${API_URL}/meta/anilist-manga/info/${id}?provider=${provider}` + ).then((res) => { + if (!res.ok) { + switch (res.status) { + case 404: { + return null; + } + } + } + return res.json(); + }); + if (data.chapters.length > 0) { + datas.push({ + providerId: provider, + chapters: data.chapters, + }); + } + } catch (error) { + console.error(`Error fetching data for provider '${provider}':`, error); + } + } + + await Promise.all(providers.map((provider) => promiseMe(provider))); + + return datas; + } catch (error) { + console.error("Error fetching data:", error); + return null; + } +} + +export default async function getConsumetChapters(id, redis) { + try { + let cached; + let chapters; + + if (redis) { + cached = await redis.get(`chapter:${id}`); + } + + if (cached) { + chapters = JSON.parse(cached); + } else { + chapters = await fetchInfo(id); + } + + if (chapters?.length === 0) { + return null; + } + if (redis) { + await redis.set(`chapter:${id}`, JSON.stringify(chapters), "EX", 60 * 60); // 1 hour + } + + return chapters; + } catch (error) { + return { error }; + } +} diff --git a/lib/consumet/manga/getPage.js b/lib/consumet/manga/getPage.js new file mode 100644 index 00000000..832c1d78 --- /dev/null +++ b/lib/consumet/manga/getPage.js @@ -0,0 +1,49 @@ +let API_URL; +API_URL = process.env.API_URI; +// remove / from the end of the url if it exists +if (API_URL.endsWith("/")) { + API_URL = API_URL.slice(0, -1); +} + +// Function to fetch new data +async function fetchData(id, providerId, chapterId, key) { + try { + const res = await fetch( + `${API_URL}/meta/anilist-manga/read?chapterId=${chapterId}&provider=${providerId}` + ); + const data = await res.json(); + return data; + } catch (error) { + console.error("Error fetching data:", error); + return null; + } +} + +export default async function getConsumetPages( + mediaId, + providerId, + chapterId, + key +) { + try { + // let cached; + // if (redis) { + // cached = await redis.get(chapterId); + // } + // if (cached) { + // return JSON.parse(cached); + // } else { + const data = await fetchData(mediaId, providerId, chapterId, key); + if (!data.error) { + // if (redis) { + // await redis.set(chapterId, JSON.stringify(data), "EX", 60 * 10); + // } + return data; + } else { + return { message: "Manga/Novel not found :(" }; + } + // } + } catch (error) { + return { error }; + } +} diff --git a/lib/graphql/query.js b/lib/graphql/query.js index a09c6ac8..45d3d685 100644 --- a/lib/graphql/query.js +++ b/lib/graphql/query.js @@ -176,8 +176,14 @@ query { }`; const mediaInfoQuery = ` - query ($id: Int) { - Media(id: $id) { + query ($id: Int, $type:MediaType) { + Media(id: $id, type:$type) { + mediaListEntry { + status + progress + progressVolumes + status + } id type format @@ -191,6 +197,10 @@ const mediaInfoQuery = ` large color } + startDate { + year + month + } bannerImage description episodes diff --git a/next.config.js b/next.config.js index a0ec43c7..d3fd8827 100644 --- a/next.config.js +++ b/next.config.js @@ -24,6 +24,10 @@ module.exports = withPWA({ protocol: "https", hostname: "simkl.in", }, + { + protocol: "https", + hostname: "tenor.com", + }, ], }, // distDir: process.env.BUILD_DIR || ".next", diff --git a/package-lock.json b/package-lock.json index 1edee36d..d90eeb32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "moopa", - "version": "4.1.3", + "version": "4.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "moopa", - "version": "4.1.3", + "version": "4.2.0", "dependencies": { "@apollo/client": "^3.7.3", "@headlessui/react": "^1.7.15", @@ -35,6 +35,8 @@ "react-loading-skeleton": "^3.2.0", "react-toastify": "^9.1.3", "react-use-draggable-scroll": "^0.4.7", + "sharp": "^0.32.6", + "sonner": "^1.0.3", "tailwind-scrollbar-hide": "^1.1.7", "workbox-webpack-plugin": "^7.0.0" }, @@ -3267,6 +3269,11 @@ "dequal": "^2.0.3" } }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, "node_modules/babel-loader": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", @@ -3359,6 +3366,16 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3410,11 +3427,53 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, + "node_modules/buffer/node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -3558,6 +3617,11 @@ "node": ">= 6" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, "node_modules/chrome-trace-event": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", @@ -3618,6 +3682,18 @@ "node": ">=0.10.0" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -3631,6 +3707,31 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3802,6 +3903,28 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -3975,6 +4098,14 @@ "node": ">=6" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -4052,6 +4183,14 @@ "node": ">= 4" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -4782,11 +4921,24 @@ "node": ">=0.8.x" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -5008,6 +5160,11 @@ "react-dom": "^18.0.0" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -5133,6 +5290,11 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -5362,6 +5524,25 @@ "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -5415,6 +5596,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -6339,6 +6525,17 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -6354,11 +6551,15 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6411,6 +6612,11 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6761,6 +6967,22 @@ "react": ">= 16.0.0" } }, + "node_modules/node-abi": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.51.0.tgz", + "integrity": "sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + }, "node_modules/node-releases": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", @@ -7402,6 +7624,57 @@ "preact": ">=10" } }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -7458,6 +7731,15 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -7495,6 +7777,11 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7508,6 +7795,28 @@ "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-3.0.0.tgz", "integrity": "sha512-janAJkWxWxmLka0hV+XvCTo0M8keeSeOuz8ZL33cTXrkS4ek9mQ2VJm9ri7fm03oTVth19Sfqb1ijCmo7K/vAg==" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", @@ -7593,6 +7902,19 @@ "node": ">=0.10.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8037,7 +8359,6 @@ "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -8058,7 +8379,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -8069,8 +8389,7 @@ "node_modules/semver/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/serialize-javascript": { "version": "6.0.1", @@ -8099,6 +8418,28 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8133,6 +8474,62 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8141,6 +8538,15 @@ "node": ">=8" } }, + "node_modules/sonner": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.0.3.tgz", + "integrity": "sha512-hBoA2zKuYW3lUnpx4K0vAn8j77YuYiwvP9sLQfieNS2pd5FkT20sMyPTDJnl9S+5T27ZJbwQRPiujwvDBwhZQg==", + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -8196,6 +8602,23 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -8538,6 +8961,26 @@ "node": ">=6" } }, + "node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/temp-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -8761,6 +9204,17 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8986,8 +9440,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { "version": "8.3.2", diff --git a/package.json b/package.json index 91fba68d..a13c9fe4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moopa", - "version": "4.1.3", + "version": "4.2.0", "private": true, "founder": "Factiven", "scripts": { @@ -38,6 +38,8 @@ "react-loading-skeleton": "^3.2.0", "react-toastify": "^9.1.3", "react-use-draggable-scroll": "^0.4.7", + "sharp": "^0.32.6", + "sonner": "^1.0.3", "tailwind-scrollbar-hide": "^1.1.7", "workbox-webpack-plugin": "^7.0.0" }, diff --git a/pages/404.js b/pages/404.js index f6e609ff..085d9847 100644 --- a/pages/404.js +++ b/pages/404.js @@ -1,27 +1,13 @@ import Head from "next/head"; import Link from "next/link"; -import { useEffect, useState } from "react"; -import { parseCookies } from "nookies"; import Image from "next/image"; import Footer from "@/components/shared/footer"; +import { NewNavbar } from "@/components/shared/NavBar"; +import { useRouter } from "next/router"; +import { ArrowLeftIcon } from "@heroicons/react/24/outline"; export default function Custom404() { - const [lang, setLang] = useState("en"); - const [cookie, setCookies] = useState(null); - - useEffect(() => { - let lang = null; - if (!cookie) { - const cookie = parseCookies(); - lang = cookie.lang || null; - setCookies(cookie); - } - if (lang === "en" || lang === null) { - setLang("en"); - } else if (lang === "id") { - setLang("id"); - } - }, []); + const router = useRouter(); return ( <> @@ -30,6 +16,7 @@ export default function Custom404() { +
The page you're looking for doesn't seem to exist.

- -
- Go back home -
- +
+ + +
diff --git a/pages/_app.js b/pages/_app.js index f553a988..e2f780da 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -3,22 +3,23 @@ import { AnimatePresence, motion as m } from "framer-motion"; import NextNProgress from "nextjs-progressbar"; import { SessionProvider } from "next-auth/react"; import "../styles/globals.css"; -import "react-toastify/dist/ReactToastify.css"; import "react-loading-skeleton/dist/skeleton.css"; import { SkeletonTheme } from "react-loading-skeleton"; import SearchPalette from "@/components/searchPalette"; import { SearchProvider } from "@/lib/context/isOpenState"; import Head from "next/head"; import { WatchPageProvider } from "@/lib/context/watchPageProvider"; -import { ToastContainer, toast } from "react-toastify"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { unixTimestampToRelativeTime } from "@/utils/getTimes"; +import SecretPage from "@/components/secret"; +import { Toaster, toast } from "sonner"; export default function App({ Component, pageProps: { session, ...pageProps }, }) { const router = useRouter(); + const [info, setInfo] = useState(null); useEffect(() => { async function getBroadcast() { @@ -31,29 +32,31 @@ export default function App({ }, }); const data = await res.json(); - if ( - data && - data?.message !== "No broadcast" && - data?.message !== "unauthorized" - ) { - toast( - `${data.message} ${ + if (data?.show === true) { + toast.message( + `🚧${data.message} ${ data?.startAt ? unixTimestampToRelativeTime(data.startAt) : "" - }`, + }🚧`, { - position: "top-center", - autoClose: false, - closeOnClick: true, - draggable: true, - theme: "colored", - className: "toaster", - style: { - background: "#232329", - color: "#fff", - }, + position: "bottom-right", + important: true, + duration: 100000, + className: "flex-center font-karla text-white", + // description: `🚧${info}🚧`, } ); + // toast.message(`Announcement`, { + // position: "top-center", + // important: true, + // // duration: 10000, + // description: `🚧${info}🚧`, + // }); } + setInfo( + `${data.message} ${ + data?.startAt ? unixTimestampToRelativeTime(data.startAt) : "" + }` + ); } catch (err) { console.log(err); } @@ -61,12 +64,16 @@ export default function App({ getBroadcast(); }, []); + const handleCheatCodeEntered = () => { + alert("Cheat code entered!"); // You can replace this with your desired action + }; + return ( <> @@ -74,7 +81,22 @@ export default function App({ - + + + {/* {info && ( +
+ 🚧{info}🚧 + setInfo()} + className="absolute right-3 cursor-pointer" + > + + +
+ )} */} + + An Error Has Occurred + + + +
+
(╯°□°)╯︵ ┻━┻
+
+ + {statusCode + ? `An error ${statusCode} occurred on server.` + : "An error occurred on client."} + +
+ + Back to home + +
+
+ + ); +} + +Error.getInitialProps = ({ res, err }) => { + const statusCode = res ? res.statusCode : err ? err.statusCode : 404; + return { statusCode }; +}; + +export default Error; diff --git a/pages/_offline.js b/pages/_offline.js new file mode 100644 index 00000000..f440b396 --- /dev/null +++ b/pages/_offline.js @@ -0,0 +1,45 @@ +import Image from "next/image"; +import React from "react"; + +export default function Fallback() { + return ( +
+
+ logo +
+

+ + + + + You are Offline :\ +

+
+ ); +} diff --git a/pages/admin/index.js b/pages/admin/index.js index cbb5086e..2a73fc16 100644 --- a/pages/admin/index.js +++ b/pages/admin/index.js @@ -27,7 +27,12 @@ export async function getServerSideProps(context) { } const admin = sessions?.user?.name === process.env.ADMIN_USERNAME; - const api = process.env.API_URI; + + let api; + api = process.env.API_URI; + if (api.endsWith("/")) { + api = api.slice(0, -1); + } if (!admin) { return { diff --git a/pages/api/v2/admin/broadcast/index.js b/pages/api/v2/admin/broadcast/index.js index d3d3af08..470d61d3 100644 --- a/pages/api/v2/admin/broadcast/index.js +++ b/pages/api/v2/admin/broadcast/index.js @@ -1,9 +1,17 @@ import { rateLimitStrict, redis } from "@/lib/redis"; -// import { getServerSession } from "next-auth"; -// import { authOptions } from "pages/api/auth/[...nextauth]"; +import { getServerSession } from "next-auth"; +import { authOptions } from "pages/api/auth/[...nextauth]"; export default async function handler(req, res) { // Check if the custom header "X-Your-Custom-Header" is present and has a specific value + const sessions = await getServerSession(req, res, authOptions); + + const admin = sessions?.user?.name === process.env.ADMIN_USERNAME; + // if req.method === POST and admin === false return 401 + if (!admin && req.method === "DELETE") { + return res.status(401).json({ message: "Unauthorized" }); + } + const customHeaderValue = req.headers["x-broadcast-key"]; if (customHeaderValue !== "get-broadcast") { @@ -21,14 +29,40 @@ export default async function handler(req, res) { }); } - const getId = await redis.get(`broadcast`); - if (getId) { - const broadcast = JSON.parse(getId); - return res - .status(200) - .json({ message: broadcast.message, startAt: broadcast.startAt }); - } else { - return res.status(200).json({ message: "No broadcast" }); + if (req.method === "POST") { + const { message, startAt = undefined, show = false } = req.body; + if (!message) { + return res.status(400).json({ message: "Message is required" }); + } + + const broadcastContent = { + message, + startAt, + show, + }; + await redis.set(`broadcasts`, JSON.stringify(broadcastContent)); + return res.status(200).json({ message: "Broadcast created" }); + } else if (req.method === "DELETE") { + const br = await redis.get(`broadcasts`); + // set broadcast show as false + if (br) { + const broadcast = JSON.parse(br); + broadcast.show = false; + await redis.set(`broadcasts`, JSON.stringify(broadcast)); + } + return res.status(200).json({ message: "Broadcast deleted" }); + } else if (req.method === "GET") { + const getId = await redis.get(`broadcasts`); + if (getId) { + const broadcast = JSON.parse(getId); + return res.status(200).json({ + message: broadcast.message, + startAt: broadcast.startAt, + show: broadcast.show, + }); + } else { + return res.status(200).json({ message: "No broadcast" }); + } } } diff --git a/pages/api/v2/admin/bug-report/index.js b/pages/api/v2/admin/bug-report/index.js index fc5ee77c..508e6cd9 100644 --- a/pages/api/v2/admin/bug-report/index.js +++ b/pages/api/v2/admin/bug-report/index.js @@ -8,16 +8,6 @@ export default async function handler(req, res) { // create random id each time the endpoint is called const id = Math.random().toString(36).substr(2, 9); - // if (!admin) { - // return res.status(401).json({ message: "Unauthorized" }); - // } - const { data } = req.body; - - // if method is not POST return message "Method not allowed" - if (req.method !== "POST") { - return res.status(405).json({ message: "Method not allowed" }); - } - try { if (redis) { try { @@ -29,16 +19,22 @@ export default async function handler(req, res) { }); } - const getId = await redis.get(`report:${id}`); - if (getId) { + if (req.method === "POST") { + const { data } = req.body; + + data.id = id; + + await redis.set(`report:${id}`, JSON.stringify(data)); return res .status(200) - .json({ message: `Data already exist for id: ${id}` }); + .json({ message: `Report has successfully sent, with Id of ${id}` }); + } else if (req.method === "DELETE") { + const { reportId } = req.body; + await redis.del(`report:${reportId}`); + return res.status(200).json({ message: `Report has been deleted` }); + } else { + return res.status(405).json({ message: "Method not allowed" }); } - await redis.set(`report:${id}`, JSON.stringify(data)); - return res - .status(200) - .json({ message: `Report has successfully sent, with Id of ${id}` }); } return res.status(200).json({ message: "redis is not defined" }); diff --git a/pages/api/v2/episode/[id].js b/pages/api/v2/episode/[id].js index c1fac8bf..3f1372b8 100644 --- a/pages/api/v2/episode/[id].js +++ b/pages/api/v2/episode/[id].js @@ -3,7 +3,13 @@ import { rateLimitStrict, rateLimiterRedis, redis } from "@/lib/redis"; import appendImagesToEpisodes from "@/utils/combineImages"; import appendMetaToEpisodes from "@/utils/appendMetaToEpisodes"; -const CONSUMET_URI = process.env.API_URI; +let CONSUMET_URI; + +CONSUMET_URI = process.env.API_URI; +if (CONSUMET_URI.endsWith("/")) { + CONSUMET_URI = CONSUMET_URI.slice(0, -1); +} + const API_KEY = process.env.API_KEY; const isAscending = (data) => { @@ -15,37 +21,70 @@ const isAscending = (data) => { return true; }; -async function fetchConsumet(id, dub) { - try { - if (dub) { - return []; +function filterData(data, type) { + // Filter the data based on the type (sub or dub) and providerId + const filteredData = data.map((item) => { + if (item?.map === true) { + if (item.episodes[type].length === 0) { + return null; + } else { + return { + ...item, + episodes: Object?.entries(item.episodes[type]).map( + ([id, episode]) => ({ + ...episode, + }) + ), + }; + } } + return item; + }); - const { data } = await axios.get( - `${CONSUMET_URI}/meta/anilist/episodes/${id}` - ); + const noEmpty = filteredData.filter((i) => i !== null); + return noEmpty; +} - if (data?.message === "Anime not found" && data?.length < 1) { - return []; +async function fetchConsumet(id) { + try { + async function fetchData(dub) { + const { data } = await axios.get( + `${CONSUMET_URI}/meta/anilist/episodes/${id}${dub ? "?dub=true" : ""}` + ); + if (data?.message === "Anime not found" && data?.length < 1) { + return []; + } + + if (dub) { + if (!data?.some((i) => i.id.includes("dub"))) return []; + } + + const reformatted = data.map((item) => ({ + id: item?.id || null, + title: item?.title || null, + img: item?.image || null, + number: item?.number || null, + createdAt: item?.createdAt || null, + description: item?.description || null, + url: item?.url || null, + })); + + return reformatted; } - const reformatted = data.map((item) => ({ - id: item?.id || null, - title: item?.title || null, - img: item?.image || null, - number: item?.number || null, - createdAt: item?.createdAt || null, - description: item?.description || null, - url: item?.url || null, - })); + const [subData, dubData] = await Promise.all([ + fetchData(), + fetchData(true), + ]); const array = [ { map: true, providerId: "gogoanime", - episodes: isAscending(reformatted) - ? reformatted - : reformatted.reverse(), + episodes: { + sub: isAscending(subData) ? subData : subData.reverse(), + dub: isAscending(dubData) ? dubData : dubData.reverse(), + }, }, ]; @@ -73,7 +112,15 @@ async function fetchAnify(id) { const filtered = data.filter( (item) => item.providerId !== "animepahe" && item.providerId !== "kass" ); - + // const modifiedData = filtered.map((provider) => { + // if (provider.providerId === "gogoanime") { + // const reversedEpisodes = [...provider.episodes].reverse(); + // return { ...provider, episodes: reversedEpisodes }; + // } + // return provider; + // }); + + // return modifiedData; return filtered; } catch (error) { console.error("Error fetching and processing data:", error.message); @@ -81,12 +128,16 @@ async function fetchAnify(id) { } } -async function fetchCoverImage(id) { +async function fetchCoverImage(id, available = false) { try { if (!process.env.API_KEY) { return []; } + if (available) { + return null; + } + const { data } = await axios.get( `https://api.anify.tv/content-metadata/${id}?apikey=${API_KEY}` ); @@ -95,7 +146,9 @@ async function fetchCoverImage(id) { return []; } - return data; + const getData = data[0].data; + + return getData; } catch (error) { console.error("Error fetching and processing data:", error.message); return []; @@ -124,10 +177,10 @@ export default async function handler(req, res) { } if (refresh) { - await redis.del(id); + await redis.del(`episode:${id}`); console.log("deleted cache"); } else { - cached = await redis.get(id); + cached = await redis.get(`episode:${id}`); console.log("using redis"); } @@ -136,49 +189,75 @@ export default async function handler(req, res) { if (cached && !refresh) { if (dub) { - const filtered = JSON.parse(cached).filter((item) => - item.episodes.some((epi) => epi.hasDub === true) + const filteredData = filterData(JSON.parse(cached), "dub"); + + let filtered = filteredData.filter((item) => + item?.episodes?.some((epi) => epi.hasDub !== false) ); + + if (meta) { + filtered = await appendMetaToEpisodes(filtered, JSON.parse(meta)); + } + return res.status(200).json(filtered); } else { - return res.status(200).json(JSON.parse(cached)); + const filteredData = filterData(JSON.parse(cached), "sub"); + + let filtered = filteredData; + + if (meta) { + filtered = await appendMetaToEpisodes(filteredData, JSON.parse(meta)); + } + + return res.status(200).json(filtered); } } else { const [consumet, anify, cover] = await Promise.all([ fetchConsumet(id, dub), fetchAnify(id), - fetchCoverImage(id), + fetchCoverImage(id, meta), ]); - const hasImage = consumet.map((i) => - i.episodes.some( - (e) => e.img !== null || !e.img.includes("https://s4.anilist.co/") - ) - ); + // const hasImage = consumet.map((i) => + // i.episodes?.sub?.some( + // (e) => e.img !== null || !e.img.includes("https://s4.anilist.co/") + // ) + // ); + + let subDub = "sub"; + if (dub) { + subDub = "dub"; + } - const rawData = [...consumet, ...(anify[0]?.data ?? [])]; + const rawData = [...consumet, ...anify]; - let data = rawData; + const filteredData = filterData(rawData, subDub); + + let data = filteredData; if (meta) { - data = await appendMetaToEpisodes(rawData, JSON.parse(meta)); - } else if (cover && cover?.length > 0 && !hasImage.includes(true)) - data = await appendImagesToEpisodes(rawData, cover); + data = await appendMetaToEpisodes(filteredData, JSON.parse(meta)); + } else if (cover && !cover.some((e) => e.img === null)) { + await redis.set(`meta:${id}`, JSON.stringify(cover)); + data = await appendMetaToEpisodes(filteredData, cover); + } if (redis && cacheTime !== null) { await redis.set( - id, - JSON.stringify(data.filter((i) => i.episodes.length > 0)), + `episode:${id}`, + JSON.stringify(rawData), "EX", cacheTime ); } if (dub) { - const filtered = data.filter((item) => - item.episodes.some((epi) => epi.hasDub === true) + const filtered = data.filter( + (item) => !item.episodes.some((epi) => epi.hasDub === false) ); - return res.status(200).json(filtered); + return res + .status(200) + .json(filtered.filter((i) => i.episodes.length > 0)); } console.log("fresh data"); diff --git a/pages/api/v2/etc/recent/[page].js b/pages/api/v2/etc/recent/[page].js index 6727787b..b1bda0f5 100644 --- a/pages/api/v2/etc/recent/[page].js +++ b/pages/api/v2/etc/recent/[page].js @@ -1,6 +1,10 @@ import { rateLimiterRedis, redis } from "@/lib/redis"; -const API_URL = process.env.API_URI; +let API_URL; +API_URL = process.env.API_URI; +if (API_URL.endsWith("/")) { + API_URL = API_URL.slice(0, -1); +} export default async function handler(req, res) { try { diff --git a/pages/api/v2/info/[id].js b/pages/api/v2/info/[id].js deleted file mode 100644 index 243756cc..00000000 --- a/pages/api/v2/info/[id].js +++ /dev/null @@ -1,47 +0,0 @@ -import axios from "axios"; -import { rateLimiterRedis, redis } from "@/lib/redis"; - -const API_KEY = process.env.API_KEY; - -export async function fetchInfo(id) { - try { - const { data } = await axios.get( - `https://api.anify.tv/info/${id}?apikey=${API_KEY}` - ); - return data; - } catch (error) { - console.error("Error fetching data:", error); - return null; - } -} - -export default async function handler(req, res) { - const id = req.query.id; - let cached; - if (redis) { - try { - const ipAddress = req.socket.remoteAddress; - await rateLimiterRedis.consume(ipAddress); - } catch (error) { - return res.status(429).json({ - error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, - }); - } - cached = await redis.get(id); - } - if (cached) { - // console.log("Using cached data"); - return res.status(200).json(JSON.parse(cached)); - } else { - const data = await fetchInfo(id); - if (data) { - // console.log("Setting cache"); - if (redis) { - await redis.set(id, JSON.stringify(data), "EX", 60 * 10); - } - return res.status(200).json(data); - } else { - return res.status(404).json({ message: "Schedule not found" }); - } - } -} diff --git a/pages/api/v2/info/index.js b/pages/api/v2/info/index.js new file mode 100644 index 00000000..95770bdb --- /dev/null +++ b/pages/api/v2/info/index.js @@ -0,0 +1,60 @@ +import { redis } from "@/lib/redis"; +import axios from "axios"; + +const API_KEY = process.env.API_KEY; + +export async function fetchInfo(id) { + try { + // console.log(id); + const { data } = await axios + .get(`https://api.anify.tv/info/${id}?apikey=${API_KEY}`) + .catch((err) => { + return { + data: null, + }; + }); + + if (!data) { + return null; + } + + const { data: Chapters } = await axios.get( + `https://api.anify.tv/chapters/${data.id}?apikey=${API_KEY}` + ); + + if (!Chapters) { + return null; + } + + return { id: data.id, chapters: Chapters }; + } catch (error) { + console.error("Error fetching data:", error); + return null; + } +} + +export default async function handler(req, res) { + //const [romaji, english, native] = req.query.title; + const { id } = req.query; + try { + let cached; + // const data = await fetchInfo(id); + cached = await redis.get(`manga:${id}`); + + if (cached) { + return res.status(200).json(JSON.parse(cached)); + } + + const manga = await fetchInfo(id); + + if (!manga) { + return res.status(404).json({ error: "Manga not found" }); + } + + await redis.set(`manga:${id}`, JSON.stringify(manga), "ex", 60 * 60 * 24); + + res.status(200).json(manga); + } catch (error) { + res.status(500).json({ error: error.message }); + } +} diff --git a/pages/api/v2/pages/[...id].js b/pages/api/v2/pages/[...id].js new file mode 100644 index 00000000..a9fe0f96 --- /dev/null +++ b/pages/api/v2/pages/[...id].js @@ -0,0 +1,34 @@ +import axios from "axios"; + +async function fetchData(id, number, provider, readId) { + try { + const { data } = await axios.get( + `https://api.anify.tv/pages?id=${id}&chapterNumber=${number}&providerId=${provider}&readId=${encodeURIComponent( + readId + )}` + ); + + if (!data) { + return null; + } + + return data; + } catch (error) { + return null; + } +} + +export default async function handler(req, res) { + const [id, number, provider, readId] = req.query.id; + + try { + const data = await fetchData(id, number, provider, readId); + // if (!data) { + // return res.status(400).json({ error: "Invalid query" }); + // } + + return res.status(200).json(data); + } catch (error) { + return res.status(500).json({ error: error.message }); + } +} diff --git a/pages/api/v2/source/index.js b/pages/api/v2/source/index.js index f15e47df..9ec60826 100644 --- a/pages/api/v2/source/index.js +++ b/pages/api/v2/source/index.js @@ -1,7 +1,11 @@ import { rateLimiterRedis, redis } from "@/lib/redis"; import axios from "axios"; -const CONSUMET_URI = process.env.API_URI; +let CONSUMET_URI; +CONSUMET_URI = process.env.API_URI; +if (CONSUMET_URI.endsWith("/")) { + CONSUMET_URI = CONSUMET_URI.slice(0, -1); +} const API_KEY = process.env.API_KEY; async function consumetSource(id) { @@ -25,7 +29,7 @@ async function anifySource(providerId, watchId, episode, id, sub) { ); return data; } catch (error) { - return null; + return { error: error.message, status: error.response.status }; } } diff --git a/pages/en/anime/[...id].js b/pages/en/anime/[...id].js index 910bbc61..e2c0039e 100644 --- a/pages/en/anime/[...id].js +++ b/pages/en/anime/[...id].js @@ -72,6 +72,8 @@ export default function Info({ info, color }) { } } fetchData(); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [id, info, session?.user?.name]); function handleOpen() { @@ -143,7 +145,7 @@ export default function Info({ info, color }) { stats={statuses?.value} prg={progress} max={info?.episodes} - image={info} + info={info} close={handleClose} /> )} @@ -208,7 +210,12 @@ export default function Info({ info, color }) { export async function getServerSideProps(ctx) { const { id } = ctx.query; - const API_URI = process.env.API_URI; + + let API_URI; + API_URI = process.env.API_URI; + if (API_URI.endsWith("/")) { + API_URI = API_URI.slice(0, -1); + } let cache; diff --git a/pages/en/anime/recently-watched.js b/pages/en/anime/recently-watched.js index c7233940..6abf09d3 100644 --- a/pages/en/anime/recently-watched.js +++ b/pages/en/anime/recently-watched.js @@ -6,12 +6,12 @@ import Skeleton from "react-loading-skeleton"; import Footer from "@/components/shared/footer"; import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; -import { toast } from "react-toastify"; import { ChevronRightIcon } from "@heroicons/react/24/outline"; import { useRouter } from "next/router"; import HistoryOptions from "@/components/home/content/historyOptions"; import Head from "next/head"; import MobileNav from "@/components/shared/MobileNav"; +import { toast } from "sonner"; export default function PopularAnime({ sessions }) { const [data, setData] = useState(null); @@ -105,11 +105,6 @@ export default function PopularAnime({ sessions }) { if (data?.message === "Episode deleted") { toast.success("Episode removed from history", { position: "bottom-right", - autoClose: 5000, - hideProgressBar: false, - closeOnClick: true, - draggable: true, - theme: "dark", }); } } else { diff --git a/pages/en/anime/watch/[...info].js b/pages/en/anime/watch/[...info].js index f918f869..a838b7f8 100644 --- a/pages/en/anime/watch/[...info].js +++ b/pages/en/anime/watch/[...info].js @@ -29,8 +29,12 @@ export async function getServerSideProps(context) { }; } - const proxy = process.env.PROXY_URI; - const disqus = process.env.DISQUS_SHORTNAME || null; + let proxy; + proxy = process.env.PROXY_URI; + if (proxy.endsWith("/")) { + proxy = proxy.slice(0, -1); + } + const disqus = process.env.DISQUS_SHORTNAME; const [aniId, provider] = query?.info; const watchId = query?.id; @@ -114,7 +118,7 @@ export async function getServerSideProps(context) { epiNumber: epiNumber || null, dub: dub || null, userData: userData?.[0] || null, - info: data.data.Media || null, + info: data?.data?.Media || null, proxy, disqus, }, @@ -179,9 +183,10 @@ export default function Watch({ if (episodes) { const getProvider = episodes?.find((i) => i.providerId === provider); - const episodeList = dub - ? getProvider?.episodes?.filter((x) => x.hasDub === true) - : getProvider?.episodes.slice(0, getMap?.episodes.length); + const episodeList = getProvider?.episodes.slice( + 0, + getMap?.episodes.length + ); const playingData = getMap?.episodes.find( (i) => i.number === Number(epiNumber) ); @@ -219,6 +224,7 @@ export default function Watch({ return () => { setEpisodeNavigation(null); }; + // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessions?.user?.name, epiNumber, dub]); useEffect(() => { @@ -287,6 +293,8 @@ export default function Watch({ }); setMarked(0); }; + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [provider, watchId, info?.id]); useEffect(() => { @@ -524,7 +532,7 @@ export default function Watch({
diff --git a/pages/en/index.js b/pages/en/index.js index 9be3c2c1..29b07786 100644 --- a/pages/en/index.js +++ b/pages/en/index.js @@ -118,23 +118,23 @@ export default function Home({ detail, populars, upComing }) { } }, [upComing]); - useEffect(() => { - const getSchedule = async () => { - try { - const res = await fetch(`/api/v2/etc/schedule`); - const data = await res.json(); - - if (!res.ok) { - setSchedules(null); - } else { - setSchedules(data); - } - } catch (err) { - console.log(err); - } - }; - getSchedule(); - }, []); + // useEffect(() => { + // const getSchedule = async () => { + // try { + // const res = await fetch(`/api/v2/etc/schedule`); + // const data = await res.json(); + + // if (!res.ok) { + // setSchedules(null); + // } else { + // setSchedules(data); + // } + // } catch (err) { + // console.log(err); + // } + // }; + // getSchedule(); + // }, []); const [releaseData, setReleaseData] = useState([]); @@ -290,6 +290,8 @@ export default function Home({ detail, populars, upComing }) { } } userData(); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [sessions?.user?.name, currentAnime, plan]); // console.log({ recentAdded }); @@ -402,7 +404,7 @@ export default function Home({ detail, populars, upComing }) {
)} -
+
data.mediaRecommendation + ); + + useEffect(() => { + setDomainUrl(window.location.origin); + }, []); + + useEffect(() => { + if (chapterNotFound) { + toast.error("Chapter not found"); + const cleanUrl = window.location.origin + window.location.pathname; + window.history.replaceState(null, null, cleanUrl); + } + }, [chapterNotFound]); + + useEffect(() => { + async function fetchData() { + try { + setLoading(true); + + const { data } = await axios.get(`/api/v2/info?id=${anifyData.id}`); + + if (!data.chapters) { + setLoading(false); + return; + } + + setChapter(data); + setLoading(false); + } catch (error) { + console.error(error); + } + } + fetchData(); + + return () => { + setChapter(null); + }; + }, [info?.id]); + + function handleOpen() { + setOpen(true); + document.body.style.overflow = "hidden"; + } + + function handleClose() { + setOpen(false); + document.body.style.overflow = "auto"; + } + + return ( + <> + + + {info + ? `Manga - ${ + info.title.romaji || info.title.english || info.title.native + }` + : "Getting Info..."} + + + + + + + + handleClose()}> +
+ {!session && ( +
+
+ Edit your list +
+ +
+ )} + {session && info && ( + + )} +
+
+ +
+ {/*
*/} +
+
+ {info?.bannerImage && ( + banner anime + )} +
+
+ + + {!loading ? ( + chapter?.chapters?.length > 0 ? ( + + ) : ( +
+

+ Oops!

It looks like this manga is not available. +

+
+ ) + ) : ( +
+
+
+
+
+
+
+
+ )} + + {info?.characters?.edges?.length > 0 && ( +
+ +
+ )} + + {info && rec && rec?.length !== 0 && ( +
+ +
+ )} +
+
+