From 1eee181e219dfd993d396ac3169e7aad3dd285eb Mon Sep 17 00:00:00 2001 From: Factiven Date: Sun, 16 Jul 2023 22:35:39 +0700 Subject: Update v3.6.4 - Added Manga page with a working tracker for AniList user - Added schedule component to home page - Added disqus comment section so you can fight on each other (not recommended) - Added /id and /en route for english and indonesian subs (id route still work in progress) --- pages/en/about.js | 57 ++ pages/en/anime/[...id].js | 1162 ++++++++++++++++++++++++++++++++++++ pages/en/anime/watch/[...info].js | 642 ++++++++++++++++++++ pages/en/contact.js | 19 + pages/en/dmca.js | 109 ++++ pages/en/index.js | 576 ++++++++++++++++++ pages/en/manga/[id].js | 172 ++++++ pages/en/manga/read/[...params].js | 262 ++++++++ pages/en/profile/[user].js | 423 +++++++++++++ pages/en/search/[param].js | 493 +++++++++++++++ 10 files changed, 3915 insertions(+) create mode 100644 pages/en/about.js create mode 100644 pages/en/anime/[...id].js create mode 100644 pages/en/anime/watch/[...info].js create mode 100644 pages/en/contact.js create mode 100644 pages/en/dmca.js create mode 100644 pages/en/index.js create mode 100644 pages/en/manga/[id].js create mode 100644 pages/en/manga/read/[...params].js create mode 100644 pages/en/profile/[user].js create mode 100644 pages/en/search/[param].js (limited to 'pages/en') diff --git a/pages/en/about.js b/pages/en/about.js new file mode 100644 index 0000000..9bd32ed --- /dev/null +++ b/pages/en/about.js @@ -0,0 +1,57 @@ +import Head from "next/head"; +import Layout from "../../components/layout"; +import { motion } from "framer-motion"; +import Link from "next/link"; + +export default function About() { + return ( + <> + + Moopa - About + + + + + + +
+

About Us

+

+ Moopa is a platform where you can watch and stream anime or read + manga for free, without any ads or VPNs. Our mission is to provide + a convenient and enjoyable experience for anime and manga + enthusiasts all around the world. +

+

+ At our site, you will find a vast collection of anime and manga + titles from different genres, including action, adventure, comedy, + romance, and more. We take pride in our fast and reliable servers, + which ensure smooth streaming and reading for all our users. +

+

+ We believe that anime and manga have the power to inspire and + entertain people of all ages and backgrounds. Our service is + designed to make it easy for fans to access the content they love, + whether they are casual viewers or die-hard fans. +

+

+ Thank you for choosing our website as your go-to platform for + anime and manga. We hope you enjoy your stay here, and feel free + to contact us if you have any feedback or suggestions. +

+ +
+ Contact Us +
+ +
+
+
+ + ); +} diff --git a/pages/en/anime/[...id].js b/pages/en/anime/[...id].js new file mode 100644 index 0000000..b6393d4 --- /dev/null +++ b/pages/en/anime/[...id].js @@ -0,0 +1,1162 @@ +import Skeleton, { SkeletonTheme } from "react-loading-skeleton"; +import "react-loading-skeleton/dist/skeleton.css"; + +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"; + +import { signIn, useSession } from "next-auth/react"; +import AniList from "../../../components/media/aniList"; +import ListEditor from "../../../components/listEditor"; + +import { GET_MEDIA_USER } from "../../../queries"; +import { GET_MEDIA_INFO } from "../../../queries"; + +import { ToastContainer } from "react-toastify"; + +// 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); + } + + 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); + + 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, + }, + { + 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) + ); + } + + 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); + } + } + } + fetchData(); + }, [id, info, session?.user?.name]); + + function handleOpen() { + setOpen(true); + document.body.style.overflow = "hidden"; + } + + function handleClose() { + setOpen(false); + document.body.style.overflow = "auto"; + } + + const filterProviders = availableProviders?.filter((x) => x.available); + + return ( + <> + + + {info + ? info?.title?.romaji || info?.title?.english + : "Retrieving Data..."} + + + + + + + + handleClose()}> +
+ {!session && ( +
+

+ Edit your list +

+ +
+ )} + {session && info && ( + + )} +
+
+ + +
+
+
+ {info ? ( + banner anime + ) : ( +
+ )} +
+
+ {/* Mobile */} + +
+
+

+ {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 ? ( +

+ ) : ( + + )} +

+
+ +
+
+ {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} + +
+ + + +
+
+
+ +
+

