aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFactiven <[email protected]>2023-10-28 22:50:51 +0700
committerFactiven <[email protected]>2023-10-28 22:50:51 +0700
commita25282429761ff0670a50fd74f8c24bdb38e728c (patch)
tree0cd14840e665f1912842967de7427a31556190e6
parentUpdate v4.2.3 (diff)
downloadmoopa-a25282429761ff0670a50fd74f8c24bdb38e728c.tar.xz
moopa-a25282429761ff0670a50fd74f8c24bdb38e728c.zip
Update v4.2.4
-rw-r--r--components/admin/dashboard/index.js3
-rw-r--r--components/admin/layout.js1
-rw-r--r--components/anime/episode.js72
-rw-r--r--components/home/content.js3
-rw-r--r--components/listEditor.js1
-rw-r--r--components/search/searchByImage.js2
-rw-r--r--components/searchPalette.js3
-rw-r--r--components/shared/NavBar.js1
-rw-r--r--lib/redis.js13
-rw-r--r--pages/api/v2/episode/[id].js44
-rw-r--r--pages/api/v2/etc/recent/[page].js45
-rw-r--r--pages/en/anime/recent.js10
-rw-r--r--public/image-error-fallback.pngbin0 -> 15431 bytes
-rw-r--r--release.md5
-rw-r--r--utils/imageUtils.js11
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
new file mode 100644
index 0000000..ddec93e
--- /dev/null
+++ b/public/image-error-fallback.png
Binary files differ
diff --git a/release.md b/release.md
index 0f80604..0fe5909 100644
--- a/release.md
+++ b/release.md
@@ -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;
+}