diff options
| author | Factiven <[email protected]> | 2023-10-28 22:50:51 +0700 |
|---|---|---|
| committer | Factiven <[email protected]> | 2023-10-28 22:50:51 +0700 |
| commit | a25282429761ff0670a50fd74f8c24bdb38e728c (patch) | |
| tree | 0cd14840e665f1912842967de7427a31556190e6 | |
| parent | Update v4.2.3 (diff) | |
| download | moopa-a25282429761ff0670a50fd74f8c24bdb38e728c.tar.xz moopa-a25282429761ff0670a50fd74f8c24bdb38e728c.zip | |
Update v4.2.4
| -rw-r--r-- | components/admin/dashboard/index.js | 3 | ||||
| -rw-r--r-- | components/admin/layout.js | 1 | ||||
| -rw-r--r-- | components/anime/episode.js | 72 | ||||
| -rw-r--r-- | components/home/content.js | 3 | ||||
| -rw-r--r-- | components/listEditor.js | 1 | ||||
| -rw-r--r-- | components/search/searchByImage.js | 2 | ||||
| -rw-r--r-- | components/searchPalette.js | 3 | ||||
| -rw-r--r-- | components/shared/NavBar.js | 1 | ||||
| -rw-r--r-- | lib/redis.js | 13 | ||||
| -rw-r--r-- | pages/api/v2/episode/[id].js | 44 | ||||
| -rw-r--r-- | pages/api/v2/etc/recent/[page].js | 45 | ||||
| -rw-r--r-- | pages/en/anime/recent.js | 10 | ||||
| -rw-r--r-- | public/image-error-fallback.png | bin | 0 -> 15431 bytes | |||
| -rw-r--r-- | release.md | 5 | ||||
| -rw-r--r-- | utils/imageUtils.js | 11 |
15 files changed, 164 insertions, 50 deletions
diff --git a/components/admin/dashboard/index.js b/components/admin/dashboard/index.js index d0c9963..930b8e0 100644 --- a/components/admin/dashboard/index.js +++ b/components/admin/dashboard/index.js @@ -168,12 +168,14 @@ export default function AdminDashboard({ </div> <div className="flex font-karla font-semibold gap-2"> <button + title="Broadcast" type="submit" className="bg-image text-white py-2 px-4 rounded-md hover:bg-opacity-80 transition duration-300" > Broadcast </button> <button + title="Remove" type="button" onClick={handleRemove} className="bg-red-700 text-white py-2 px-4 rounded-md hover:bg-opacity-80 transition duration-300" @@ -247,6 +249,7 @@ export default function AdminDashboard({ )} <button type="button" + title="Resolved" onClick={() => { setReportId(i?.id); handleResolved(); diff --git a/components/admin/layout.js b/components/admin/layout.js index 3209dcf..85a5fe7 100644 --- a/components/admin/layout.js +++ b/components/admin/layout.js @@ -49,6 +49,7 @@ export default function AdminLayout({ children, page, setPage }) { <div className="flex flex-col px-1"> {Navigation.map((item, index) => ( <button + title="Dashboard" key={item.name} onClick={() => { setPage(item.page); diff --git a/components/anime/episode.js b/components/anime/episode.js index a42307f..3650944 100644 --- a/components/anime/episode.js +++ b/components/anime/episode.js @@ -6,6 +6,30 @@ import ThumbnailDetail from "./viewMode/thumbnailDetail"; import ListMode from "./viewMode/listMode"; import { toast } from "sonner"; +function allProvider(response, setMapProviders, setProviderId) { + const getMap = response.find((i) => i?.map === true); + let allProvider = response; + + if (getMap) { + allProvider = response.filter((i) => { + if (i?.providerId === "gogoanime" && i?.map !== true) { + return null; + } + return i; + }); + setMapProviders(getMap?.episodes); + } + + if (allProvider.length > 0) { + const defaultProvider = allProvider.find( + (x) => x.providerId === "gogoanime" || x.providerId === "9anime" + ); + setProviderId(defaultProvider?.providerId || allProvider[0].providerId); // set to first provider id + } + + return allProvider; +} + export default function AnimeEpisode({ info, session, @@ -34,29 +58,12 @@ export default function AnimeEpisode({ info.status === "RELEASING" ? "true" : "false" }${isDub ? "&dub=true" : ""}` ).then((res) => res.json()); - const getMap = response.find((i) => i?.map === true); - let allProvider = response; - if (getMap) { - allProvider = response.filter((i) => { - if (i?.providerId === "gogoanime" && i?.map !== true) { - return null; - } - return i; - }); - setMapProviders(getMap?.episodes); - } - - if (allProvider.length > 0) { - const defaultProvider = allProvider.find( - (x) => x.providerId === "gogoanime" || x.providerId === "9anime" - ); - setProviderId(defaultProvider?.providerId || allProvider[0].providerId); // set to first provider id - } + const providers = allProvider(response, setMapProviders, setProviderId); setView(Number(localStorage.getItem("view")) || 3); setArtStorage(JSON.parse(localStorage.getItem("artplayer_settings"))); - setProviders(allProvider); + setProviders(providers); setLoading(false); }; fetchData(); @@ -172,11 +179,32 @@ export default function AnimeEpisode({ }${isDub ? "&dub=true" : ""}&refresh=true` ); if (!res.ok) { - console.log(res); - toast.error("Something went wrong"); - setProviders([]); + const json = await res.json(); + if (res.status === 429) { + toast.error(json.error); + const resp = await fetch( + `/api/v2/episode/${info.id}?releasing=${ + info.status === "RELEASING" ? "true" : "false" + }${isDub ? "&dub=true" : ""}` + ).then((res) => res.json()); + + if (resp) { + const providers = allProvider( + resp, + setMapProviders, + setProviderId + ); + setProviders(providers); + } + } else { + toast.error("Something went wrong"); + setProviders([]); + } setLoading(false); } else { + const remainingRequests = res.headers.get("X-RateLimit-Remaining"); + toast.success("Remaining requests " + remainingRequests); + const data = await res.json(); const getMap = data.find((i) => i?.map === true) || data[0]; let allProvider = data; diff --git a/components/home/content.js b/components/home/content.js index a380e1f..1cf4c5f 100644 --- a/components/home/content.js +++ b/components/home/content.js @@ -13,6 +13,7 @@ import { ExclamationCircleIcon, PlayIcon } from "@heroicons/react/24/solid"; import { useRouter } from "next/router"; import HistoryOptions from "./content/historyOptions"; import { toast } from "sonner"; +import { truncateImgUrl } from "@/utils/imageUtils"; export default function Content({ ids, @@ -287,7 +288,7 @@ export default function Content({ anime.image || anime.coverImage?.extraLarge || anime.coverImage?.large || - anime?.coverImage || + truncateImgUrl(anime?.coverImage) || "https://cdn.discordapp.com/attachments/986579286397964290/1058415946945003611/gray_pfp.png" } alt={ diff --git a/components/listEditor.js b/components/listEditor.js index f4f46ea..7d30835 100644 --- a/components/listEditor.js +++ b/components/listEditor.js @@ -145,6 +145,7 @@ const ListEditor = ({ </div> <button type="submit" + title="Save" className="bg-[#363642] text-white rounded-sm mt-2 py-1 text-sm sm:text-base" > Save diff --git a/components/search/searchByImage.js b/components/search/searchByImage.js index f95c2ad..f61418f 100644 --- a/components/search/searchByImage.js +++ b/components/search/searchByImage.js @@ -65,7 +65,7 @@ export default function SearchByImage({ useEffect(() => { // Add a global event listener for the paste event const handlePaste = async (e) => { - e.preventDefault(); + // e.preventDefault(); const items = e.clipboardData.items; diff --git a/components/searchPalette.js b/components/searchPalette.js index 10b9003..b450423 100644 --- a/components/searchPalette.js +++ b/components/searchPalette.js @@ -149,6 +149,7 @@ export default function SearchPalette() { <Menu.Item> {({ active }) => ( <button + title="Anime" onClick={() => setType("ANIME")} className={`${ active @@ -164,6 +165,7 @@ export default function SearchPalette() { <Menu.Item> {({ active }) => ( <button + title="Manga" onClick={() => setType("MANGA")} className={`${ active @@ -239,6 +241,7 @@ export default function SearchPalette() { {nextPage && ( <button type="button" + title="View More" onClick={() => { router.push( `/en/search/${type.toLowerCase()}/${ diff --git a/components/shared/NavBar.js b/components/shared/NavBar.js index 034a06b..8cfdfc1 100644 --- a/components/shared/NavBar.js +++ b/components/shared/NavBar.js @@ -173,6 +173,7 @@ export function NewNavbar({ <div className="flex w-[20%] justify-end items-center gap-4"> <button type="button" + title="Search" onClick={() => setIsOpen(true)} className="flex-center w-[26px] h-[26px]" > diff --git a/lib/redis.js b/lib/redis.js index 713b5d9..9522e4c 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -6,6 +6,7 @@ const REDIS_URL = process.env.REDIS_URL; let redis; let rateLimiterRedis; let rateLimitStrict; +let rateSuperStrict; if (REDIS_URL) { redis = new Redis(REDIS_URL); @@ -27,10 +28,20 @@ if (REDIS_URL) { duration: 1, }; + const optSuperStrict = { + storeClient: redis, + keyPrefix: "rateLimitSuperStrict", + points: 3, + // duration 10 minutes + duration: 10 * 60, + blockDuration: 10 * 60, + }; + rateLimiterRedis = new RateLimiterRedis(opt); rateLimitStrict = new RateLimiterRedis(optStrict); + rateSuperStrict = new RateLimiterRedis(optSuperStrict); } else { console.warn("REDIS_URL is not defined. Redis caching will be disabled."); } -export { redis, rateLimiterRedis, rateLimitStrict }; +export { redis, rateLimiterRedis, rateLimitStrict, rateSuperStrict }; diff --git a/pages/api/v2/episode/[id].js b/pages/api/v2/episode/[id].js index 8d0f443..0e747d6 100644 --- a/pages/api/v2/episode/[id].js +++ b/pages/api/v2/episode/[id].js @@ -1,5 +1,5 @@ import axios from "axios"; -import { rateLimitStrict, rateLimiterRedis, redis } from "@/lib/redis"; +import { rateLimiterRedis, rateSuperStrict, redis } from "@/lib/redis"; import appendMetaToEpisodes from "@/utils/appendMetaToEpisodes"; let CONSUMET_URI; @@ -159,16 +159,26 @@ export default async function handler(req, res) { let cached; let meta; + let headers; if (redis) { try { const ipAddress = req.socket.remoteAddress; refresh - ? await rateLimitStrict.consume(ipAddress) + ? await rateSuperStrict.consume(ipAddress) : await rateLimiterRedis.consume(ipAddress); + + headers = refresh + ? await rateSuperStrict.get(ipAddress) + : await rateLimiterRedis.get(ipAddress); + + console.log(headers); } catch (error) { return res.status(429).json({ - error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + error: `Too Many Requests, retry after ${getTimeFromMs( + error.msBeforeNext + )}`, + remaining: error.remainingPoints, }); } @@ -195,6 +205,9 @@ export default async function handler(req, res) { filtered = await appendMetaToEpisodes(filtered, JSON.parse(meta)); } + res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); + res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); + return res.status(200).json(filtered); } else { const filteredData = filterData(JSON.parse(cached), "sub"); @@ -205,7 +218,10 @@ export default async function handler(req, res) { filtered = await appendMetaToEpisodes(filteredData, JSON.parse(meta)); } - return res.status(200).json(filtered); + res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); + res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); + + return res.status(200).send(filtered); } } else { const [consumet, anify, cover] = await Promise.all([ @@ -256,8 +272,26 @@ export default async function handler(req, res) { .json(filtered.filter((i) => i.episodes.length > 0)); } - console.log("fresh data"); + if (redis) { + res.setHeader("X-RateLimit-Limit", refresh ? 1 : 50); + res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); + res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); + } return res.status(200).json(data.filter((i) => i.episodes.length > 0)); } } + +function getTimeFromMs(time) { + const timeInSeconds = time / 1000; + + if (timeInSeconds >= 3600) { + const hours = Math.floor(timeInSeconds / 3600); + return `${hours} hour${hours > 1 ? "s" : ""}`; + } else if (timeInSeconds >= 60) { + const minutes = Math.floor(timeInSeconds / 60); + return `${minutes} minute${minutes > 1 ? "s" : ""}`; + } else { + return `${timeInSeconds} second${timeInSeconds > 1 ? "s" : ""}`; + } +} diff --git a/pages/api/v2/etc/recent/[page].js b/pages/api/v2/etc/recent/[page].js index e21c38e..2ff22ea 100644 --- a/pages/api/v2/etc/recent/[page].js +++ b/pages/api/v2/etc/recent/[page].js @@ -1,4 +1,4 @@ -import { rateLimiterRedis, redis } from "@/lib/redis"; +import { rateLimitStrict, redis } from "@/lib/redis"; let API_URL; API_URL = process.env.API_URI || null; @@ -11,31 +11,46 @@ export default async function handler(req, res) { if (redis) { try { const ipAddress = req.socket.remoteAddress; - await rateLimiterRedis.consume(ipAddress); + await rateLimitStrict.consume(ipAddress); } catch (error) { return res.status(429).json({ error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, }); } } - const page = req.query.page || 1; - var hasNextPage = true; - var datas = []; + let cache; - async function fetchData(page) { - const data = await fetch( - `${API_URL}/meta/anilist/recent-episodes?page=${page}&perPage=30&provider=gogoanime` - ).then((res) => res.json()); - - const filtered = data?.results?.filter((i) => i.type !== "ONA"); - hasNextPage = data?.hasNextPage; - datas = filtered; + if (redis) { + cache = await redis.get(`recent-episode`); } - await fetchData(page); + if (cache) { + return res.status(200).json({ results: JSON.parse(cache) }); + } else { + const page = req.query.page || 1; + + var hasNextPage = true; + var datas = []; + + async function fetchData(page) { + const data = await fetch( + `https://api.anify.tv/recent?type=anime&page=${page}&perPage=45` + ).then((res) => res.json()); + + // const filtered = data?.results?.filter((i) => i.type !== "ONA"); + // hasNextPage = data?.hasNextPage; + datas = data; + } + + await fetchData(page); - return res.status(200).json({ hasNextPage, results: datas }); + if (redis) { + await redis.set(`recent-episode`, JSON.stringify(datas), "EX", 60 * 60); + } + + return res.status(200).json({ results: datas }); + } } catch (error) { res.status(500).json({ error }); } diff --git a/pages/en/anime/recent.js b/pages/en/anime/recent.js index 400e926..4a8111d 100644 --- a/pages/en/anime/recent.js +++ b/pages/en/anime/recent.js @@ -8,6 +8,7 @@ import { getServerSession } from "next-auth"; import { authOptions } from "../../api/auth/[...nextauth]"; import Image from "next/image"; import MobileNav from "@/components/shared/MobileNav"; +import { truncateImgUrl } from "@/utils/imageUtils"; export async function getServerSideProps(context) { const session = await getServerSession(context.req, context.res, authOptions); @@ -99,7 +100,7 @@ export default function Recent({ sessions }) { <div className="w-[140px] h-[190px] lg:w-[170px] lg:h-[230px] object-cover rounded opacity-90 z-20"> <div className="absolute bg-gradient-to-b from-black/30 to-transparent from-5% to-30% top-0 z-30 w-[140px] h-[190px] lg:w-[170px] lg:h-[230px] rounded" /> <Image - src={i.image} + src={i.image || truncateImgUrl(i.coverImage)} alt={i.title.romaji} width={500} height={500} @@ -108,13 +109,16 @@ export default function Recent({ sessions }) { </div> <Image src="/svg/episode-badge.svg" - alt="episode-bade" + alt="episode-badge" width={200} height={100} className="w-24 lg:w-28 absolute top-1 -right-[13px] lg:-right-[15px] z-40" /> <p className="absolute z-40 text-center w-[80px] lg:w-[100px] top-[5px] -right-2 lg:top-[4px] lg:-right-3 font-karla text-sm lg:text-base"> - Episode <span className="text-white">{i?.episodeNumber}</span> + Episode{" "} + <span className="text-white"> + {i?.episodeNumber || i?.currentEpisode} + </span> </p> </Link> <Link diff --git a/public/image-error-fallback.png b/public/image-error-fallback.png Binary files differnew file mode 100644 index 0000000..ddec93e --- /dev/null +++ b/public/image-error-fallback.png @@ -2,8 +2,9 @@ This document contains a summary of all significant changes made to this release. -## 🎉 Update v4.2.3 +## 🎉 Update v4.2.4 ### What's Changed -- Add: Animepahe provider +- fix: Re-Added new episodes +- fix: Cannot paste text on search bar diff --git a/utils/imageUtils.js b/utils/imageUtils.js index e5ac6a9..56c98fb 100644 --- a/utils/imageUtils.js +++ b/utils/imageUtils.js @@ -20,3 +20,14 @@ export function getHeaders(providerId) { export function getRandomId() { return Math.random().toString(36).substr(2, 9); } + +export function truncateImgUrl(url) { + // Find the index of .png if not found find the index of .jpg + let index = + url.indexOf(".png") !== -1 ? url.indexOf(".png") : url.indexOf(".jpg"); + if (index !== -1) { + // If .png is found + url = url.slice(0, index + 4); // Slice the string from the start to the index of .png plus 4 (the length of .png) + } + return url; +} |