From 996cead3b1eee3d098b74f5c876b1772951ee884 Mon Sep 17 00:00:00 2001 From: Shinei Nouzen <157171073+Shineii86@users.noreply.github.com> Date: Thu, 30 Jul 2026 00:49:57 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20Full=20diagnostic=20sweep=20=E2=80=94=20?= =?UTF-8?q?bugs,=20dead=20code,=20unused=20imports,=20docs=20accuracy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Code fixes: - seasons/watchOrder: Remove dead ternary (both branches identical) - mirror.helper: Remove redundant ternary (both branches identical) - topAnimeRankings: Fix JSDoc (day/week/month, not top/newest) - apiRoutes: Fix proxy domain validation (exact match, not includes) - apiRoutes: Fix proxy .m3u8 check (pathname, not hostname) - pagination: Fix totalItems calculation (was overestimating) - server.js: Remove dead has404File variable - server.js: Remove duplicate /api/cache/stats route - server.js: Remove unused getCacheStats import Unused imports removed: - animeInfo: URLS from dataUrl.js - episodeList: headers from header.config.js - episodeListAjax: axios - trending: URLS from dataUrl.js - trendingSidebar: URLS from dataUrl.js - upcomingAnime: parseListItems - watchPage: URLS, BASE_URL from dataUrl.js Docs fixes: - README: 33->38 endpoints, 24->28 controllers, 26->28 extractors - README: Cache TTL 5min->per-endpoint, Timeout 15s->30s - docs/index.md: 33->38 endpoints - architecture.md: LRU cache strategy, remove stale cookie-parser - agents: compression version, extractor/controller counts - agents: domain typo, stale CORS dep, version badge --- README.md | 12 ++++++------ agents/backend-architect.md | 6 +++--- agents/devops-automator.md | 2 +- agents/security-architect.md | 5 ++--- agents/technical-writer.md | 5 +++-- docs/architecture.md | 5 ++--- docs/readme.md | 2 +- server.js | 7 ------- src/extractors/animeInfo.extractor.js | 1 - src/extractors/episodeList.extractor.js | 2 -- src/extractors/episodeListAjax.extractor.js | 1 - src/extractors/seasons.extractor.js | 2 +- src/extractors/topAnimeRankings.extractor.js | 2 +- src/extractors/trending.extractor.js | 1 - src/extractors/trendingSidebar.extractor.js | 1 - src/extractors/upcomingAnime.extractor.js | 1 - src/extractors/watchOrder.extractor.js | 2 +- src/extractors/watchPage.extractor.js | 1 - src/helper/mirror.helper.js | 2 +- src/helper/pagination.helper.js | 2 +- src/routes/apiRoutes.js | 2 +- 21 files changed, 24 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 260a1d6..d36bf6d 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ flowchart TD ### ⚡ Core - **Real-time scraping** from anikototv.to - **Smart caching** with LRU and configurable TTL per endpoint -- **33 RESTful endpoints** covering all data +- **38 RESTful endpoints** covering all data - **AJAX episode loading** for accurate data - **Mapper API integration** for extra servers - **Graceful error handling** per endpoint @@ -320,7 +320,7 @@ AniKotoAPI/ │ │ ├── 📄 header.config.js # 📋 Request headers │ │ └── 📄 ids.config.js # 🏷️ Genre/Type/Status/Source/Season ID mappings │ │ -│ ├── 📂 controllers/ # 🎮 Route handlers (24 files) +│ ├── 📂 controllers/ # 🎮 Route handlers (28 files) │ │ ├── 📄 homeInfo.controller.js │ │ ├── 📄 animeInfo.controller.js │ │ ├── 📄 search.controller.js @@ -332,7 +332,7 @@ AniKotoAPI/ │ │ ├── 📄 watchPage.controller.js │ │ └── 📄 ... (15 more) │ │ -│ ├── 📂 extractors/ # 🔍 HTML scrapers (26 files) +│ ├── 📂 extractors/ # 🔍 HTML scrapers (28 files) │ │ ├── 📄 homeInfo.extractor.js # 🏠 Home page data │ │ ├── 📄 search.extractor.js # 🔍 Search results │ │ ├── 📄 animeInfo.extractor.js # 📺 Anime details @@ -2298,8 +2298,8 @@ CMD ["node", "server.js"] |:---|:---| | ⚡ Cached response | ~200ms | | 🔄 Fresh fetch | ~1-3s | -| 💾 Cache TTL | 5 minutes | -| ⏱️ Timeout per request | 15 seconds | +| 💾 Cache TTL | 3 min–1 hour (per-endpoint) | +| ⏱️ Timeout per request | 30 seconds | | 🎬 Total anime indexed | 10,000+ | | 📦 Total codebase | ~120KB | @@ -2390,7 +2390,7 @@ Yes! CORS is enabled for all origins (*). Just make fetch requests
🔄 How often does the data refresh?
-The cache TTL is 5 minutes. After that, the next request triggers a fresh scrape from anikototv.to. This keeps data relatively fresh while reducing load on the source site. +The cache uses an LRU strategy with per-endpoint TTLs ranging from 3 minutes (search, stream) to 1 hour (schedule). After TTL expiry, the next request triggers a fresh scrape from anikototv.to.
diff --git a/agents/backend-architect.md b/agents/backend-architect.md index 2344ece..d613eac 100644 --- a/agents/backend-architect.md +++ b/agents/backend-architect.md @@ -12,7 +12,7 @@ You are **Backend Architect** for AniKotoAPI, a free REST API scraping anime dat ## Your Identity - **Project**: AniKotoAPI v2.2.0 — https://github.com/Shineii86/AniKotoAPI -- **Stack**: Node.js (ESM modules), Express 4.21, Cheerio 1.0, Axios 1.8, compression 1.8 +- **Stack**: Node.js (ESM modules), Express 4.21, Cheerio 1.0, Axios 1.8, compression 1.7 - **Deployment**: Vercel serverless (read-only filesystem, `/tmp` only writable) - **Data Source**: anikototv.to (5 mirror domains: anikototv.to, anikoto.cz, anikoto.me, anikoto.net, anikototv.se) @@ -30,8 +30,8 @@ Client → Vercel Edge/Express → Rate Limiter → CORS + Security Headers → - `src/helper/mirror.helper.js` — Multi-mirror fallback with health checks, session caching - `src/helper/extractPages.helper.js` — Page fetching using `fetchWithMirror()` - `src/helper/parseListItem.helper.js` — Shared list item parser (DRY across extractors) -- `src/extractors/` — 26 HTML scrapers using Cheerio selectors -- `src/controllers/` — 26 route handlers +- `src/extractors/` — 28 HTML scrapers using Cheerio selectors +- `src/controllers/` — 28 route handlers - `src/configs/ids.config.js` — Genre/Type/Status/Rating/Sort/Source/Season ID mappings ### Endpoints (38 total) diff --git a/agents/devops-automator.md b/agents/devops-automator.md index a444d43..7945d25 100644 --- a/agents/devops-automator.md +++ b/agents/devops-automator.md @@ -91,7 +91,7 @@ git remote set-url origin https://github.com/Shineii86/AniKotoAPI.git ### Version Bump Checklist 1. [ ] Update `package.json` version -2. [ ] Update README.md badge (`Version-2.0.0`) +2. [ ] Update README.md badge (`Version-2.2.0`) 3. [ ] Add CHANGELOG.md entry (prepend at top) 4. [ ] Update OpenAPI spec version in `apiRoutes.js` 5. [ ] Update endpoint count if changed diff --git a/agents/security-architect.md b/agents/security-architect.md index 1f144c3..ce655b1 100644 --- a/agents/security-architect.md +++ b/agents/security-architect.md @@ -38,7 +38,7 @@ You are **Security Architect** for AniKotoAPI, responsible for securing a public ```javascript // ALLOWED_ORIGINS env var (comma-separated) // Default: allow all origins -// Only GET method allowed +// GET and POST methods allowed // Handles OPTIONS preflight ``` @@ -99,8 +99,7 @@ You are **Security Architect** for AniKotoAPI, responsible for securing a public | express | 4.21 | Check for known CVEs | | axios | 1.8 | Check for SSRF bugs | | cheerio | 1.0-rc.12 | Check for prototype pollution | -| cors | 2.8 | Check for bypass issues | -| compression | 1.8 | Check for BREACH attacks | +| compression | 1.7 | Check for BREACH attacks | | dotenv | 16.4 | Check for env injection | ## What You Do diff --git a/agents/technical-writer.md b/agents/technical-writer.md index 5514933..a31bee3 100644 --- a/agents/technical-writer.md +++ b/agents/technical-writer.md @@ -19,7 +19,7 @@ You are **Technical Writer** for AniKotoAPI, responsible for maintaining all doc ``` AniKotoAPI/ -├── README.md # Main docs (2200+ lines) +├── README.md # Main docs (2600+ lines) ├── CHANGELOG.md # Version history (append only) ├── CONTRIBUTING.md # Contribution guidelines ├── docs/ @@ -27,7 +27,8 @@ AniKotoAPI/ │ ├── endpoints.md # Full API reference (38 endpoints) │ ├── streaming.md # 3-step streaming flow guide │ ├── examples.md # cURL, JS, Python examples -│ └── architecture.md # Project structure, tech stack +│ ├── architecture.md # Project structure, tech stack +│ └── testing.md # Integration test suite & benchmarks └── public/ ├── index.html # Landing page ├── tos.html # Terms of Service diff --git a/docs/architecture.md b/docs/architecture.md index 2b34994..dc06226 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -178,8 +178,8 @@ Client Response ## Caching Strategy -- **Type:** In-memory Map -- **TTL:** 5 minutes (300,000ms) +- **Type:** LRU Cache (Map-based with TTL expiration) +- **TTL:** Per-endpoint (3 min for search/stream, 5 min for home/trending, 10 min for info, 1 hour for schedule) - **Key:** Full request URL - **Behavior:** First request fetches from source, subsequent requests served from cache - **Reset:** TTL resets on each access @@ -252,6 +252,5 @@ Error responses: | express | ^4.21.0 | Web framework | | cheerio | ^1.0.0-rc.12 | HTML parsing | | axios | ^1.8.0 | HTTP requests | -| cookie-parser | ^1.4.7 | Cookie parsing | | dotenv | ^16.4.0 | Environment variables | | compression | ^1.7.4 | Response compression | diff --git a/docs/readme.md b/docs/readme.md index f005a6a..6c9dff7 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -57,7 +57,7 @@ curl "https://anikototvapi.vercel.app/api/info?id=naruto-eybxz" ## Features -- **33 Endpoints** — Home, search, info, episodes, streaming, schedule, seasons, watch order, download, and more +- **38 Endpoints** — Home, search, info, episodes, streaming, schedule, seasons, watch order, download, and more - **No API Key** — Just make requests, no registration needed - **LRU Cache** — Configurable TTL per endpoint type (3min to 60min) - **Multi-Mirror Fallback** — Automatic failover across 5 mirror domains diff --git a/server.js b/server.js index cb54cd8..38b07f9 100644 --- a/server.js +++ b/server.js @@ -25,7 +25,6 @@ import crypto from "crypto"; import { fileURLToPath } from "url"; import { dirname } from "path"; import { createApiRoutes } from "./src/routes/apiRoutes.js"; -import { getCacheStats } from "./src/helper/cache.helper.js"; import { addCreatorInfo } from "./src/middleware/creatorInfo.js"; dotenv.config(); @@ -39,7 +38,6 @@ const PORT = process.env.PORT || 4444; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const publicDir = path.join(process.cwd(), "public"); -const has404File = fs.existsSync(path.join(publicDir, "404.html")); const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(","); // ---- FEATURE: Response compression ---- @@ -188,11 +186,6 @@ app.use((req, res, next) => { next(); }); -// ---- FEATURE: Cache stats endpoint ---- -app.get("/api/cache/stats", (req, res) => { - res.json({ success: true, results: getCacheStats() }); -}); - // ---- FEATURE: Creator info middleware (injects attribution into all responses) ---- app.use(addCreatorInfo); diff --git a/src/extractors/animeInfo.extractor.js b/src/extractors/animeInfo.extractor.js index f201d19..78ccdb7 100644 --- a/src/extractors/animeInfo.extractor.js +++ b/src/extractors/animeInfo.extractor.js @@ -17,7 +17,6 @@ */ import * as cheerio from "cheerio"; -import { URLS } from "../configs/dataUrl.js"; import { fetchWithMirror } from "../helper/mirror.helper.js"; // ══════════════════════════════════════════════════════════════ diff --git a/src/extractors/episodeList.extractor.js b/src/extractors/episodeList.extractor.js index e4da435..8422441 100644 --- a/src/extractors/episodeList.extractor.js +++ b/src/extractors/episodeList.extractor.js @@ -17,8 +17,6 @@ */ import * as cheerio from "cheerio"; -import { headers } from "../configs/header.config.js"; -import { URLS, BASE_URL } from "../configs/dataUrl.js"; import { fetchWithMirror } from "../helper/mirror.helper.js"; // ══════════════════════════════════════════════════════════════ diff --git a/src/extractors/episodeListAjax.extractor.js b/src/extractors/episodeListAjax.extractor.js index ad5570b..f45c50e 100644 --- a/src/extractors/episodeListAjax.extractor.js +++ b/src/extractors/episodeListAjax.extractor.js @@ -17,7 +17,6 @@ */ import * as cheerio from "cheerio"; -import axios from "axios"; import { headers } from "../configs/header.config.js"; import { fetchWithMirror } from "../helper/mirror.helper.js"; diff --git a/src/extractors/seasons.extractor.js b/src/extractors/seasons.extractor.js index 5f5451b..7bb3864 100644 --- a/src/extractors/seasons.extractor.js +++ b/src/extractors/seasons.extractor.js @@ -40,7 +40,7 @@ import { fetchWithMirror } from "../helper/mirror.helper.js"; */ const extractSeasons = async (slugOrId) => { try { - const path = /^\d+$/.test(slugOrId) ? `/watch/${slugOrId}` : `/watch/${slugOrId}`; + const path = `/watch/${slugOrId}`; const { data } = await fetchWithMirror(path); const html = typeof data === "string" ? data : String(data); diff --git a/src/extractors/topAnimeRankings.extractor.js b/src/extractors/topAnimeRankings.extractor.js index 8f478dd..93876a7 100644 --- a/src/extractors/topAnimeRankings.extractor.js +++ b/src/extractors/topAnimeRankings.extractor.js @@ -28,7 +28,7 @@ import { fetchWithMirror } from "../helper/mirror.helper.js"; * Fetches and parses the top anime rankings from the homepage. * Supports switching between "top" (all-time) and "newest" rankings. * - * @param {string} sort - Ranking sort mode: "top" or "newest" (default: "top") + * @param {string} sort - Ranking sort mode: "day", "week", or "month" (default: "day") * @returns {Promise>} Array of ranked anime objects * @returns {number} return[].rank - Ranking position (1-9) * @returns {string} return[].slug - URL slug for the anime diff --git a/src/extractors/trending.extractor.js b/src/extractors/trending.extractor.js index 71eee9d..64ce1a4 100644 --- a/src/extractors/trending.extractor.js +++ b/src/extractors/trending.extractor.js @@ -16,7 +16,6 @@ */ import * as cheerio from "cheerio"; -import { URLS } from "../configs/dataUrl.js"; import { fetchWithMirror } from "../helper/mirror.helper.js"; // ══════════════════════════════════════════════════════════════ diff --git a/src/extractors/trendingSidebar.extractor.js b/src/extractors/trendingSidebar.extractor.js index 6bfccf3..c89925a 100644 --- a/src/extractors/trendingSidebar.extractor.js +++ b/src/extractors/trendingSidebar.extractor.js @@ -16,7 +16,6 @@ */ import * as cheerio from "cheerio"; -import { URLS } from "../configs/dataUrl.js"; import { fetchWithMirror } from "../helper/mirror.helper.js"; // ══════════════════════════════════════════════════════════════ diff --git a/src/extractors/upcomingAnime.extractor.js b/src/extractors/upcomingAnime.extractor.js index 16d5b37..c8a6c5b 100644 --- a/src/extractors/upcomingAnime.extractor.js +++ b/src/extractors/upcomingAnime.extractor.js @@ -17,7 +17,6 @@ import * as cheerio from "cheerio"; import { fetchWithMirror } from "../helper/mirror.helper.js"; -import { parseListItems } from "../helper/parseListItem.helper.js"; // ══════════════════════════════════════════════════════════════ // UPCOMING ANIME EXTRACTION diff --git a/src/extractors/watchOrder.extractor.js b/src/extractors/watchOrder.extractor.js index d7e52e1..a9ad0a4 100644 --- a/src/extractors/watchOrder.extractor.js +++ b/src/extractors/watchOrder.extractor.js @@ -40,7 +40,7 @@ import { fetchWithMirror } from "../helper/mirror.helper.js"; */ const extractWatchOrder = async (slugOrId) => { try { - const path = /^\d+$/.test(slugOrId) ? `/watch/${slugOrId}` : `/watch/${slugOrId}`; + const path = `/watch/${slugOrId}`; const { data } = await fetchWithMirror(path); const html = typeof data === "string" ? data : String(data); diff --git a/src/extractors/watchPage.extractor.js b/src/extractors/watchPage.extractor.js index 23e7a2a..3c6258b 100644 --- a/src/extractors/watchPage.extractor.js +++ b/src/extractors/watchPage.extractor.js @@ -16,7 +16,6 @@ */ import * as cheerio from "cheerio"; -import { URLS, BASE_URL } from "../configs/dataUrl.js"; import { fetchWithMirror } from "../helper/mirror.helper.js"; // ══════════════════════════════════════════════════════════════ diff --git a/src/helper/mirror.helper.js b/src/helper/mirror.helper.js index f6ff594..d45c2db 100644 --- a/src/helper/mirror.helper.js +++ b/src/helper/mirror.helper.js @@ -201,7 +201,7 @@ async function fetchWithMirror(path, options = {}) { console.log(`[MIRROR] Switched to: ${mirror}`); } return { - data: returnType === "text" ? response.data : response.data, + data: response.data, mirror }; } diff --git a/src/helper/pagination.helper.js b/src/helper/pagination.helper.js index 7b6d3fa..35cf0ff 100644 --- a/src/helper/pagination.helper.js +++ b/src/helper/pagination.helper.js @@ -45,7 +45,7 @@ const addPaginationMeta = (data, currentPage, itemsPerPage = 30) => { const totalPages = data.totalPages || 1; const page = parseInt(currentPage) || 1; - const totalItems = data.data ? data.data.length * totalPages : 0; + const totalItems = data.data ? data.data.length + (totalPages - 1) * itemsPerPage : 0; return { ...data, diff --git a/src/routes/apiRoutes.js b/src/routes/apiRoutes.js index 0bb99f4..ecae657 100644 --- a/src/routes/apiRoutes.js +++ b/src/routes/apiRoutes.js @@ -238,7 +238,7 @@ app.use((req, res, next) => { // NOTE: Validate URL is from known streaming domains const allowedDomains = ["vidtube.site", "vidplay.site", "megaplay.buzz", "megaplay-1.buzz", "cdn.anipixcdn.co"]; const urlObj = new URL(url); - const isAllowed = allowedDomains.some(d => urlObj.hostname.includes(d)) || urlObj.hostname.endsWith(".m3u8"); + const isAllowed = allowedDomains.some(d => urlObj.hostname === d || urlObj.hostname.endsWith(`.${d}`)) || urlObj.pathname.endsWith(".m3u8"); if (!isAllowed) { return res.status(403).json({ success: false, message: "Domain not allowed for proxy" });