+ {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 && ( +
+ +
+ )} +
+ + + + ); +} + +export async function getServerSideProps(context) { + const { id } = context.query; + const API_URI = process.env.API_URI; + + const res = await fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: GET_MEDIA_INFO, + variables: { + id: id?.[0], + }, + }), + }); + + const json = await res.json(); + const data = json?.data?.Media; + + if (!data) { + return { + notFound: true, + }; + } + + const textColor = setTxtColor(data?.coverImage?.color); + + const color = { + backgroundColor: `${data?.coverImage?.color || "#ffff"}`, + color: textColor, + }; + + return { + props: { + info: data, + color: color, + api: API_URI, + }, + }; +} + +function convertSecondsToTime(sec) { + let days = Math.floor(sec / (3600 * 24)); + let hours = Math.floor((sec % (3600 * 24)) / 3600); + let minutes = Math.floor((sec % 3600) / 60); + + let time = ""; + + if (days > 0) { + time += `${days}d `; + } + + if (hours > 0) { + time += `${hours}h `; + } + + if (minutes > 0) { + time += `${minutes}m `; + } + + return time.trim(); +} + +function getBrightness(hexColor) { + if (!hexColor) { + return 200; + } + const rgb = hexColor + .match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i) + .slice(1) + .map((x) => parseInt(x, 16)); + return (299 * rgb[0] + 587 * rgb[1] + 114 * rgb[2]) / 1000; +} + +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 new file mode 100644 index 0000000..d6e40e1 --- /dev/null +++ b/pages/en/anime/watch/[...info].js @@ -0,0 +1,642 @@ +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, { SkeletonTheme } 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 }) +); + +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"); + 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 [artStorage, setArtStorage] = useState(null); + + const [url, setUrl] = useState(null); + + const router = useRouter(); + + // console.log({ playing }); + + 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 }); + + if (sessions) { + const response = await fetch("https://graphql.anilist.co/", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: GET_MEDIA_USER, + variables: { + username: sessions?.user.name, + }, + }), + }); + + const dat = await response.json(); + + const prog = dat.data.MediaListCollection; + + 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)); + + if (gut) { + setProgress(gut.progress); + } + + 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); + } + } + 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; + + 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]); + + return ( + <> + + {playingTitle || "Loading..."} + + + +
+ +
+
+ {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) => ( + + ))} + + )} +
+
+
+
+ + + ); +} + +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) { + return { + notFound: true, + }; + } + + const id = info[0]; + const aniId = info[1]; + const provider = info[2] || null; + + return { + props: { + sessions: session, + id, + aniId, + provider, + proxy, + api: API_URI, + }, + }; +} diff --git a/pages/en/contact.js b/pages/en/contact.js new file mode 100644 index 0000000..400a9e8 --- /dev/null +++ b/pages/en/contact.js @@ -0,0 +1,19 @@ +import Layout from "../../components/layout"; + +const Contact = () => { + return ( + +
+

Contact Us

+

If you have any questions or comments, please email us at:

+

+ + contact@moopa.live + +

+
+
+ ); +}; + +export default Contact; diff --git a/pages/en/dmca.js b/pages/en/dmca.js new file mode 100644 index 0000000..8dad7d7 --- /dev/null +++ b/pages/en/dmca.js @@ -0,0 +1,109 @@ +import Head from "next/head"; +import Layout from "../../components/layout"; + +export default function DMCA() { + return ( + <> + + Moopa - DMCA + + + + + + + + +
+
+
+

+ DMCA - Disclaimer +

+
+
+
+

+ Moopa.live is committed to respecting the intellectual + property rights of others and complying with the Digital + Millennium Copyright Act (DMCA). We take copyright + infringement seriously and will respond to notices of alleged + copyright infringement that comply with the DMCA and any other + applicable laws. +

+

+ If you believe that any content on our website is infringing + upon your copyrights, please send us an email. Please allow up + to 2-5 business days for a response. Please note that emailing + your complaint to other parties such as our Internet Service + Provider, Hosting Provider, and other third parties will not + expedite your request and may result in a delayed response due + to the complaint not being filed properly. +

+
+

+ In order for us to process your complaint, please provide the + following information: +

+
+
    +
  • + · Your name, address, and telephone number. We reserve the + right to verify this information. +
  • +
  • + · Identification of the copyrighted work claimed to have + been infringed. +
  • +
  • + · The exact and complete URL link where the infringing + material is located. +
  • +
  • + · The exact and complete URL link where the infringing + material is located. +
  • +
  • + · The exact and complete URL link where the infringing + material is located. +
  • +
  • · Please write to us in English or Indonesian.
  • +
+
+

+ Please note that anonymous or incomplete messages will not be + dealt with. Thank you for your understanding. +

+

DISCLAIMER:

+

+ None of the files listed on Moopa.live are hosted on our + servers. All links point to content hosted on third-party + websites. Moopa.live does not accept responsibility for content + hosted on third-party websites and has no involvement in the + downloading/uploading of movies. We only post links that are + available on the internet. If you believe that any content on + our website infringes upon your intellectual property rights and + you hold the copyright for that content, please report it to{" "} + + contact@moopa.live + {" "} + and the content will be immediately removed. +

+
+
+
+
+ + ); +} diff --git a/pages/en/index.js b/pages/en/index.js new file mode 100644 index 0000000..d13f182 --- /dev/null +++ b/pages/en/index.js @@ -0,0 +1,576 @@ +import { aniListData } from "../../lib/anilist/AniList"; +import React, { useState, useEffect } from "react"; +import Head from "next/head"; +import Link from "next/link"; +import Footer from "../../components/footer"; +import Image from "next/image"; +import Content from "../../components/home/content"; + +import { motion } from "framer-motion"; + +import { signIn, signOut } from "next-auth/react"; +import { useAniList } from "../../lib/anilist/useAnilist"; +import { getServerSession } from "next-auth/next"; +import { authOptions } from "../api/auth/[...nextauth]"; +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 dotenv from "dotenv"; +import Navigasi from "../../components/home/staticNav"; + +// Filter schedules for each day +const filterByCountryOfOrigin = (schedule, country) => { + const filteredSchedule = {}; + for (const day in schedule) { + filteredSchedule[day] = schedule[day].filter( + (anime) => anime.countryOfOrigin === country + ); + } + return filteredSchedule; +}; + +export default function Home({ + detail, + populars, + sessions, + upComing, + schedules, +}) { + const { media: current } = useAniList(sessions, { stats: "CURRENT" }); + const { media: plan } = useAniList(sessions, { stats: "PLANNING" }); + const { media: release } = useAniList(sessions); + + const [anime, setAnime] = useState([]); + let scheduleData = null; + + const update = () => { + setAnime((prevAnime) => prevAnime.slice(1)); + }; + + const [days, hours, minutes, seconds] = useCountdown( + anime[0]?.nextAiringEpisode?.airingAt * 1000 || Date.now(), + update + ); + + useEffect(() => { + if (upComing && upComing.length > 0) { + setAnime(upComing); + } + }, [upComing]); + + const [releaseData, setReleaseData] = useState([]); + + // console.log(schedules); + + useEffect(() => { + function getRelease() { + let releasingAnime = []; + let progress = []; + release.map((list) => { + list.entries.map((entry) => { + if (entry.media.status === "RELEASING") { + releasingAnime.push(entry.media); + } + + progress.push(entry); + }); + }); + setReleaseData(releasingAnime); + setProg(progress); + } + getRelease(); + }, [release]); + + const [isVisible, setIsVisible] = useState(false); + const [list, setList] = useState(null); + const [planned, setPlanned] = useState(null); + const [greeting, setGreeting] = useState(""); + + const [prog, setProg] = useState(null); + + const popular = populars?.data; + const data = detail.data[0]; + + const handleShowClick = () => { + setIsVisible(true); + }; + + const handleHideClick = () => { + setIsVisible(false); + }; + + useEffect(() => { + const time = new Date().getHours(); + let greeting = ""; + + if (time >= 5 && time < 12) { + greeting = "Good morning"; + } else if (time >= 12 && time < 18) { + greeting = "Good afternoon"; + } else if (time >= 18 && time < 22) { + greeting = "Good evening"; + } else if (time >= 22 || time < 5) { + greeting = "Good night"; + } + + setGreeting(greeting); + + async function userData() { + if (!sessions) return; + const getMedia = + current.filter((item) => item.status === "CURRENT")[0] || null; + const list = getMedia?.entries + .map(({ media }) => media) + .filter((media) => media); + + const planned = plan?.[0]?.entries + .map(({ media }) => media) + .filter((media) => media); + + if (list) { + setList(list.reverse()); + } + if (planned) { + setPlanned(planned.reverse()); + } + } + userData(); + }, [sessions, current, plan]); + + return ( + <> + + Moopa + + + + + + + + + {/* NAVBAR */} +
+ {!isVisible && ( + + )} +
+ + {/* Mobile Menu */} +
+ {isVisible && sessions && ( + + user avatar + + )} + {isVisible && ( +
+
+ + + + {sessions ? ( + + ) : ( + + )} +
+ +
+ )} +
+ +
+ + + {/* PC / TABLET */} +
+
+
+

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

+

+ +

+
+
+
+
+ + {`alt +
+
+
+
+ {/* {!sessions && ( +

+ {greeting}! +

+ )} */} + {sessions && ( +
+
+ {greeting},

{sessions?.user.name}

+ +
+
+ )} + +
+ + {sessions && releaseData?.length > 0 && ( + + + + )} + + {sessions && list?.length > 0 && ( + + + + )} + + {/* SECTION 2 */} + {sessions && planned?.length > 0 && ( + + + + )} + + {/* SECTION 3 */} + {detail && ( + + + + )} + + {/* Schedule */} + {anime.length > 0 && schedules && ( + + + + )} + + {/* SECTION 4 */} + {popular && ( + + + + )} + + + + + +
+
+