fix: Full diagnostic sweep — bugs, dead code, unused imports, docs accuracy

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
This commit is contained in:
Shinei Nouzen
2026-07-30 00:50:18 +05:30
parent 715add099b
commit 996cead3b1
21 changed files with 24 additions and 40 deletions
+6 -6
View File
@@ -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 (<code>*</code>). Just make fetch requests
<details>
<summary><b>🔄 How often does the data refresh?</b></summary>
<br/>
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.
</details>
<details>
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -3
View File
@@ -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
+3 -2
View File
@@ -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
+2 -3
View File
@@ -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 |
+1 -1
View File
@@ -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
-7
View File
@@ -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);
-1
View File
@@ -17,7 +17,6 @@
*/
import * as cheerio from "cheerio";
import { URLS } from "../configs/dataUrl.js";
import { fetchWithMirror } from "../helper/mirror.helper.js";
// ══════════════════════════════════════════════════════════════
-2
View File
@@ -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";
// ══════════════════════════════════════════════════════════════
@@ -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";
+1 -1
View File
@@ -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);
+1 -1
View File
@@ -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<Object>>} Array of ranked anime objects
* @returns {number} return[].rank - Ranking position (1-9)
* @returns {string} return[].slug - URL slug for the anime
-1
View File
@@ -16,7 +16,6 @@
*/
import * as cheerio from "cheerio";
import { URLS } from "../configs/dataUrl.js";
import { fetchWithMirror } from "../helper/mirror.helper.js";
// ══════════════════════════════════════════════════════════════
@@ -16,7 +16,6 @@
*/
import * as cheerio from "cheerio";
import { URLS } from "../configs/dataUrl.js";
import { fetchWithMirror } from "../helper/mirror.helper.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
+1 -1
View File
@@ -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);
-1
View File
@@ -16,7 +16,6 @@
*/
import * as cheerio from "cheerio";
import { URLS, BASE_URL } from "../configs/dataUrl.js";
import { fetchWithMirror } from "../helper/mirror.helper.js";
// ══════════════════════════════════════════════════════════════
+1 -1
View File
@@ -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
};
}
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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" });