From 1eb531338f5ae3696fa9d68a4171a73f0107c2f8 Mon Sep 17 00:00:00 2001 From: Factiven Date: Fri, 4 Aug 2023 14:49:35 +0700 Subject: Update v3.8.5 - Merged Beta to Main (#32) * initial commit * Update_v.3.6.7-beta-v1.2 * Update_v.3.6.7-beta-v1.3 * Update_v.3.6.7-beta-v1.3 > update API * Fixed mediaList won't update * added .env disqus shortname * Update_v3.6.7-beta-v1.4 >Implementing database * Create main.yml * Update v3.6.7-beta-v1.5 small patch * title home page * Update content.js * Delete db-test.js * Update content.js * Update home page card * Update v3.7.0 * Update v3.7.1-beta > migrating backend to main code > fixed schedule component * Update v3.8.0 > Added dub options > Moved schedule backend * Update v.3.8.1 > Fixed episodes on watch page isn't dubbed * Update v3.8.1-patch-1 * Update v3.8.1-patch-2 > Another patch for dub * Update v3.8.2 > Removed prisma configuration for database since it's not stable yet * Update v3.8.3 > Fixed different provider have same id * Update v.3.8.3 > Fixed player bug where the controls won't hide after updating anilist progress * Update v3.8.4-patch-2 * Update v3.8.5 > Update readme.md > Update .env.example --- .env.example | 8 +- .github/workflows/main.yml | 29 + README.md | 29 +- components/anime/changeView.js | 107 +++ components/anime/episode.js | 281 ++++++++ components/anime/infoDetails.js | 203 ++++++ components/anime/mobile/topSection.js | 81 +++ components/anime/viewMode/listMode.js | 39 + components/anime/viewMode/thumbnailDetail.js | 76 ++ components/anime/viewMode/thumbnailOnly.js | 59 ++ components/anime/watch/primary/details.js | 177 +++++ components/anime/watch/primarySide.js | 213 ++++++ components/anime/watch/secondarySide.js | 129 ++++ components/disqus.js | 2 +- components/home/content.js | 23 +- components/home/genres.js | 2 +- components/home/schedule.js | 4 +- components/listEditor.js | 2 +- components/videoPlayer.js | 49 +- lib/anilist/AniList.js | 1 + lib/anilist/useAnilist.js | 18 +- lib/useCountdownSeconds.js | 37 - next.config.js | 52 ++ package-lock.json | 682 ++++++++++++++++-- package.json | 14 +- pages/404.js | 69 +- pages/api/anify/schedule.js | 53 ++ pages/api/consumet/episode/[id].js | 63 ++ pages/api/consumet/source/[...params].js | 36 + pages/en/anime/[...id].js | 1000 ++------------------------ pages/en/anime/watch/[...info].js | 749 +++++-------------- pages/en/index.js | 7 +- pages/en/test.js | 11 + public/404.svg | 23 + styles/globals.css | 2 +- utils/useCountdownSeconds.js | 37 + 36 files changed, 2651 insertions(+), 1716 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 components/anime/changeView.js create mode 100644 components/anime/episode.js create mode 100644 components/anime/infoDetails.js create mode 100644 components/anime/mobile/topSection.js create mode 100644 components/anime/viewMode/listMode.js create mode 100644 components/anime/viewMode/thumbnailDetail.js create mode 100644 components/anime/viewMode/thumbnailOnly.js create mode 100644 components/anime/watch/primary/details.js create mode 100644 components/anime/watch/primarySide.js create mode 100644 components/anime/watch/secondarySide.js delete mode 100644 lib/useCountdownSeconds.js create mode 100644 pages/api/anify/schedule.js create mode 100644 pages/api/consumet/episode/[id].js create mode 100644 pages/api/consumet/source/[...params].js create mode 100644 pages/en/test.js create mode 100644 public/404.svg create mode 100644 utils/useCountdownSeconds.js diff --git a/.env.example b/.env.example index 1fc8fb3..a79878a 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,14 @@ +## AniList CLIENT_ID="get the id from here https://anilist.co/settings/developer" CLIENT_SECRET="get the secret from here https://anilist.co/settings/developer" GRAPHQL_ENDPOINT="https://graphql.anilist.co" + +## NextAuth NEXTAUTH_SECRET='run this cmd in your bash terminal (openssl rand -base64 32) with no bracket, and paste it here' NEXTAUTH_URL="for development use http://localhost:3000/ and for production use your domain url" + +## NextJS PROXY_URI="I recommend you to use this cors-anywhere as a proxy https://github.com/Rob--W/cors-anywhere follow the instruction on how to use it there. Skip this if you only use gogoanime as a source" 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 for manga page. get the key from https://anify.tv/discord" \ No newline at end of file +API_KEY="this API key is used for schedules and manga page. get the key from https://anify.tv/discord" +DISQUS_SHORTNAME='put your disqus shortname here. (optional)' \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..878e5d2 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,29 @@ +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +name: Create Release + +jobs: + build: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + body: | + Changes in this Release + - First Change + - Second Change + draft: false + prerelease: false diff --git a/README.md b/README.md index b2618fc..02170e6 100644 --- a/README.md +++ b/README.md @@ -29,21 +29,21 @@
More Screenshots -
Home page after you login
+

Home page after you login

-
Profile Page
+

Profile Page

-
Info page for PC/Mobile
+

Info page for PC/Mobile

-
Watch Page
- +

Watch Page

+ -
Read Page
+

Manga Reader

@@ -56,6 +56,8 @@ - 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 @@ -70,7 +72,7 @@ - [x] Ability to auto track anime after watching >= 90% through the video - [x] Create a user profile page to see lists of anime watched - [x] Ability to edit list inside detail page -- [X] Working on Manga pages +- [x] Working on Manga pages ## Bug Report @@ -93,14 +95,20 @@ npm install 3. Create `.env` file in the root folder and put this inside the file : ```bash +## AniList CLIENT_ID="get the id from here https://anilist.co/settings/developer" CLIENT_SECRET="get the secret from here https://anilist.co/settings/developer" GRAPHQL_ENDPOINT="https://graphql.anilist.co" + +## NextAuth NEXTAUTH_SECRET='run this cmd in your bash terminal (openssl rand -base64 32) with no bracket, and paste it here' NEXTAUTH_URL="for development use http://localhost:3000/ and for production use your domain url" + +## NextJS PROXY_URI="I recommend you to use this cors-anywhere as a proxy https://github.com/Rob--W/cors-anywhere follow the instruction on how to use it there. Skip this if you only use gogoanime as a source" 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 for manga page. get the key from https://anify.tv/discord (key doesn't contain any special character in it)" +API_KEY="this API key is used for schedules and manga page. get the key from https://anify.tv/discord" +DISQUS_SHORTNAME='put your disqus shortname here. (optional)' ``` 4. Add this endpoint as Redirect Url on AniList Developer : @@ -115,6 +123,10 @@ https://your-website-url/api/auth/callback/AniListProvider npm run dev ``` +## Disclaimer + +If you want to host this web app yourself, please try to make significant changes to give it a unique look. The main reason I'm sharing this project as open source is to help others find some guidance, not to encourage copying and pasting. If you end up using this code for your own project, I'd love to see what you come up with! Feel free to share it with me, as I'm excited to see the creative things you can build using this code. :) + ## Credits - [Consumet API](https://github.com/consumet/api.consumet.org) for anime sources @@ -129,6 +141,7 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md ## Contact Thank You for passing by!! + If you have any questions or feedback, please reach out to us at [contact@moopa.live](mailto:contact@moopa.live?subject=[Moopa]%20-%20Your%20Subject), or you can join our [discord sever](https://discord.gg/4xTGhr85BG).
or you can DM me on Discord `Factiven#9110`/`CritenDust#3704`. (just contact me on one of these account) diff --git a/components/anime/changeView.js b/components/anime/changeView.js new file mode 100644 index 0000000..cab9054 --- /dev/null +++ b/components/anime/changeView.js @@ -0,0 +1,107 @@ +import { useEffect, useState } from "react"; + +export default function ChangeView({ view, setView, episode }) { + // const [view, setView] = useState(1); + // const episode = null; + return ( +
+
0 + ? episode?.some((item) => item?.title === null) + ? "pointer-events-none" + : "cursor-pointer" + : "pointer-events-none" + } + onClick={() => { + setView(1); + localStorage.setItem("view", 1); + }} + > + + 0 + ? episode?.some((item) => item?.title === null) + ? "fill-[#1c1c22]" + : view === 1 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + rx="3" + > + +
+
0 + ? episode?.some((item) => item?.title === null) + ? "pointer-events-none" + : "cursor-pointer" + : "pointer-events-none" + } + onClick={() => { + setView(2); + localStorage.setItem("view", 2); + }} + > + 0 + ? episode?.some((item) => item?.title === null) + ? "fill-[#1c1c22]" + : view === 2 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + viewBox="0 0 33 20" + > + + + +
+
0 ? `cursor-pointer` : "pointer-events-none" + } + onClick={() => { + setView(3); + localStorage.setItem("view", 3); + }} + > + 0 + ? view === 3 + ? "fill-action" + : "fill-[#3A3A44]" + : "fill-[#1c1c22]" + }`} + viewBox="0 0 33 20" + > + + + + +
+
+ ); +} diff --git a/components/anime/episode.js b/components/anime/episode.js new file mode 100644 index 0000000..c889c25 --- /dev/null +++ b/components/anime/episode.js @@ -0,0 +1,281 @@ +import { useEffect, useState, Fragment } from "react"; +import { ChevronDownIcon, ClockIcon } from "@heroicons/react/20/solid"; +import { convertSecondsToTime } from "../../utils/getTimes"; +import ChangeView from "./changeView"; +import ThumbnailOnly from "./viewMode/thumbnailOnly"; +import ThumbnailDetail from "./viewMode/thumbnailDetail"; +import ListMode from "./viewMode/listMode"; +import axios from "axios"; + +export default function AnimeEpisode({ info, progress }) { + const [providerId, setProviderId] = useState(); // default provider + const [currentPage, setCurrentPage] = useState(1); // for pagination + const [visible, setVisible] = useState(false); // for mobile view + const itemsPerPage = 13; // choose your number of items per page + + const [loading, setLoading] = useState(true); + const [artStorage, setArtStorage] = useState(null); + const [view, setView] = useState(3); + const [isDub, setIsDub] = useState(false); + + const [providers, setProviders] = useState(null); + + useEffect(() => { + setLoading(true); + setProviders(null); + const fetchData = async () => { + try { + const { data: firstResponse } = await axios.get( + `/api/consumet/episode/${info.id}${isDub === true ? "?dub=true" : ""}` + ); + if (firstResponse.data.length > 0) { + const defaultProvider = firstResponse.data?.find( + (x) => x.providerId === "gogoanime" + ); + setProviderId( + defaultProvider?.providerId || firstResponse.data[0].providerId + ); // set to first provider id + } + + setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); + setProviders(firstResponse.data); + setLoading(false); + } catch (error) { + setLoading(false); + setProviders([]); + } + }; + fetchData(); + }, [info.id, isDub]); + + const episodes = + providers?.find((provider) => provider.providerId === providerId) + ?.episodes || []; + + const lastEpisodeIndex = currentPage * itemsPerPage; + const firstEpisodeIndex = lastEpisodeIndex - itemsPerPage; + const currentEpisodes = episodes.slice(firstEpisodeIndex, lastEpisodeIndex); + const totalPages = Math.ceil(episodes.length / itemsPerPage); + + const handleChange = (event) => { + setProviderId(event.target.value); + }; + + const handlePageChange = (pageNumber) => { + setCurrentPage(pageNumber); + }; + + useEffect(() => { + if (episodes?.some((item) => item?.title === null)) { + setView(3); + } + }, [providerId, episodes]); + + return ( + <> +
+
+
+
+ {info && ( +

+ Episodes +

+ )} + {info?.nextAiringEpisode && ( +
+
+

Next :

+
+ {convertSecondsToTime( + info.nextAiringEpisode.timeUntilAiring + )} +
+
+
+ +
+
+ )} +
+ +
+
setIsDub((prev) => !prev)} + className="flex lg:hidden flex-col items-center relative rounded-md bg-secondary py-1.5 px-3 font-karla text-sm hover:ring-1 ring-action cursor-pointer group" + > + {isDub ? "Dub" : "Sub"} + + Switch to {isDub ? "Sub" : "Dub"} + +
+
setVisible(!visible)} + > + + + +
+
+
+
+ {providers && ( +
setIsDub((prev) => !prev)} + className="hidden lg:flex flex-col items-center relative rounded-[3px] bg-secondary py-1 px-3 font-karla text-sm hover:ring-1 ring-action cursor-pointer group" + > + {isDub ? "Dub" : "Sub"} + + Switch to {isDub ? "Sub" : "Dub"} + +
+ )} + {providers && providers.length > 0 && ( + <> +
+
+ + {/* + Select Providers + */} + +
+ + {totalPages > 1 && ( +
+ + +
+ )} +
+ + )} + + +
+
+ + {/* Episodes */} + {!loading ? ( +
+ {Array.isArray(providers) ? ( + providers.length > 0 ? ( + currentEpisodes.map((episode, index) => { + return ( + + {view === 1 && ( + + )} + {view === 2 && ( + + )} + {view === 3 && ( + + )} + + ); + }) + ) : ( +
+

+ Oops!

It looks like this anime is not available. +

+
+ ) + ) : ( +

{providers.message}

+ )} +
+ ) : ( +
+
+
+
+
+
+
+
+ )} +
+ + ); +} diff --git a/components/anime/infoDetails.js b/components/anime/infoDetails.js new file mode 100644 index 0000000..0cf233c --- /dev/null +++ b/components/anime/infoDetails.js @@ -0,0 +1,203 @@ +import Image from "next/image"; +import Link from "next/link"; +import Skeleton from "react-loading-skeleton"; + +export default function DesktopDetails({ + info, + statuses, + handleOpen, + loading, + color, + setShowAll, + showAll, +}) { + return ( + <> +
+
+ {info ? ( + <> +
+ poster anime + + + ) : ( + + )} +
+ +
+
+

+ {info ? ( + info?.title?.romaji || info?.title?.english + ) : ( + + )} +

+ {info ? ( +
+ {info?.episodes && ( +
+ {info?.episodes} Episodes +
+ )} + {info?.startDate?.year && ( +
+ {info?.startDate?.year} +
+ )} + {info?.averageScore && ( +
+ {info?.averageScore}% +
+ )} + {info?.type && ( +
+ {info?.type} +
+ )} + {info?.status && ( +
+ {info?.status} +
+ )} +
+ Sub | EN +
+
+ ) : ( + + )} +
+ {info ? ( +

+ ) : ( + + )} +

+
+ +
+
+ {info?.relations?.edges?.length > 0 && ( +
+ Relations +
+ )} + {info?.relations?.edges?.length > 3 && ( +
setShowAll(!showAll)} + > + {showAll ? "show less" : "show more"} +
+ )} +
+
+ {info?.relations?.edges ? ( + info?.relations?.edges + .slice(0, showAll ? info?.relations?.edges.length : 3) + .map((r, index) => { + const rel = r.node; + return ( + +
+
+ {rel.id} +
+
+
+ {r.relationType} +
+
+ {rel.title.userPreferred || rel.title.romaji} +
+
{rel.type}
+
+
+ + ); + }) + ) : ( + <> + {[1, 2, 3].map((item) => ( +
+ +
+ ))} +
+ +
+ + )} +
+
+ + ); +} diff --git a/components/anime/mobile/topSection.js b/components/anime/mobile/topSection.js new file mode 100644 index 0000000..4f7c4b3 --- /dev/null +++ b/components/anime/mobile/topSection.js @@ -0,0 +1,81 @@ +import { HeartIcon } from "@heroicons/react/20/solid"; + +import { + TvIcon, + ArrowTrendingUpIcon, + RectangleStackIcon, +} from "@heroicons/react/24/outline"; + +export default function DetailTop({ info, statuses, handleOpen, loading }) { + return ( +
+
+

+ {info?.title?.romaji || info?.title?.english} +

+

+

+ {info?.genres + ?.slice(0, info?.genres?.length > 3 ? info?.genres?.length : 3) + .map((item, index) => ( + + {item} + + ))} +
+ {info && ( +
+
+ +
+ +
+
+
+ )} +
+
+
+ {info && info.status !== "NOT_YET_RELEASED" ? ( + <> +
+ +

{info?.type}

+
+
+ +

{info?.averageScore}%

+
+
+ + {info?.episodes ? ( +

{info?.episodes} Episodes

+ ) : ( +

TBA

+ )} +
+ + ) : ( +
{info && "Not Yet Released"}
+ )} +
+
+
+ ); +} diff --git a/components/anime/viewMode/listMode.js b/components/anime/viewMode/listMode.js new file mode 100644 index 0000000..2016262 --- /dev/null +++ b/components/anime/viewMode/listMode.js @@ -0,0 +1,39 @@ +import Link from "next/link"; + +export default function ListMode({ + info, + episode, + index, + providerId, + progress, + dub, +}) { + return ( +
+ +

Episode {episode.number}

+ {episode.title && ( +

+ "{episode.title}" +

+ )} + + {index !== episode?.length - 1 && } +
+ ); +} diff --git a/components/anime/viewMode/thumbnailDetail.js b/components/anime/viewMode/thumbnailDetail.js new file mode 100644 index 0000000..a085bc7 --- /dev/null +++ b/components/anime/viewMode/thumbnailDetail.js @@ -0,0 +1,76 @@ +import Image from "next/image"; +import Link from "next/link"; + +export default function ThumbnailDetail({ + index, + epi, + info, + provider, + artStorage, + progress, + dub, +}) { + const time = artStorage?.[epi?.id]?.time; + const duration = artStorage?.[epi?.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + + return ( + +
+
+ Anime Cover + + + Episode {epi?.number} + +
+ + + +
+
+
+ +
+

+ {epi?.title} +

+ {epi?.description && ( +

+ {epi?.description} +

+ )} +
+ + ); +} diff --git a/components/anime/viewMode/thumbnailOnly.js b/components/anime/viewMode/thumbnailOnly.js new file mode 100644 index 0000000..6063dfc --- /dev/null +++ b/components/anime/viewMode/thumbnailOnly.js @@ -0,0 +1,59 @@ +import Image from "next/image"; +import Link from "next/link"; + +export default function ThumbnailOnly({ + info, + providerId, + episode, + artStorage, + progress, + dub, +}) { + const time = artStorage?.[episode?.id]?.time; + const duration = artStorage?.[episode?.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + return ( + + + Episode {episode?.number} + + +
+ epi image + + ); +} diff --git a/components/anime/watch/primary/details.js b/components/anime/watch/primary/details.js new file mode 100644 index 0000000..94c3360 --- /dev/null +++ b/components/anime/watch/primary/details.js @@ -0,0 +1,177 @@ +import { useEffect, useState } from "react"; +import { useAniList } from "../../../../lib/anilist/useAnilist"; +import Skeleton from "react-loading-skeleton"; +import DisqusComments from "../../../disqus"; +import Image from "next/image"; + +export default function Details({ + info, + session, + epiNumber, + id, + onList, + setOnList, + handleOpen, + disqus, +}) { + const [showComments, setShowComments] = useState(false); + const { markPlanning } = useAniList(session); + const [url, setUrl] = useState(null); + + function handlePlan() { + if (onList === false) { + markPlanning(info.id); + setOnList(true); + } + } + + useEffect(() => { + const url = window.location.href; + setShowComments(false); + setUrl(url); + }, [id]); + + return ( +
+
+
+ {info ? ( + Anime Cover + ) : ( + + )} +
+
+
+

+ Studios +

+
+ {info ? info.studios.edges[0].node.name : } +
+
+
+ { + session ? handlePlan() : handleOpen(); + }} + className={`w-8 h-8 hover:fill-white text-white hover:cursor-pointer ${ + onList ? "fill-white" : "" + }`} + > + + +
+
+
+
+

+ Status +

+
{info ? info.status : }
+
+
+

+ Titles +

+
+ {info ? ( + <> +
{info.title?.romaji || ""}
+
+ {info.title?.english || ""} +
+
{info.title?.native || ""}
+ + ) : ( + + )} +
+
+
+
+
+ {info && + info.genres?.map((item, index) => ( +
+ {item} +
+ ))} +
+
+ {info && ( +

+ )} +

+ {/* {
} */} + {!showComments && ( +
+ +
+ )} + {showComments && ( +
+ {info && url && ( +
+ +
+ )} +
+ )} +
+ ); +} diff --git a/components/anime/watch/primarySide.js b/components/anime/watch/primarySide.js new file mode 100644 index 0000000..49bb1b6 --- /dev/null +++ b/components/anime/watch/primarySide.js @@ -0,0 +1,213 @@ +import { useEffect, useState } from "react"; +import { ChevronDownIcon } from "@heroicons/react/20/solid"; +import { ForwardIcon } from "@heroicons/react/24/solid"; +import { useRouter } from "next/router"; +import { signIn } from "next-auth/react"; +import Details from "./primary/details"; +import VideoPlayer from "../../videoPlayer"; +import Link from "next/link"; +import Skeleton from "react-loading-skeleton"; +import Modal from "../../modal"; +import AniList from "../../media/aniList"; +import axios from "axios"; + +export default function PrimarySide({ + info, + session, + epiNumber, + setLoading, + navigation, + loading, + providerId, + watchId, + status, + onList, + proxy, + disqus, + setOnList, + episodeList, +}) { + const [episodeData, setEpisodeData] = useState(); + const [open, setOpen] = useState(false); + const [skip, setSkip] = useState(); + + const router = useRouter(); + + useEffect(() => { + setLoading(true); + setEpisodeData(); + setSkip(); + async function fetchData() { + if (info) { + const { data } = await axios.get( + `/api/consumet/source/${providerId}/${watchId}` + ); + + const skip = await fetch( + `https://api.aniskip.com/v2/skip-times/${info.idMal}/${parseInt( + epiNumber + )}?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=` + ).then((r) => { + if (!r.ok) { + switch (r.status) { + case 404: { + return null; + } + } + } + return r.json(); + }); + + const op = + skip?.results?.find((item) => item.skipType === "op") || null; + const ed = + skip?.results?.find((item) => item.skipType === "ed") || null; + + setSkip({ op, ed }); + + setEpisodeData(data); + setLoading(false); + } + // setMal(malId); + } + + fetchData(); + }, [providerId, watchId, info]); + + function handleOpen() { + setOpen(true); + document.body.style.overflow = "hidden"; + } + + function handleClose() { + setOpen(false); + document.body.style.overflow = "auto"; + } + + return ( + <> + handleClose()}> + {!session && ( +
+

