diff options
Diffstat (limited to 'pages/api')
| -rw-r--r-- | pages/api/auth/[...nextauth].ts (renamed from pages/api/auth/[...nextauth].js) | 65 | ||||
| -rw-r--r-- | pages/api/og.tsx (renamed from pages/api/og.jsx) | 2 | ||||
| -rw-r--r-- | pages/api/v2/episode/[id].tsx (renamed from pages/api/v2/episode/[id].js) | 128 | ||||
| -rw-r--r-- | pages/api/v2/etc/recent/[page].js | 57 | ||||
| -rw-r--r-- | pages/api/v2/etc/recent/[page].tsx | 81 | ||||
| -rw-r--r-- | pages/api/v2/etc/schedule/index.tsx (renamed from pages/api/v2/etc/schedule/index.js) | 35 |
6 files changed, 217 insertions, 151 deletions
diff --git a/pages/api/auth/[...nextauth].js b/pages/api/auth/[...nextauth].ts index da78d07..70b2e3d 100644 --- a/pages/api/auth/[...nextauth].js +++ b/pages/api/auth/[...nextauth].ts @@ -1,29 +1,6 @@ -import NextAuth from "next-auth"; -import { ApolloClient, InMemoryCache, gql } from "@apollo/client"; +import NextAuth, { NextAuthOptions } from "next-auth"; -const defaultOptions = { - watchQuery: { - fetchPolicy: "no-cache", - errorPolicy: "ignore", - }, - query: { - fetchPolicy: "no-cache", - errorPolicy: "all", - }, -}; - -const client = new ApolloClient({ - uri: "https://graphql.anilist.co", - cache: new InMemoryCache(), - defaultOptions: defaultOptions, -}); - -// import clientPromise from "../../../lib/mongodb"; -// import { MongoDBAdapter } from "@next-auth/mongodb-adapter"; - -export const authOptions = { - // Configure one or more authentication providers - // adapter: MongoDBAdapter(clientPromise), +export const authOptions: NextAuthOptions = { secret: process.env.NEXTAUTH_SECRET, providers: [ { @@ -38,8 +15,17 @@ export const authOptions = { userinfo: { url: process.env.GRAPHQL_ENDPOINT, async request(context) { - const { data } = await client.query({ - query: gql` + // console.log(context.tokens.access_token); + const { data } = await fetch("https://graphql.anilist.co", { + method: "POST", + headers: { + "Content-Type": "application/json", + // ...(context.tokens.access_token && { + Authorization: `Bearer ${context.tokens.access_token}`, + // }), + }, + body: JSON.stringify({ + query: ` query { Viewer { id @@ -57,34 +43,33 @@ export const authOptions = { } } `, - context: { - headers: { - Authorization: "Bearer " + context.tokens.access_token, - }, - }, - }); + }), + }).then((res) => res.json()); - const userLists = data.Viewer.mediaListOptions.animeList.customLists; + const userLists = data.Viewer?.mediaListOptions.animeList.customLists; let custLists = userLists || []; if (!userLists?.includes("Watched using Moopa")) { custLists.push("Watched using Moopa"); - const fetchGraphQL = async (query, variables) => { + const fetchGraphQL = async ( + query: string, + variables: { lists: any } + ) => { const response = await fetch("https://graphql.anilist.co/", { method: "POST", headers: { "Content-Type": "application/json", - Authorization: context.tokens.access_token - ? `Bearer ${context.tokens.access_token}` - : undefined, + ...(context.tokens.access_token && { + Authorization: `Bearer ${context.tokens.access_token}`, + }), }, body: JSON.stringify({ query, variables }), }); return response.json(); }; - const customLists = async (lists) => { + const customLists = async (lists: any) => { const setList = ` mutation($lists: [String]){ UpdateUser(animeListOptions: { customLists: $lists }){ @@ -104,7 +89,7 @@ export const authOptions = { name: data.Viewer.name, sub: data.Viewer.id, image: data.Viewer.avatar, - list: data.Viewer.mediaListOptions.animeList.customLists, + list: data.Viewer?.mediaListOptions.animeList.customLists, }; }, }, diff --git a/pages/api/og.jsx b/pages/api/og.tsx index d52f90e..47619bc 100644 --- a/pages/api/og.jsx +++ b/pages/api/og.tsx @@ -11,7 +11,7 @@ const outfit = fetch( new URL("../../assets/Outfit-Regular.ttf", import.meta.url) ).then((res) => res.arrayBuffer()); -export default async function handler(request) { +export default async function handler(request: any) { const Karla = await karla; const Outfit = await outfit; diff --git a/pages/api/v2/episode/[id].js b/pages/api/v2/episode/[id].tsx index b601f62..b646126 100644 --- a/pages/api/v2/episode/[id].js +++ b/pages/api/v2/episode/[id].tsx @@ -1,17 +1,21 @@ +// @ts-nocheck + import axios from "axios"; import { rateLimiterRedis, rateSuperStrict, redis } from "@/lib/redis"; import appendMetaToEpisodes from "@/utils/appendMetaToEpisodes"; +import { NextApiRequest, NextApiResponse } from "next"; +import { AnifyEpisode, ConsumetInfo, EpisodeData } from "types"; +import { Episode } from "@/types/api/Episode"; +import { getProviderWithMostEpisodesAndImage } from "@/utils/parseMetaData"; -let CONSUMET_URI; +let CONSUMET_URI: string | null; CONSUMET_URI = process.env.API_URI || null; if (CONSUMET_URI && CONSUMET_URI.endsWith("/")) { CONSUMET_URI = CONSUMET_URI.slice(0, -1); } -const API_KEY = process.env.API_KEY; - -const isAscending = (data) => { +const isAscending = (data: Episode[]) => { for (let i = 1; i < data.length; i++) { if (data[i].number < data[i - 1].number) { return false; @@ -20,7 +24,16 @@ const isAscending = (data) => { return true; }; -function filterData(data, type) { +export interface RawEpisodeData { + map?: boolean; + providerId: string; + episodes: { + sub: Episode[]; + dub: Episode[]; + }; +} + +function filterData(data: RawEpisodeData[], type: "sub" | "dub") { // Filter the data based on the type (sub or dub) and providerId const filteredData = data.map((item) => { if (item?.map === true) { @@ -44,10 +57,10 @@ function filterData(data, type) { return noEmpty; } -async function fetchConsumet(id) { +async function fetchConsumet(id?: string | string[] | undefined) { try { - async function fetchData(dub) { - const { data } = await axios.get( + const fetchData = async (dub?: any) => { + const { data } = await axios.get<ConsumetInfo>( `${CONSUMET_URI}/meta/anilist/info/${id}${dub ? "?dub=true" : ""}` ); if (data?.message === "Anime not found" && data?.length < 1) { @@ -59,23 +72,32 @@ async function fetchConsumet(id) { } const reformatted = data.episodes?.map((item) => ({ - id: item?.id || null, + id: item.id, title: item?.title || null, img: item?.image || null, number: item?.number || null, - createdAt: item?.createdAt || null, + createdAt: item?.airDate || null, description: item?.description || null, - url: item?.url || null, })); return reformatted; - } + }; const [subData, dubData] = await Promise.all([ fetchData(), fetchData(true), ]); + if (subData.every((i) => i.id?.includes("dub"))) { + // replace dub in title with sub + subData.forEach((item) => { + if (item.id?.includes("dub")) { + item.id = item.id?.replace("dub", "anime"); + } + }); + console.log("replaced dub with sub"); + } + const array = [ { map: true, @@ -88,38 +110,34 @@ async function fetchConsumet(id) { ]; return array; - } catch (error) { + } catch (error: any) { console.error("Error fetching and processing data:", error.message); return []; } } -async function fetchAnify(id) { +async function fetchAnify(id?: string) { try { - const { data } = await axios.get(`https://api.anify.tv/episodes/${id}`); + const { data } = await axios.get<AnifyEpisode[]>( + `https://api.anify.tv/episodes/${id}` + ); if (!data) { return []; } - const filtered = data.filter((item) => item.providerId !== "kass"); - // const modifiedData = filtered.map((provider) => { - // if (provider.providerId === "gogoanime") { - // const reversedEpisodes = [...provider.episodes].reverse(); - // return { ...provider, episodes: reversedEpisodes }; - // } - // return provider; - // }); + const filtered = data.filter( + (item) => item.providerId !== "9anime" && item.providerId !== "kass" + ); - // return modifiedData; return filtered; - } catch (error) { + } catch (error: any) { console.error("Error fetching and processing data:", error.message); return []; } } -async function fetchCoverImage(id, available = false) { +async function fetchCoverImage(id: string, available = false) { try { if (!process.env.API_KEY) { return []; @@ -137,16 +155,20 @@ async function fetchCoverImage(id, available = false) { return []; } - const getData = data[0].data; + const getData = getProviderWithMostEpisodesAndImage(data); + // const getData = data?.[0]?.data; - return getData; - } catch (error) { + return getData.data; + } catch (error: any) { console.error("Error fetching and processing data:", error.message); return []; } } -export default async function handler(req, res) { +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { const { id, releasing = "false", dub = false, refresh = null } = req.query; // if releasing is true then cache for 1 hour, if it false cache for 1 month; @@ -159,11 +181,11 @@ export default async function handler(req, res) { let cached; let meta; - let headers; + let headers: any = {}; if (redis) { try { - const ipAddress = req.socket.remoteAddress; + const ipAddress: any = req.socket.remoteAddress; refresh ? await rateSuperStrict.consume(ipAddress) : await rateLimiterRedis.consume(ipAddress); @@ -171,9 +193,7 @@ export default async function handler(req, res) { headers = refresh ? await rateSuperStrict.get(ipAddress) : await rateLimiterRedis.get(ipAddress); - - console.log(headers); - } catch (error) { + } catch (error: any) { return res.status(429).json({ error: `Too Many Requests, retry after ${getTimeFromMs( error.msBeforeNext @@ -182,20 +202,28 @@ export default async function handler(req, res) { }); } + meta = await redis.get(`meta:${id}`); + const parsedMeta = JSON.parse(meta); + if (parsedMeta?.length === 0) { + await redis.del(`meta:${id}`); + console.log("deleted meta cache"); + meta = null; + } + if (refresh) { await redis.del(`episode:${id}`); - console.log("deleted cache"); } else { cached = await redis.get(`episode:${id}`); - console.log("using redis"); + if (cached?.length === 0) { + await redis.del(`episode:${id}`); + cached = null; + } } - - meta = await redis.get(`meta:${id}`); } if (cached && !refresh) { if (dub) { - const filteredData = filterData(JSON.parse(cached), "dub"); + const filteredData: EpisodeData[] = filterData(JSON.parse(cached), "dub"); let filtered = filteredData.filter((item) => item?.episodes?.some((epi) => epi.hasDub !== false) @@ -208,7 +236,9 @@ export default async function handler(req, res) { res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); - return res.status(200).json(filtered); + return res + .status(200) + .json(filtered?.filter((i) => i?.providerId !== "9anime")); } else { const filteredData = filterData(JSON.parse(cached), "sub"); @@ -221,11 +251,13 @@ export default async function handler(req, res) { res.setHeader("X-RateLimit-Remaining", headers.remainingPoints); res.setHeader("X-RateLimit-BeforeReset", headers.msBeforeNext); - return res.status(200).send(filtered); + return res + .status(200) + .send(filtered?.filter((i) => i?.providerId !== "9anime")); } } else { const [consumet, anify, cover] = await Promise.all([ - fetchConsumet(id, dub), + fetchConsumet(id), fetchAnify(id), fetchCoverImage(id, meta), ]); @@ -249,12 +281,16 @@ export default async function handler(req, res) { if (meta) { data = await appendMetaToEpisodes(filteredData, JSON.parse(meta)); - } else if (cover && !cover.some((e) => e.img === null)) { + } else if ( + cover && + // !cover?.some((item: { img: null }) => item.img === null) && + cover?.length > 0 + ) { if (redis) await redis.set(`meta:${id}`, JSON.stringify(cover)); data = await appendMetaToEpisodes(filteredData, cover); } - if (redis && cacheTime !== null) { + if (redis && cacheTime !== null && rawData?.length > 0) { await redis.set( `episode:${id}`, JSON.stringify(rawData), @@ -282,7 +318,7 @@ export default async function handler(req, res) { } } -function getTimeFromMs(time) { +function getTimeFromMs(time: number) { const timeInSeconds = time / 1000; if (timeInSeconds >= 3600) { diff --git a/pages/api/v2/etc/recent/[page].js b/pages/api/v2/etc/recent/[page].js deleted file mode 100644 index 2ff22ea..0000000 --- a/pages/api/v2/etc/recent/[page].js +++ /dev/null @@ -1,57 +0,0 @@ -import { rateLimitStrict, redis } from "@/lib/redis"; - -let API_URL; -API_URL = process.env.API_URI || null; -if (API_URL && API_URL.endsWith("/")) { - API_URL = API_URL.slice(0, -1); -} - -export default async function handler(req, res) { - try { - if (redis) { - try { - const ipAddress = req.socket.remoteAddress; - await rateLimitStrict.consume(ipAddress); - } catch (error) { - return res.status(429).json({ - error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, - }); - } - } - - let cache; - - if (redis) { - cache = await redis.get(`recent-episode`); - } - - 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); - - 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/api/v2/etc/recent/[page].tsx b/pages/api/v2/etc/recent/[page].tsx new file mode 100644 index 0000000..e49591c --- /dev/null +++ b/pages/api/v2/etc/recent/[page].tsx @@ -0,0 +1,81 @@ +import { rateLimitStrict, redis } from "@/lib/redis"; +import { AnifyRecentEpisode } from "@/utils/types"; +import axios from "axios"; +import { NextApiRequest, NextApiResponse } from "next"; + +let API_URL: string | null; +API_URL = process.env.API_URI || null; +if (API_URL && API_URL.endsWith("/")) { + API_URL = API_URL.slice(0, -1); +} + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + try { + if (redis) { + try { + const ipAddress: any = req.socket.remoteAddress; + await rateLimitStrict?.consume(ipAddress); + } catch (error: any) { + return res.status(429).json({ + error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, + }); + } + } + + let cache; + + if (redis) { + cache = await redis.get(`recent-episode`); + } + + if (cache) { + return res.status(200).json({ results: JSON.parse(cache) }); + } else { + const page = req.query.page || 1; + + var hasNextPage = true; + let datas: AnifyRecentEpisode[] = []; + + const fetchData = async (page: any) => { + const { data } = await axios.get( + `https://api.anify.tv/recent?type=anime&page=${page}&perPage=45&fields=[id,slug,title,currentEpisode,coverImage,episodes]` + ); + + // const filtered = data?.results?.filter((i) => i.type !== "ONA"); + // hasNextPage = data?.hasNextPage; + + const newData = data.map((i: AnifyRecentEpisode) => { + const getGogo = i.episodes?.data?.find( + (x) => x.providerId === "gogoanime" + ); + const getGogoEpisode = getGogo?.episodes?.find( + (x) => x.number === i.currentEpisode + ); + + return { + id: i.id, + slug: getGogoEpisode?.id, + title: i.title, + currentEpisode: i.currentEpisode, + coverImage: i.coverImage, + }; + }); + + datas = newData; + }; + + await fetchData(page); + + 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/api/v2/etc/schedule/index.js b/pages/api/v2/etc/schedule/index.tsx index 2ddc82a..e6f0b26 100644 --- a/pages/api/v2/etc/schedule/index.js +++ b/pages/api/v2/etc/schedule/index.tsx @@ -1,6 +1,7 @@ import axios from "axios"; import cron from "cron"; import { rateLimiterRedis, redis } from "@/lib/redis"; +import { NextApiRequest, NextApiResponse } from "next"; // Function to fetch new data async function fetchData() { @@ -37,22 +38,42 @@ const job = new cron.CronJob("0 0 * * 1", () => { }); job.start(); -export default async function handler(req, res) { +interface Title { + romaji: string; + english: string; + native: string; +} + +type CachedData = { + id: string; + title: Title; + coverImage: string; + bannerImage: string; + airingAt: number; + airingEpisode: number; +}; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { try { - let cached; + let cached: CachedData | null = null; if (redis) { try { - const ipAddress = req.socket.remoteAddress; - await rateLimiterRedis.consume(ipAddress); - } catch (error) { + const ipAddress: any = req.socket.remoteAddress; + await rateLimiterRedis?.consume(ipAddress); + } catch (error: any) { return res.status(429).json({ error: `Too Many Requests, retry after ${error.msBeforeNext / 1000}`, }); } - cached = await redis.get("schedule"); + const cachedData = await redis.get("schedule"); + cached = cachedData ? JSON.parse(cachedData) : null; } + if (cached) { - return res.status(200).json(JSON.parse(cached)); + return res.status(200).json(cached); } else { const data = await fetchData(); |