+ Edit your list +

+ +
+ )} +
+
+
+ {!loading ? ( + episodeData && ( + + ) + ) : ( +
+ )} +
+
+ {info && episodeList ? ( +
+
+

+ + {navigation?.playing?.title || info.title?.romaji} + +

+

+ Episode {epiNumber} +

+
+
+
+ + +
+ +
+
+ ) : ( +
+
+
+ +
+
+

+ +

+
+ )} +
+
+
+ + ); +} diff --git a/components/anime/watch/secondarySide.js b/components/anime/watch/secondarySide.js new file mode 100644 index 0000000..e3f0224 --- /dev/null +++ b/components/anime/watch/secondarySide.js @@ -0,0 +1,129 @@ +import Skeleton from "react-loading-skeleton"; +import Image from "next/image"; +import Link from "next/link"; + +export default function SecondarySide({ + info, + providerId, + watchId, + episode, + progress, + artStorage, + dub, +}) { + return ( +
+

Up Next

+
+ {episode && episode.length > 0 ? ( + episode.some((item) => item.title && item.description) > 0 ? ( + episode.map((item) => { + const time = artStorage?.[item.id]?.time; + const duration = artStorage?.[item.id]?.duration; + let prog = (time / duration) * 100; + if (prog > 90) prog = 100; + return ( + +
+
+ Anime Cover + + + Episode {item.number} + + {item.id == watchId && ( +
+ + + +
+ )} +
+
+
+

+ {item.title} +

+

+ {item?.description} +

+
+ + ); + }) + ) : ( + episode.map((item) => { + return ( + + Episode {item.number} + + ); + }) + ) + ) : ( + <> + {[1].map((item) => ( + + ))} + + )} +
+
+ ); +} diff --git a/components/disqus.js b/components/disqus.js index b276995..724bec3 100644 --- a/components/disqus.js +++ b/components/disqus.js @@ -1,7 +1,7 @@ import { DiscussionEmbed } from "disqus-react"; const DisqusComments = ({ post }) => { - const disqusShortname = "your_disqus_shortname"; + const disqusShortname = post.name || "your_disqus_shortname"; const disqusConfig = { url: post.url, identifier: post.id, // Single post id diff --git a/components/home/content.js b/components/home/content.js index 9b2b1a9..9d41fe9 100644 --- a/components/home/content.js +++ b/components/home/content.js @@ -151,7 +151,7 @@ export default function Content({ ids, section, data, og, userName }) {
{ids === "onGoing" && ( -
+

{anime.title.romaji || anime.title.english} @@ -217,7 +217,7 @@ export default function Content({ ids, section, data, og, userName }) { alt={ anime.title.romaji || anime.title.english || "coverImage" } - width={209} + width={500} height={300} placeholder="blur" blurDataURL={ @@ -229,6 +229,21 @@ export default function Content({ ids, section, data, og, userName }) { className="z-20 h-[190px] w-[135px] lg:h-[265px] lg:w-[185px] object-cover rounded-md brightness-90" /> + {ids !== "onGoing" && ( + +

+ {anime.status === "RELEASING" ? ( + + ) : anime.status === "NOT_YET_RELEASED" ? ( + + ) : null} + {anime.title.romaji} +

+ + )}
); })} diff --git a/components/home/genres.js b/components/home/genres.js index ac67260..3eefecd 100644 --- a/components/home/genres.js +++ b/components/home/genres.js @@ -57,7 +57,7 @@ export default function Genres() {
-
+
{g.map((a, index) => (
-

Coming Up Next!

+

Coming Up Next!

{data.title.romaji || data.title.english || data.title.native} diff --git a/components/listEditor.js b/components/listEditor.js index d88f2af..49aa38e 100644 --- a/components/listEditor.js +++ b/components/listEditor.js @@ -3,7 +3,7 @@ import Image from "next/image"; import { toast } from "react-toastify"; const ListEditor = ({ animeId, session, stats, prg, max, image = null }) => { - const [status, setStatus] = useState(stats ?? ""); + const [status, setStatus] = useState(stats ?? "CURRENT"); const [progress, setProgress] = useState(prg ?? 0); const handleSubmit = async (e) => { diff --git a/components/videoPlayer.js b/components/videoPlayer.js index 22e6916..301812a 100644 --- a/components/videoPlayer.js +++ b/components/videoPlayer.js @@ -25,8 +25,7 @@ export default function VideoPlayer({ session, aniId, stats, - op, - ed, + skip, title, poster, proxy, @@ -77,17 +76,13 @@ export default function VideoPlayer({ return { ...(isDefault && { default: true }), html: items.quality === "default" ? "adaptive" : items.quality, - // url: `${proxy}${items.url}`, url: provider === "gogoanime" - ? `https://cors.moopa.my.id/?url=${encodeURIComponent( + ? `https://cors.moopa.workers.dev/?url=${encodeURIComponent( items.url )}${referer ? `&referer=${encodeURIComponent(referer)}` : ""}` : `${proxy}${items.url}`, }; - // url: `https://m3u8proxy.moopa.workers.dev/?url=${encodeURIComponent(items.url)}${ - // referer ? `&referer=${encodeURIComponent(referer)}` : "" - // }`, }); const defSource = source?.find((i) => i?.default === true); @@ -109,19 +104,12 @@ export default function VideoPlayer({ }); const defSub = data?.subtitles.find((i) => i.lang === "English"); - // const thumb = data?.subtitles.find((i) => i.lang === "Thumbnails"); - // setThumbnails(thumb?.url); setDefSub(defSub?.url); - // console.log(subtitle); setSubtitle(subtitle); } - // const defUrl = `https://cors.moopa.my.id/?url=${encodeURIComponent( - // sumber.url - // )}${referer ? `&referer=${encodeURIComponent(referer)}` : ""}`; - setSource(source); } catch (error) { console.error(error); @@ -213,6 +201,8 @@ export default function VideoPlayer({ } }); + let marked = 0; + art.on("video:timeupdate", () => { if (!session) return; const mediaSession = navigator.mediaSession; @@ -228,9 +218,11 @@ export default function VideoPlayer({ if (percentage >= 0.9) { // use >= instead of > - markProgress(aniId, progress, stats); - art.off("video:timeupdate"); - console.log("Video progress marked"); + if (marked < 1) { + marked = 1; + markProgress(aniId, progress, stats); + // console.log("Video progress marked"); + } } }); @@ -243,9 +235,9 @@ export default function VideoPlayer({ }); if ( - op && - currentTime >= op.interval.startTime && - currentTime <= op.interval.endTime + skip?.op && + currentTime >= skip.op.interval.startTime && + currentTime <= skip.op.interval.endTime ) { // Add the layer if it's not already added if (!art.controls["op"]) { @@ -260,14 +252,14 @@ export default function VideoPlayer({ position: "top", html: '', click: function (...args) { - art.seek = op.interval.endTime; + art.seek = skip.op.interval.endTime; }, }); } } else if ( - ed && - currentTime >= ed.interval.startTime && - currentTime <= ed.interval.endTime + skip?.ed && + currentTime >= skip.ed.interval.startTime && + currentTime <= skip.ed.interval.endTime ) { // Add the layer if it's not already added if (!art.controls["ed"]) { @@ -282,7 +274,7 @@ export default function VideoPlayer({ position: "top", html: '', click: function (...args) { - art.seek = ed.interval.endTime; + art.seek = skip.ed.interval.endTime; }, }); } @@ -296,13 +288,6 @@ export default function VideoPlayer({ } } }); - - // art.on("destroy", async () => { - // art.storage.set(id, { - // time: art.currentTime, - // duration: art.duration, - // }); - // }); }} /> )} diff --git a/lib/anilist/AniList.js b/lib/anilist/AniList.js index f602dad..f5fe19c 100644 --- a/lib/anilist/AniList.js +++ b/lib/anilist/AniList.js @@ -24,6 +24,7 @@ export async function aniListData({ sort, page = 1 }) { media(id: $id, search: $search, sort: $sort type: ANIME) { id idMal + status title { romaji english diff --git a/lib/anilist/useAnilist.js b/lib/anilist/useAnilist.js index 4ec55a9..bedb4a5 100644 --- a/lib/anilist/useAnilist.js +++ b/lib/anilist/useAnilist.js @@ -92,6 +92,22 @@ export function useAniList(session, stats) { ); }; + const markPlanning = (mediaId) => { + if (!accessToken) return; + const completeQuery = ` + mutation($mediaId: Int ) { + SaveMediaListEntry(mediaId: $mediaId, status: PLANNING) { + id + mediaId + status + } + } + `; + fetchGraphQL(completeQuery, { mediaId }).then((data) => + console.log({ added_to_list: data }) + ); + }; + const markProgress = (mediaId, progress, stats, volumeProgress) => { if (!accessToken) return; const progressWatched = ` @@ -122,5 +138,5 @@ export function useAniList(session, stats) { }); }; - return { media, markComplete, markProgress }; + return { media, markComplete, markProgress, markPlanning }; } diff --git a/lib/useCountdownSeconds.js b/lib/useCountdownSeconds.js deleted file mode 100644 index df3cb63..0000000 --- a/lib/useCountdownSeconds.js +++ /dev/null @@ -1,37 +0,0 @@ -import { useEffect, useState } from "react"; - -const useCountdown = (targetDate, update) => { - const countDownDate = new Date(targetDate).getTime(); - - const [countDown, setCountDown] = useState( - countDownDate - new Date().getTime() - ); - - useEffect(() => { - const interval = setInterval(() => { - const newCountDown = countDownDate - new Date().getTime(); - setCountDown(newCountDown); - if (newCountDown <= 0 && newCountDown > -1000) { - update(); - } - }, 1000); - - return () => clearInterval(interval); - }, [countDownDate, update]); - - return getReturnValues(countDown); -}; - -const getReturnValues = (countDown) => { - // calculate time left - const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); - const hours = Math.floor( - (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) - ); - const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); - const seconds = Math.floor((countDown % (1000 * 60)) / 1000); - - return [days, hours, minutes, seconds]; -}; - -export { useCountdown }; diff --git a/next.config.js b/next.config.js index 6150453..6d1cf50 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,5 @@ /** @type {import('next').NextConfig} */ +// const { createSecureHeaders } = require("next-secure-headers"); const withPWA = require("next-pwa")({ dest: "public", @@ -17,5 +18,56 @@ module.exports = withPWA({ }, ], }, + distDir: process.env.BUILD_DIR || ".next", trailingSlash: true, + // async headers() { + // return [ + // { + // // matching all API routes + // source: "/api/:path*", + // headers: [ + // { key: "Access-Control-Allow-Credentials", value: "true" }, + // { + // key: "Access-Control-Allow-Origin", + // value: "https://moopa.live", + // }, // replace this your actual origin + // { + // key: "Access-Control-Allow-Methods", + // value: "GET,DELETE,PATCH,POST,PUT", + // }, + // { + // key: "Access-Control-Allow-Headers", + // value: + // "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version", + // }, + // ], + // }, + // { + // source: "/(.*)", + // headers: createSecureHeaders({ + // contentSecurityPolicy: { + // directives: { + // styleSrc: [ + // "'self'", + // "'unsafe-inline'", + // "https://cdnjs.cloudflare.com", + // "https://fonts.googleapis.com", + // ], + // imgSrc: [ + // "'self'", + // "https://s4.anilist.co", + // "data:", + // "https://media.kitsu.io", + // "https://artworks.thetvdb.com", + // "https://img.moopa.live", + // ], + // baseUri: "self", + // formAction: "self", + // frameAncestors: true, + // }, + // }, + // }), + // }, + // ]; + // }, }); diff --git a/package-lock.json b/package-lock.json index 2b68db9..9a86a84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,31 +1,32 @@ { "name": "moopa", - "version": "3.6.7", + "version": "3.8.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "moopa", - "version": "3.6.7", + "version": "3.8.5", "dependencies": { "@apollo/client": "^3.7.3", "@headlessui/react": "^1.7.15", "@heroicons/react": "^2.0.17", - "@next/font": "13.0.7", "@vercel/og": "^0.5.4", "artplayer": "^5.0.9", "artplayer-plugin-hls-quality": "^2.0.0", + "axios": "^1.4.0", "closest-match": "^1.3.3", + "cron": "^2.4.0", "disqus-react": "^1.1.5", "dotenv": "^16.0.3", "framer-motion": "^8.5.0", - "gql": "^1.1.2", "graphql": "^15.8.0", "hls.js": "^1.3.2", + "memory-cache": "^0.2.0", "next": "13.0.7", "next-auth": "^4.22.0", "next-pwa": "^5.6.0", - "next-themes": "^0.2.1", + "next-secure-headers": "^2.2.0", "nextjs-progressbar": "^0.0.16", "nookies": "^2.5.2", "react": "18.2.0", @@ -36,11 +37,10 @@ "tailwind-scrollbar-hide": "^1.1.7" }, "devDependencies": { - "autoprefixer": "^10.4.13", + "autoprefixer": "^10.4.14", + "depcheck": "^1.4.3", "eslint": "^8.38.0", "eslint-config-next": "12.1.6", - "postcss": "^8.4.20", - "prettier": "^2.8.3", "tailwind-scrollbar": "^2.1.0", "tailwindcss": "^3.3.1" } @@ -2054,11 +2054,6 @@ "glob": "7.1.7" } }, - "node_modules/@next/font": { - "version": "13.0.7", - "resolved": "https://registry.npmjs.org/@next/font/-/font-13.0.7.tgz", - "integrity": "sha512-39SzuoMI6jbrIzPs3KtXdKX03OrVp6Y7kRHcoVmOg69spiBzruPJ5x5DQSfN+OXqznbvVBNZBXnmdnSqs3qXiA==" - }, "node_modules/@next/swc-android-arm-eabi": { "version": "13.0.7", "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.7.tgz", @@ -2485,6 +2480,12 @@ "resolved": "https://registry.npmjs.org/@types/nprogress/-/nprogress-0.2.0.tgz", "integrity": "sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A==" }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -2612,6 +2613,117 @@ "node": ">=16" } }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-core/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-sfc/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/compiler-sfc/node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/reactivity-transform/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, + "node_modules/@vue/reactivity-transform/node_modules/magic-string": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz", + "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==", + "dev": true + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", @@ -2888,7 +3000,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2930,6 +3042,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", @@ -3033,6 +3154,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/artplayer": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/artplayer/-/artplayer-5.0.9.tgz", @@ -3057,6 +3187,11 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", @@ -3118,6 +3253,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -3206,7 +3351,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -3299,6 +3444,18 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -3354,7 +3511,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, + "devOptional": true, "funding": [ { "type": "individual", @@ -3381,7 +3538,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "devOptional": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -3417,6 +3574,17 @@ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/closest-match": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/closest-match/-/closest-match-1.3.3.tgz", @@ -3446,6 +3614,17 @@ "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", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3498,6 +3677,39 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dev": true, + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cron": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.4.0.tgz", + "integrity": "sha512-Cx77ic1TyIAtUggr0oAhtS8MLzPBUqGNIvdDM7jE3oFIxfe8LXWI9q3iQN/H2CebAiMir53LQKWOhEKnzkJTAQ==", + "dependencies": { + "luxon": "^3.2.1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3673,6 +3885,91 @@ "rimraf": "bin.js" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depcheck": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/depcheck/-/depcheck-1.4.3.tgz", + "integrity": "sha512-vy8xe1tlLFu7t4jFyoirMmOR7x7N601ubU9Gkifyr9z8rjBFtEdWHDBMqXyk6OkK+94NXutzddVXJuo0JlUQKQ==", + "dev": true, + "dependencies": { + "@babel/parser": "7.16.4", + "@babel/traverse": "^7.12.5", + "@vue/compiler-sfc": "^3.0.5", + "camelcase": "^6.2.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.2.0", + "deps-regex": "^0.1.4", + "ignore": "^5.1.8", + "is-core-module": "^2.4.0", + "js-yaml": "^3.14.0", + "json5": "^2.1.3", + "lodash": "^4.17.20", + "minimatch": "^3.0.4", + "multimatch": "^5.0.0", + "please-upgrade-node": "^3.2.0", + "query-ast": "^1.0.3", + "readdirp": "^3.5.0", + "require-package-name": "^2.0.1", + "resolve": "^1.18.1", + "sass": "^1.29.0", + "scss-parser": "^1.0.4", + "semver": "^7.3.2", + "yargs": "^16.1.0" + }, + "bin": { + "depcheck": "bin/depcheck.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/depcheck/node_modules/@babel/parser": { + "version": "7.16.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz", + "integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/depcheck/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/depcheck/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/deps-regex": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.1.4.tgz", + "integrity": "sha512-3tzwGYogSJi8HoG93R5x9NrdefZQOXgHgGih/7eivloOq6yC6O+yoFxZnkgP661twvfILONfoKRdF9GQOGx2RA==", + "dev": true + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3783,6 +4080,15 @@ "node": ">=10.13.0" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", @@ -4293,6 +4599,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -4502,6 +4821,25 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4510,6 +4848,19 @@ "is-callable": "^1.1.3" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", @@ -4610,6 +4961,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", @@ -4740,14 +5100,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gql": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/gql/-/gql-1.1.2.tgz", - "integrity": "sha512-fF+1HtQqLdv1LtA6fntKXzuUPVHaQYoVwL60bKpZK4VCpTVcdMijOxSLPUKjj2PAyU1vrHAk7D9EpkBw89CyoQ==", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4897,6 +5249,12 @@ "node": ">= 4" } }, + "node_modules/immutable": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.1.tgz", + "integrity": "sha512-lj9cnmB/kVS0QHsJnYKD1uo3o39nrbKxszjnqS9Fr6NB7bZzW45U6WSGBPKXDL/CvDKqDNPA4r3DoDQ8GTxo2A==", + "devOptional": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -4949,6 +5307,15 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -4962,6 +5329,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "node_modules/is-bigint": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", @@ -4977,7 +5350,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -5044,6 +5417,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -5345,8 +5727,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "peer": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema": { "version": "0.4.0", @@ -5554,6 +5935,14 @@ "yallist": "^3.0.2" } }, + "node_modules/luxon": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", + "engines": { + "node": ">=12" + } + }, "node_modules/magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -5584,6 +5973,11 @@ "semver": "bin/semver.js" } }, + "node_modules/memory-cache": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", + "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5613,7 +6007,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "peer": true, "engines": { "node": ">= 0.6" } @@ -5622,7 +6015,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5655,6 +6047,31 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/multimatch": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", + "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/multimatch/node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -5789,14 +6206,12 @@ "next": ">=9.0.0" } }, - "node_modules/next-themes": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", - "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", - "peerDependencies": { - "next": "*", - "react": "*", - "react-dom": "*" + "node_modules/next-secure-headers": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/next-secure-headers/-/next-secure-headers-2.2.0.tgz", + "integrity": "sha512-C7OfZ9JdSJyYMz2ZBMI/WwNbt0qNjlFWX9afUp8nEUzbz6ez3JbeopdyxSZJZJAzVLIAfyk6n73rFpd4e22jRg==", + "engines": { + "node": ">=10.0.0" } }, "node_modules/next/node_modules/postcss": { @@ -5862,7 +6277,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -6159,6 +6574,24 @@ "hex-rgb": "^4.1.0" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6313,6 +6746,15 @@ "node": ">=8" } }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, "node_modules/postcss": { "version": "8.4.26", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.26.tgz", @@ -6443,21 +6885,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -6484,6 +6911,11 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -6492,6 +6924,16 @@ "node": ">=6" } }, + "node_modules/query-ast": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/query-ast/-/query-ast-1.0.5.tgz", + "integrity": "sha512-JK+1ma4YDuLjvKKcz9JZ70G+CM9qEOs/l1cZzstMMfwKUabTJ9sud5jvDGrUNuv03yKUgs82bLkHXJkDyhRmBw==", + "dev": true, + "dependencies": { + "invariant": "2.2.4", + "lodash": "^4.17.21" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -6597,7 +7039,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -6685,6 +7127,15 @@ "jsesc": "bin/jsesc" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -6693,6 +7144,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-package-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", + "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==", + "dev": true + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -6871,6 +7328,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sass": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", + "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==", + "devOptional": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/satori": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/satori/-/satori-0.10.1.tgz", @@ -6921,6 +7395,19 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/scss-parser": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/scss-parser/-/scss-parser-1.0.6.tgz", + "integrity": "sha512-SH3TaoaJFzfAtqs3eG1j5IuHJkeEW5rKUPIjIN+ZorLAyJLHItQGnsgwHk76v25GtLtpT9IqfAcqK4vFWdiw+w==", + "dev": true, + "dependencies": { + "invariant": "2.2.4", + "lodash": "4.17.21" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6936,6 +7423,12 @@ "node": ">=10" } }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, "node_modules/semver/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -7045,6 +7538,32 @@ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", "deprecated": "Please use @jridgewell/sourcemap-codec instead" }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "node_modules/string.prototype.codepointat": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", @@ -8253,11 +8772,37 @@ "workbox-core": "6.6.0" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -8272,6 +8817,33 @@ "node": ">= 14" } }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index c34d31b..f170b98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moopa", - "version": "3.6.7", + "version": "3.8.5", "private": true, "founder": "Factiven", "scripts": { @@ -14,21 +14,22 @@ "@apollo/client": "^3.7.3", "@headlessui/react": "^1.7.15", "@heroicons/react": "^2.0.17", - "@next/font": "13.0.7", "@vercel/og": "^0.5.4", "artplayer": "^5.0.9", "artplayer-plugin-hls-quality": "^2.0.0", + "axios": "^1.4.0", "closest-match": "^1.3.3", + "cron": "^2.4.0", "disqus-react": "^1.1.5", "dotenv": "^16.0.3", "framer-motion": "^8.5.0", - "gql": "^1.1.2", "graphql": "^15.8.0", "hls.js": "^1.3.2", + "memory-cache": "^0.2.0", "next": "13.0.7", "next-auth": "^4.22.0", "next-pwa": "^5.6.0", - "next-themes": "^0.2.1", + "next-secure-headers": "^2.2.0", "nextjs-progressbar": "^0.0.16", "nookies": "^2.5.2", "react": "18.2.0", @@ -39,11 +40,10 @@ "tailwind-scrollbar-hide": "^1.1.7" }, "devDependencies": { - "autoprefixer": "^10.4.13", + "autoprefixer": "^10.4.14", + "depcheck": "^1.4.3", "eslint": "^8.38.0", "eslint-config-next": "12.1.6", - "postcss": "^8.4.20", - "prettier": "^2.8.3", "tailwind-scrollbar": "^2.1.0", "tailwindcss": "^3.3.1" } diff --git a/pages/404.js b/pages/404.js index 746e8fa..c774372 100644 --- a/pages/404.js +++ b/pages/404.js @@ -32,74 +32,7 @@ export default function Custom404() {
- - - - - - - - - - - - - - - - - - - - - - - + 404

Oops! Page not found

diff --git a/pages/api/anify/schedule.js b/pages/api/anify/schedule.js new file mode 100644 index 0000000..99f10d6 --- /dev/null +++ b/pages/api/anify/schedule.js @@ -0,0 +1,53 @@ +import axios from "axios"; +import cacheData from "memory-cache"; +import cron from "cron"; + +const API_KEY = process.env.API_KEY; + +// Function to fetch new data +async function fetchData() { + try { + const { data } = await axios.get( + `https://api.anify.tv/schedule?apikey=${API_KEY}` + ); + return data; + } catch (error) { + console.error("Error fetching data:", error); + return null; + } +} + +// Function to refresh the cache with new data +async function refreshCache() { + const newData = await fetchData(); + if (newData) { + cacheData.put("schedule", newData, 1000 * 60 * 15); + console.log("Cache refreshed successfully."); + } +} + +// Schedule cache refresh every Monday at 00:00 AM (local time) +const job = new cron.CronJob("0 0 * * 1", () => { + refreshCache(); +}); +job.start(); + +export default async function handler(req, res) { + try { + const cached = cacheData.get("schedule"); + if (cached) { + return res.status(200).json(cached); + } else { + const data = await fetchData(); + + if (data) { + res.status(200).json(data); + cacheData.put("schedule", data, 1000 * 60 * 60 * 24 * 7); + } else { + res.status(404).json({ message: "Schedule not found" }); + } + } + } catch (error) { + res.status(500).json({ error }); + } +} diff --git a/pages/api/consumet/episode/[id].js b/pages/api/consumet/episode/[id].js new file mode 100644 index 0000000..60bb4fb --- /dev/null +++ b/pages/api/consumet/episode/[id].js @@ -0,0 +1,63 @@ +import axios from "axios"; +import cacheData from "memory-cache"; + +const API_URL = process.env.API_URI; + +export default async function handler(req, res) { + try { + const id = req.query.id; + const dub = req.query.dub || false; + + const providers = ["enime", "gogoanime"]; + const datas = []; + + const cached = cacheData.get(id + dub); + if (cached) { + return res.status(200).json(cached); + } else { + async function fetchData(provider) { + try { + const data = await fetch( + dub && provider === "gogoanime" + ? `${API_URL}/meta/anilist/info/${id}?dub=true` + : `${API_URL}/meta/anilist/info/${id}?provider=${provider}` + ).then((res) => { + if (!res.ok) { + switch (res.status) { + case 404: { + return null; + } + } + } + return res.json(); + }); + if (data.episodes.length > 0) { + datas.push({ + providerId: provider, + episodes: dub ? data.episodes : data.episodes.reverse(), + }); + } + } catch (error) { + console.error( + `Error fetching data for provider '${provider}':`, + error + ); + } + } + if (dub === false) { + await Promise.all(providers.map((provider) => fetchData(provider))); + } else { + await fetchData("gogoanime"); + } + + if (datas.length === 0) { + return res.status(404).json({ message: "Anime not found" }); + } else { + cacheData.put(id + dub, { data: datas }, 1000 * 60 * 60 * 15); // 15 minutes + res.status(200).json({ data: datas }); + } + } + } catch (error) { + res.status(500).json({ error }); + } +} diff --git a/pages/api/consumet/source/[...params].js b/pages/api/consumet/source/[...params].js new file mode 100644 index 0000000..e589d4a --- /dev/null +++ b/pages/api/consumet/source/[...params].js @@ -0,0 +1,36 @@ +import axios from "axios"; +import cacheData from "memory-cache"; + +const API_URL = process.env.API_URI; + +export default async function handler(req, res) { + const query = req.query.params; + try { + const provider = query[0]; + const id = query[1]; + + const cached = cacheData.get(id); + if (cached) { + return res.status(200).json(cached); + } else { + let datas; + + const { data } = await axios.get( + `${API_URL}/meta/anilist/watch/${id}?provider=${provider}` + ); + + if (data) { + datas = data; + cacheData.put(id, data, 1000 * 60 * 5); + } + + if (!datas) { + return res.status(404).json({ message: "Source not found" }); + } + + res.status(200).json(datas); + } + } catch (error) { + res.status(500).json({ error }); + } +} diff --git a/pages/en/anime/[...id].js b/pages/en/anime/[...id].js index 86396e3..0b83f24 100644 --- a/pages/en/anime/[...id].js +++ b/pages/en/anime/[...id].js @@ -1,22 +1,8 @@ -import Skeleton from "react-loading-skeleton"; - -import { - ChevronDownIcon, - ClockIcon, - HeartIcon, -} from "@heroicons/react/20/solid"; -import { - TvIcon, - ArrowTrendingUpIcon, - RectangleStackIcon, -} from "@heroicons/react/24/outline"; - import Head from "next/head"; import Image from "next/image"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import Layout from "../../../components/layout"; -import Link from "next/link"; import Content from "../../../components/home/content"; import Modal from "../../../components/modal"; @@ -28,252 +14,75 @@ import { GET_MEDIA_USER } from "../../../queries"; import { GET_MEDIA_INFO } from "../../../queries"; import { ToastContainer } from "react-toastify"; -import { convertSecondsToTime } from "../../../utils/getTimes"; - -// import { aniInfo } from "../../components/devComp/data"; -// console.log(GET_MEDIA_USER); -export default function Info({ info, color, api }) { - // Episodes dropdown - const [firstEpisodeIndex, setFirstEpisodeIndex] = useState(0); - const [lastEpisodeIndex, setLastEpisodeIndex] = useState(); - const [selectedRange, setSelectedRange] = useState("All"); - function onEpisodeIndexChange(e) { - if (e.target.value === "All") { - setFirstEpisodeIndex(0); - setLastEpisodeIndex(); - setSelectedRange("All"); - return; - } - setFirstEpisodeIndex(e.target.value.split("-")[0] - 1); - setLastEpisodeIndex(e.target.value.split("-")[1]); - setSelectedRange(e.target.value); - } +import DetailTop from "../../../components/anime/mobile/topSection"; +import DesktopDetails from "../../../components/anime/infoDetails"; +import AnimeEpisode from "../../../components/anime/episode"; +export default function Info({ info, color }) { const { data: session } = useSession(); - const [episode, setEpisode] = useState(null); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(0); const [statuses, setStatuses] = useState(null); const [domainUrl, setDomainUrl] = useState(""); const [showAll, setShowAll] = useState(false); - const [visible, setVisible] = useState(false); const [open, setOpen] = useState(false); - const [time, setTime] = useState(0); const { id } = useRouter().query; - const [epiView, setEpiView] = useState("3"); - - const [artStorage, setArtStorage] = useState(null); - const rec = info?.recommendations?.nodes?.map( (data) => data.mediaRecommendation ); - const [provider, setProvider] = useState(); - const [prvValue, setPrvValue] = useState("gogoanime"); - - const [availableProviders, setAvailableProviders] = useState([]); - // const [err, setErr] = useState(''); - - function handleProvider(e) { - setEpisode( - Array.isArray(provider[e.target.value]) - ? provider[e.target.value]?.reverse() - : provider[e.target.value] - ); - setPrvValue(e.target.value); - localStorage.setItem("provider", e.target.value); - } - - //for episodes dropdown - useEffect(() => { - setFirstEpisodeIndex(0); - setLastEpisodeIndex(); - setSelectedRange("All"); - }, [info, prvValue]); - useEffect(() => { handleClose(); async function fetchData() { setLoading(true); if (id) { try { - const { protocol, host } = window.location; - const prv = localStorage.getItem("provider"); - const url = `${protocol}//${host}`; - - const view = localStorage.getItem("epiView"); - - if (prv) { - setPrvValue(prv); - } else { - setPrvValue("gogoanime"); - } - - setDomainUrl(url); + setDomainUrl(window.location.origin); - setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); - - setEpisode(null); setProgress(0); setStatuses(null); - let reloadCount = 0; - - try { - const fetchPromises = [ - fetch(`${api}/meta/anilist/info/${info.id}?provider=enime`), - fetch(`${api}/meta/anilist/info/${info.id}?provider=zoro`), - fetch(`${api}/meta/anilist/info/${info.id}?provider=gogoanime`), - ]; - - const results = await Promise.allSettled(fetchPromises); - const successfulResponses = []; - let errorCount = 0; - - results.forEach((result) => { - if (result.status === "fulfilled") { - successfulResponses.push(result.value); - } else { - errorCount++; - } - }); - - if (errorCount === fetchPromises.length) { - // All fetch requests failed, handle the error here - setEpisode([]); - } else { - // Process the successfulResponses here - const responsesData = await Promise.all( - successfulResponses.map((response) => response.json()) - ); - const [enime, zoro, gogoanime] = responsesData; - - const prov = { - enime: enime?.episodes || enime, - zoro: zoro?.episodes || zoro, - gogoanime: gogoanime?.episodes || gogoanime, - }; - - const aPrv = [ - { - name: "enime", - available: - enime?.episodes && enime?.episodes.length > 0 - ? true - : false, + if (session?.user?.name) { + const response = await fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: GET_MEDIA_USER, + variables: { + username: session?.user?.name, }, - { - name: "zoro", - available: - zoro?.episodes && zoro?.episodes.length > 0 ? true : false, - }, - { - name: "gogoanime", - available: - gogoanime?.episodes && gogoanime?.episodes.length > 0 - ? true - : false, - }, - ]; - - setAvailableProviders(aPrv); - - const infProv = { - enime: enime, - zoro: zoro, - gogoanime: gogoanime, - }; - - if (prv) { - setEpisode( - Array.isArray(prov[prv]) ? prov[prv]?.reverse() : prov[prv] - ); - } else { - setEpisode( - Array.isArray(prov["gogoanime"]) - ? prov["gogoanime"]?.reverse() - : prov["gogoanime"] - ); - } - - const data = infProv[prv] || infProv["gogoanime"]; - // const data = aniInfo; - if (!data || data?.episodes?.length === 0) { - setEpisode([]); - } else { - if (data.episodes?.some((i) => i.title === null)) { - setEpiView("3"); - } else if (view) { - setEpiView(view); - } else { - setEpiView("3"); - } - } - - if (session?.user?.name) { - const response = await fetch("https://graphql.anilist.co/", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: GET_MEDIA_USER, - variables: { - username: session?.user?.name, - }, - }), - }); - - const responseData = await response.json(); - - const prog = responseData?.data?.MediaListCollection; - - if (prog && prog.lists.length > 0) { - const gut = prog.lists - .flatMap((item) => item.entries) - .find((item) => item.mediaId === parseInt(id[0])); - - if (gut) { - setProgress(gut.progress); - const statusMapping = { - CURRENT: { name: "Watching", value: "CURRENT" }, - PLANNING: { name: "Plan to watch", value: "PLANNING" }, - COMPLETED: { name: "Completed", value: "COMPLETED" }, - DROPPED: { name: "Dropped", value: "DROPPED" }, - PAUSED: { name: "Paused", value: "PAUSED" }, - REPEATING: { name: "Rewatching", value: "REPEATING" }, - }; - setStatuses(statusMapping[gut.status]); - } - } - } + }), + }); - if (data.nextAiringEpisode) { - setTime( - convertSecondsToTime(data.nextAiringEpisode.timeUntilAiring) - ); + const responseData = await response.json(); + + const prog = responseData?.data?.MediaListCollection; + + if (prog && prog.lists.length > 0) { + const gut = prog.lists + .flatMap((item) => item.entries) + .find((item) => item.mediaId === parseInt(id[0])); + + if (gut) { + setProgress(gut.progress); + const statusMapping = { + CURRENT: { name: "Watching", value: "CURRENT" }, + PLANNING: { name: "Plan to watch", value: "PLANNING" }, + COMPLETED: { name: "Completed", value: "COMPLETED" }, + DROPPED: { name: "Dropped", value: "DROPPED" }, + PAUSED: { name: "Paused", value: "PAUSED" }, + REPEATING: { name: "Rewatching", value: "REPEATING" }, + }; + setStatuses(statusMapping[gut.status]); } - - setProvider(prov); - } - } catch (error) { - console.error(error); - if (reloadCount < 2) { - reloadCount++; - setTimeout(() => { - window.location.reload(); - }, 1000); - } else { - setEpisode([]); } } } catch (error) { console.error(error); - setTimeout(() => { - window.location.reload(); - }, 1000); } finally { setLoading(false); } @@ -292,8 +101,6 @@ export default function Info({ info, color, api }) { document.body.style.overflow = "auto"; } - const filterProviders = availableProviders?.filter((x) => x.available); - return ( <> @@ -343,7 +150,7 @@ export default function Info({ info, color, api }) {
{info ? ( - banner anime + <> + banner anime + banner anime + ) : (
)}
- {/* Mobile */} + {/* Mobile Anime Information */} -
-
-

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

-

-

- {info?.genres - ?.slice( - 0, - info?.genres?.length > 3 ? info?.genres?.length : 3 - ) - .map((item, index) => ( - - {item} - - ))} -
- {info && ( -
-
- -
- -
-
-
- )} -
-
-
- {info && info.status !== "NOT_YET_RELEASED" ? ( - <> -
- -

{info?.type}

-
-
- -

{info?.averageScore}%

-
-
- - {info?.episodes ? ( -

{info?.episodes} Episodes

- ) : ( -

TBA

- )} -
- - ) : ( -
{info && "Not Yet Released"}
- )} -
-
-
- - {/* PC */} -
-
- {info ? ( - <> -
- poster anime - - - ) : ( - - )} -
+ - {/* PC */} -
-
-

- {info ? ( - info?.title?.romaji || info?.title?.english - ) : ( - - )} -

- {info ? ( -
- {info?.episodes && ( -
- {info?.episodes} Episodes -
- )} - {info?.startDate?.year && ( -
- {info?.startDate?.year} -
- )} - {info?.averageScore && ( -
- {info?.averageScore}% -
- )} - {info?.type && ( -
- {info?.type} -
- )} - {info?.status && ( -
- {info?.status} -
- )} -
- Sub | EN -
-
- ) : ( - - )} -
- {info ? ( -

- ) : ( - - )} -

-
+ {/* PC Anime Information*/} + -
-
- {info?.relations?.edges?.length > 0 && ( -
- Relations -
- )} - {info?.relations?.edges?.length > 3 && ( -
setShowAll(!showAll)} - > - {showAll ? "show less" : "show more"} -
- )} -
-
- {info?.relations?.edges ? ( - info?.relations?.edges - .slice(0, showAll ? info?.relations?.edges.length : 3) - .map((r, index) => { - const rel = r.node; - return ( - -
-
- {rel.id} -
-
-
- {r.relationType} -
-
- {rel.title.userPreferred || rel.title.romaji} -
-
{rel.type}
-
-
- - ); - }) - ) : ( - <> - {[1, 2, 3].map((item) => ( -
- -
- ))} -
- -
- - )} -
-
-
-
-
-
- {info && ( -

- Episodes -

- )} - {info?.nextAiringEpisode && ( -
-
-

Next :

-
- {time} -
-
-
- -
-
- )} -
-
setVisible(!visible)} - > - - - -
-
-
-
- {filterProviders?.length > 0 && ( -
-

Provider

- - -
- )} - {episode?.length > 50 && ( -
-

Episodes

- - -
- )} -
-
-
0 - ? episode?.some((item) => item?.title === null) - ? "pointer-events-none" - : "cursor-pointer" - : "pointer-events-none" - } - onClick={() => { - setEpiView("1"); - localStorage.setItem("epiView", "1"); - }} - > - - 0 - ? episode?.some((item) => item?.title === null) - ? "fill-[#1c1c22]" - : epiView === "1" - ? "fill-action" - : "fill-[#3A3A44]" - : "fill-[#1c1c22]" - }`} - rx="3" - > - -
-
0 - ? episode?.some((item) => item?.title === null) - ? "pointer-events-none" - : "cursor-pointer" - : "pointer-events-none" - } - onClick={() => { - setEpiView("2"); - localStorage.setItem("epiView", "2"); - }} - > - 0 - ? episode?.some((item) => item?.title === null) - ? "fill-[#1c1c22]" - : epiView === "2" - ? "fill-action" - : "fill-[#3A3A44]" - : "fill-[#1c1c22]" - }`} - viewBox="0 0 33 20" - > - - - -
-
0 - ? `cursor-pointer` - : "pointer-events-none" - } - onClick={() => { - setEpiView("3"); - localStorage.setItem("epiView", "3"); - }} - > - 0 - ? epiView === "3" - ? "fill-action" - : "fill-[#3A3A44]" - : "fill-[#1c1c22]" - }`} - viewBox="0 0 33 20" - > - - - - -
-
-
-
- {!loading ? ( - Array.isArray(episode) ? ( - episode && ( -
- {episode?.length !== 0 && episode ? ( -
- {epiView === "1" - ? episode - .slice(firstEpisodeIndex, lastEpisodeIndex) - ?.map((epi, index) => { - const time = artStorage?.[epi?.id]?.time; - const duration = - artStorage?.[epi?.id]?.duration; - let prog = (time / duration) * 100; - if (prog > 90) prog = 100; - return ( - - - Episode {epi?.number} - - -
- epi image - - ); - }) - : ""} - {epiView === "2" && - episode - .slice(firstEpisodeIndex, lastEpisodeIndex) - .map((epi, index) => { - const time = artStorage?.[epi?.id]?.time; - const duration = - artStorage?.[epi?.id]?.duration; - let prog = (time / duration) * 100; - if (prog > 90) prog = 100; - return ( - -
-
- Anime Cover - - - Episode {epi?.number} - -
- - - -
-
-
+ {/* Episodes */} -
-

- {epi?.title} -

- {epi?.description && ( -

- {epi?.description} -

- )} -
- - ); - })} - {epiView === "3" && - episode - .slice(firstEpisodeIndex, lastEpisodeIndex) - .map((epi, index) => { - return ( -
- -

Episode {epi.number}

- {epi.title && ( -

- "{epi.title}" -

- )} - - {index !== episode?.length - 1 && ( - - )} -
- ); - })} -
- ) : ( -

No Episodes Available

- )} -
- ) - ) : ( -
-
-                      {episode?.message}
-                    
-
- ) - ) : ( -
-
-
-
-
-
-
-
- )} -
+
{info && rec?.length !== 0 && (
@@ -1114,17 +286,3 @@ function setTxtColor(hexColor) { const brightness = getBrightness(hexColor); return brightness < 150 ? "#fff" : "#000"; } - -const getLanguageClassName = (language) => { - switch (language) { - case "javascript": - return "language-javascript"; - case "html": - return "language-html"; - case "bash": - return "language-bash"; - // add more languages here as needed - default: - return ""; - } -}; diff --git a/pages/en/anime/watch/[...info].js b/pages/en/anime/watch/[...info].js index 17ec5f7..67e38c2 100644 --- a/pages/en/anime/watch/[...info].js +++ b/pages/en/anime/watch/[...info].js @@ -1,151 +1,82 @@ -import Image from "next/image"; -import Link from "next/link"; import Head from "next/head"; import { useEffect, useState } from "react"; -import dynamic from "next/dynamic"; import { getServerSession } from "next-auth/next"; import { authOptions } from "../../../api/auth/[...nextauth]"; -import Skeleton from "react-loading-skeleton"; - -import { ChevronDownIcon, ForwardIcon } from "@heroicons/react/24/solid"; -import { useRouter } from "next/router"; - -import { GET_MEDIA_USER } from "../../../../queries"; - import dotenv from "dotenv"; import Navigasi from "../../../../components/home/staticNav"; -import DisqusComments from "../../../../components/disqus"; - -const VideoPlayer = dynamic(() => - import("../../../../components/videoPlayer", { ssr: false }) -); +import PrimarySide from "../../../../components/anime/watch/primarySide"; +import SecondarySide from "../../../../components/anime/watch/secondarySide"; +import { GET_MEDIA_USER } from "../../../../queries"; -export default function Info({ sessions, id, aniId, provider, proxy, api }) { - const [epiData, setEpiData] = useState(null); - const [data, setAniData] = useState(null); - const [skip, setSkip] = useState({ op: null, ed: null }); - const [statusWatch, setStatusWatch] = useState("CURRENT"); +export default function Info({ + sessions, + aniId, + watchId, + provider, + epiNumber, + dub, + proxy, + disqus, +}) { + const [info, setInfo] = useState(null); + const [currentEpisode, setCurrentEpisode] = useState(null); const [loading, setLoading] = useState(false); - const [showComments, setShowComments] = useState(false); - const [playing, setPlaying] = useState(null); - const [playingEpisode, setPlayingEpisode] = useState(null); - const [playingTitle, setPlayingTitle] = useState(null); - - const [poster, setPoster] = useState(null); const [progress, setProgress] = useState(0); - - const [episodes, setEpisodes] = useState([]); + const [statuses, setStatuses] = useState("CURRENT"); const [artStorage, setArtStorage] = useState(null); - - const [url, setUrl] = useState(null); - - const router = useRouter(); - - // console.log({ playing }); + const [episodesList, setepisodesList] = useState(); + const [onList, setOnList] = useState(false); useEffect(() => { - const defaultState = { - epiData: null, - skip: { op: null, ed: null }, - statusWatch: "CURRENT", - playingEpisode: null, - loading: false, - showComments: false, - }; - - // Reset all state variables to their default values - Object.keys(defaultState).forEach((key) => { - const value = defaultState[key]; - if (Array.isArray(value)) { - value.length - ? eval( - `set${ - key.charAt(0).toUpperCase() + key.slice(1) - }(${JSON.stringify(value)})` - ) - : eval(`set${key.charAt(0).toUpperCase() + key.slice(1)}([])`); - } else { - eval( - `set${key.charAt(0).toUpperCase() + key.slice(1)}(${JSON.stringify( - value - )})` - ); - } - }); - - const url = window.location.href; - setUrl(url); - - const fetchData = async () => { - try { - if (provider) { - const res = await fetch( - `${api}/meta/anilist/watch/${id}?provider=${provider}` - ); - const epiData = await res.json(); - setEpiData(epiData); - } else { - const res = await fetch(`${api}/meta/anilist/watch/${id}`); - const epiData = await res.json(); - setEpiData(epiData); - } - } catch (error) { - setTimeout(() => { - window.location.reload(); - }, 3000); - } - - let aniData = null; - setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); - - if (provider) { - const res = await fetch( - `${api}/meta/anilist/info/${aniId}?provider=${provider}` - ); - aniData = await res.json(); - setEpisodes(aniData.episodes?.reverse()); - setAniData(aniData); - } else { - const res2 = await fetch(`${api}/meta/anilist/info/${aniId}`); - aniData = await res2.json(); - setEpisodes(aniData.episodes?.reverse()); - setAniData(aniData); - } - - let playingEpisode = aniData.episodes - .filter((item) => item.id == id) - .map((item) => item.number); - - setPlayingEpisode(playingEpisode); - - const playing = aniData.episodes.find((item) => item.id === id); - - setPoster(playing?.image); - setPlaying(playing); - - const title = aniData.episodes - .filter((item) => item.id == id) - .find((item) => item.title !== null); - setPlayingTitle( - title?.title || aniData.title?.romaji || aniData.title?.english - ); - - const res4 = await fetch( - `https://api.aniskip.com/v2/skip-times/${aniData.malId}/${parseInt( - playingEpisode - )}?types[]=ed&types[]=mixed-ed&types[]=mixed-op&types[]=op&types[]=recap&episodeLength=` - ); - const skip = await res4.json(); - - const op = skip.results?.find((item) => item.skipType === "op") || null; - const ed = skip.results?.find((item) => item.skipType === "ed") || null; - - setSkip({ op, ed }); + setLoading(true); + async function getInfo() { + const ress = await fetch(`https://graphql.anilist.co`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: `query ($id: Int) { + Media (id: $id) { + id + idMal + title { + romaji + english + native + } + status + genres + episodes + studios { + edges { + node { + id + name + } + } + } + description + coverImage { + extraLarge + color + } + synonyms + + } + } + `, + variables: { + id: aniId, + }, + }), + }); + const data = await ress.json(); - if (sessions) { + if (sessions?.user?.name) { const response = await fetch("https://graphql.anilist.co/", { method: "POST", headers: { @@ -154,450 +85,121 @@ export default function Info({ sessions, id, aniId, provider, proxy, api }) { body: JSON.stringify({ query: GET_MEDIA_USER, variables: { - username: sessions?.user.name, + username: sessions?.user?.name, }, }), }); - const dat = await response.json(); - - const prog = dat.data.MediaListCollection; + const responseData = await response.json(); + + const prog = responseData?.data?.MediaListCollection; + + if (prog && prog.lists.length > 0) { + const gut = prog.lists + .flatMap((item) => item.entries) + .find((item) => item.mediaId === parseInt(aniId)); + + if (gut) { + setProgress(gut.progress); + setOnList(true); + } + + if (gut?.status === "COMPLETED") { + setStatuses("REPEATING"); + } else if ( + gut?.status === "REPEATING" && + gut?.media?.episodes === parseInt(epiNumber) + ) { + setStatuses("COMPLETED"); + } else if (gut?.status === "REPEATING") { + setStatuses("REPEATING"); + } else if (gut?.media?.episodes === parseInt(epiNumber)) { + setStatuses("COMPLETED"); + } else if ( + gut?.media?.episodes !== null && + data?.data?.Media.episodes === parseInt(epiNumber) + ) { + setStatuses("COMPLETED"); + setLoading(false); + } + } + } - const gat = prog?.lists.map((item) => item.entries); - const git = gat?.map((item) => - item?.find((item) => item.media.id === parseInt(aniId)) - ); - const gut = git?.find((item) => item?.media.id === parseInt(aniId)); + setInfo(data.data.Media); - if (gut) { - setProgress(gut.progress); - } + const response = await fetch( + `/api/consumet/episode/${aniId}${dub ? `?dub=${dub}` : ""}` + ); + const episodes = await response.json(); - if (gut?.status === "COMPLETED") { - setStatusWatch("REPEATING"); - } else if ( - gut?.status === "REPEATING" && - gut?.media?.episodes === parseInt(playingEpisode) - ) { - setStatusWatch("COMPLETED"); - } else if (gut?.status === "REPEATING") { - setStatusWatch("REPEATING"); - } else if (gut?.media?.episodes === parseInt(playingEpisode)) { - setStatusWatch("COMPLETED"); - } else if ( - gut?.media?.episodes !== null && - aniData.totalEpisodes === parseInt(playingEpisode) - ) { - setStatusWatch("COMPLETED"); - setLoading(true); + if (episodes) { + const getProvider = episodes.data?.find( + (i) => i.providerId === provider + ); + if (getProvider) { + setepisodesList(getProvider.episodes); + const currentEpisode = getProvider.episodes?.find( + (i) => i.number === parseInt(epiNumber) + ); + const nextEpisode = getProvider.episodes?.find( + (i) => i.number === parseInt(epiNumber) + 1 + ); + const previousEpisode = getProvider.episodes?.find( + (i) => i.number === parseInt(epiNumber) - 1 + ); + setCurrentEpisode({ + prev: previousEpisode, + playing: currentEpisode, + next: nextEpisode, + }); + } else { + setLoading(false); } } - setLoading(true); - }; - fetchData(); - }, [id, aniId, provider, sessions]); - - useEffect(() => { - const mediaSession = navigator.mediaSession; - if (!mediaSession) return; - const artwork = poster - ? [{ src: poster, sizes: "512x512", type: "image/jpeg" }] - : undefined; + setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); + // setEpiData(episodes); + setLoading(false); + } + getInfo(); + }, [sessions?.user?.name, epiNumber, dub]); - mediaSession.metadata = new MediaMetadata({ - title: playingTitle, - artist: `Moopa ${ - playingTitle === data?.title?.romaji - ? "- Episode " + playingEpisode - : `- ${data?.title?.romaji || data?.title?.english}` - }`, - artwork, - }); - }, [poster, playingTitle, playingEpisode, data]); + // console.log(proxy); return ( <> - {playingTitle || "Loading..."} + {info?.title?.romaji || "Retrieving data..."} -
- -
-
- {loading ? ( - Array.isArray(epiData?.sources) ? ( -
- -
- ) : ( -
-

- Whoops! Something went wrong. Please reload the page or try - other sources. {`:(`} -

-
- ) - ) : ( -
- )} -
- {data && data?.episodes.length > 0 ? ( - data.episodes - .filter((items) => items.id == id) - .map((item, index) => ( -
-
-
- - {item.title || - data.title.romaji || - data.title.english} - -
-

- Episode {item.number} -

-
-
-
- - -
- -
-
- )) - ) : ( -
-
-
- -
-
-

- -

-
- )} -
- -
-
- {data ? ( - Anime Cover - ) : ( - - )} -
-
-
-

- Studios -

-
- {data ? data.studios : } -
-
-
- - - -
-
-
-
-

- Status -

-
{data ? data.status : }
-
-
-

- Titles -

-
- {data ? ( - <> -
- {data.title.romaji || ""} -
-
- {data.title.english || ""} -
-
- {data.title.native || ""} -
- - ) : ( - - )} -
-
-
-
-
- {data && - data.genres.map((item, index) => ( -
- {item} -
- ))} -
-
- {data && ( -

- )} -

- {!showComments && loading && ( -
- -
- )} - {showComments && ( -
- {data && url && playing && ( -
- -
- )} -
- )} -
-
-
-

- Up Next -

-
- {data && data?.episodes.length > 0 ? ( - data.episodes.some((item) => item.title && item.description) ? ( - episodes.map((item) => { - const time = artStorage?.[item.id]?.time; - const duration = artStorage?.[item.id]?.duration; - let prog = (time / duration) * 100; - if (prog > 90) prog = 100; - return ( - -
-
- Anime Cover - - - Episode {item.number} - - {item.id == id && ( -
- - - -
- )} -
-
-
-

- {item.title} -

-

- {item.description} -

-
- - ); - }) - ) : ( - data.episodes.map((item) => { - return ( - - Episode {item.number} - - ); - }) - ) - ) : ( - <> - {[1].map((item) => ( - - ))} - - )} -
-
+ +
+
+ +
@@ -607,31 +209,34 @@ export default function Info({ sessions, id, aniId, provider, proxy, api }) { export async function getServerSideProps(context) { dotenv.config(); - const API_URI = process.env.API_URI; - const session = await getServerSession(context.req, context.res, authOptions); - const proxy = process.env.PROXY_URI; - - const { info } = context.query; - if (!info) { + const query = context.query; + if (!query) { return { notFound: true, }; } - const id = info[0]; - const aniId = info[1]; - const provider = info[2] || null; + const proxy = process.env.PROXY_URI; + const disqus = process.env.DISQUS_SHORTNAME; + + const aniId = query.info[0]; + const provider = query.info[1]; + const watchId = query.id; + const epiNumber = query.num; + const dub = query.dub; return { props: { sessions: session, - id, - aniId, - provider, + aniId: aniId || null, + provider: provider || null, + watchId: watchId || null, + epiNumber: epiNumber || null, + dub: dub || null, proxy, - api: API_URI, + disqus, }, }; } diff --git a/pages/en/index.js b/pages/en/index.js index cbf96cd..35de96d 100644 --- a/pages/en/index.js +++ b/pages/en/index.js @@ -16,10 +16,11 @@ import SearchBar from "../../components/searchBar"; import Genres from "../../components/home/genres"; import Schedule from "../../components/home/schedule"; import getUpcomingAnime from "../../lib/anilist/getUpcomingAnime"; -import { useCountdown } from "../../lib/useCountdownSeconds"; +import { useCountdown } from "../../utils/useCountdownSeconds"; import Navigasi from "../../components/home/staticNav"; import MobileNav from "../../components/home/mobileNav"; +import axios from "axios"; // Filter schedules for each day // const filterByCountryOfOrigin = (schedule, country) => { @@ -58,8 +59,7 @@ export default function Home({ detail, populars, sessions, upComing }) { useEffect(() => { const getSchedule = async () => { - const res = await fetch(`https://ruka.moopa.live/api/schedules`); - const data = await res.json(); + const { data } = await axios.get(`/api/anify/schedule`); setSchedules(data); }; getSchedule(); @@ -132,7 +132,6 @@ export default function Home({ detail, populars, sessions, upComing }) { } userData(); }, [sessions, current, plan]); - return ( <> diff --git a/pages/en/test.js b/pages/en/test.js new file mode 100644 index 0000000..cf76827 --- /dev/null +++ b/pages/en/test.js @@ -0,0 +1,11 @@ +import VideoPlayer from "../../components/videoPlayer"; + +export default function Test() { + return ( +
+
+ +
+
+ ); +} diff --git a/public/404.svg b/public/404.svg new file mode 100644 index 0000000..063d205 --- /dev/null +++ b/public/404.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/globals.css b/styles/globals.css index d84eed0..8063105 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -151,7 +151,7 @@ button { width: 10px; border-radius: 50%; display: inline-block; - margin-right: 10px; + margin-right: 5px; margin-left: 2px; margin-bottom: 1px; } diff --git a/utils/useCountdownSeconds.js b/utils/useCountdownSeconds.js new file mode 100644 index 0000000..df3cb63 --- /dev/null +++ b/utils/useCountdownSeconds.js @@ -0,0 +1,37 @@ +import { useEffect, useState } from "react"; + +const useCountdown = (targetDate, update) => { + const countDownDate = new Date(targetDate).getTime(); + + const [countDown, setCountDown] = useState( + countDownDate - new Date().getTime() + ); + + useEffect(() => { + const interval = setInterval(() => { + const newCountDown = countDownDate - new Date().getTime(); + setCountDown(newCountDown); + if (newCountDown <= 0 && newCountDown > -1000) { + update(); + } + }, 1000); + + return () => clearInterval(interval); + }, [countDownDate, update]); + + return getReturnValues(countDown); +}; + +const getReturnValues = (countDown) => { + // calculate time left + const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); + const hours = Math.floor( + (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) + ); + const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((countDown % (1000 * 60)) / 1000); + + return [days, hours, minutes, seconds]; +}; + +export { useCountdown }; -- cgit v1.2.3