From 8f423bf39cf82c89510f4e348fe533e18c4cf94d Mon Sep 17 00:00:00 2001 From: Shinei Nouzen <157171073+Shineii86@users.noreply.github.com> Date: Thu, 30 Jul 2026 08:28:51 +0530 Subject: [PATCH] docs: Complete documentation overhaul with live API data + bug fixes Documentation (5800+ lines total): - endpoints.md: All 43 endpoints documented with real response examples, param tables, response patterns, common workflows, error formats - streaming.md: Complete streaming workflow guide with HLS.js/Plyr/Video.js integration, M3U8 proxy explanation, error handling - examples.md: Working code examples in cURL, JS fetch, Python requests, Node.js axios, HTML/JS player integration (2240 lines) - architecture.md: Full project structure, request flow diagrams, caching strategy, mirror failover, security, deployment notes - index.md: Overview with quick start, feature highlights, endpoint tables Bug fixes: - episodeList.extractor: Parse JSON response before Cheerio (was passing raw JSON string as HTML, causing escaped quotes in IDs) - episodeListAjax.extractor: Same JSON parsing fix - topAnimeRankings.controller: Fix JSDoc and default sort from top to day (actual values are day/week/month, not top/newest) - apiRoutes.js: Fix OpenAPI spec sort description for top-rankings Tests: 35/35 passing --- docs/architecture.md | 1102 ++++++-- docs/endpoints.md | 306 ++- docs/examples.md | 2328 +++++++++++++++-- docs/index.md | 405 +++ docs/streaming.md | 1095 ++++++-- .../topAnimeRankings.controller.js | 8 +- src/extractors/episodeList.extractor.js | 3 +- src/extractors/episodeListAjax.extractor.js | 3 +- src/routes/apiRoutes.js | 2 +- 9 files changed, 4436 insertions(+), 816 deletions(-) create mode 100644 docs/index.md diff --git a/docs/architecture.md b/docs/architecture.md index dc06226..ce7386f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,256 +1,896 @@ -# Architecture +/** + * ============================================================================ + * AniKotoAPI — Architecture Document + * Version : 2.2.0 + * Generated: 2026-07-30 + * ============================================================================ + * + * Free REST API for scraping anime data from anikototv.to + * Tech Stack: Node.js (ESM) · Express 4.21 · Cheerio 1.0 · Axios 1.8 + * compression 1.7 · LRU Cache · Vercel Serverless + * + * Table of Contents + * ────────────────────────────────────────────────────────────────────────── + * 1. High-Level Architecture + * 2. Request Flow + * 3. Directory Layout + * 4. Component Responsibilities + * 5. Data Flow + * 6. Mirror Failover Strategy + * 7. Caching Strategy + * 8. Rate Limiting & Compression + * 9. Error Handling Strategy + * 10. Security Considerations + * 11. Deployment (Vercel Serverless) + * 12. Testing Strategy + * 13. Versioning & Changelog Notes + * ============================================================================ + */ -## Project Structure +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 1 — HIGH-LEVEL ARCHITECTURE +// ═══════════════════════════════════════════════════════════════════════════ -``` -AniKotoAPI/ -├── server.js # Express entry point, port 4444 -├── package.json # name: "AniKotoAPI" -├── vercel.json # Routes /api/* and /* to server.js -├── .env # Configuration (see .env.example) -│ -├── public/ # Static files served from process.cwd() -│ ├── index.html # Premium landing page (56KB, SVG icons, live console) -│ ├── 404.html # Glitch animation error page -│ ├── manifest.json # PWA manifest (theme: #A855F7) -│ ├── robots.txt # Crawler directives -│ ├── sitemap.xml # 4 pages (/, /tos, /privacy, /api) -│ ├── og-image.svg # SVG Open Graph image -│ ├── privacy.html # Privacy policy (served at /privacy) -│ └── tos.html # Terms of service (served at /tos) -│ -├── docs/ # API documentation -│ ├── index.md # Overview, quick start, features -│ ├── endpoints.md # Full API reference (38 endpoints) -│ ├── streaming.md # Streaming flow guide + resolver/proxy -│ ├── examples.md # cURL, JavaScript, Python, Node.js -│ └── architecture.md # This file -│ -├── src/ -│ ├── configs/ -│ │ ├── dataUrl.js # URL patterns, BASE_URL: https://anikototv.to -│ │ ├── header.config.js # Request headers (User-Agent, Referer, etc.) -│ │ └── ids.config.js # Genre/type/status/source/season ID mappings -│ │ -│ ├── routes/ -│ │ ├── apiRoutes.js # All route definitions + OpenAPI spec (38 endpoints) -│ │ └── category.route.js # genre/:name, type/:name, status/:name -│ │ -│ ├── controllers/ -│ │ ├── homeInfo.controller.js # Homepage data -│ │ ├── search.controller.js # Anime search -│ │ ├── animeInfo.controller.js # Anime info (accepts ?id= or ?slug=) -│ │ ├── episodeList.controller.js # Episode list -│ │ ├── episodeListAjax.controller.js # AJAX episode list -│ │ ├── servers.controller.js # Server list -│ │ ├── stream.controller.js # Stream URL -│ │ ├── streamResolver.controller.js # 🆕 Stream URL resolution + quality detection -│ │ ├── suggestion.controller.js # Anime suggestions -│ │ ├── spotlight.controller.js # Spotlight anime -│ │ ├── trending.controller.js # Trending anime -│ │ ├── topten.controller.js # Top 10 -│ │ ├── topAnimeRankings.controller.js # 🆕 Top rankings -│ │ ├── upcomingAnime.controller.js # 🆕 Upcoming anime -│ │ ├── recentlyUpdatedTabs.controller.js # 🆕 Recently updated with tabs -│ │ ├── completedAnime.controller.js # 🆕 Completed anime -│ │ ├── schedule.controller.js # Schedule -│ │ ├── random.controller.js # Random anime -│ │ ├── newRelease.controller.js # New releases + Latest Updated -│ │ ├── popular.controller.js # Most popular -│ │ ├── category.controller.js # Genre/Type/Status filter -│ │ └── filter.controller.js # Advanced filter -│ │ -│ ├── extractors/ -│ │ ├── homeInfo.extractor.js # Homepage extraction -│ │ ├── search.extractor.js # Search results -│ │ ├── animeInfo.extractor.js # Anime details -│ │ ├── episodeList.extractor.js # Episode list + server_ids -│ │ ├── episodeListAjax.extractor.js # AJAX episode list -│ │ ├── serverList.extractor.js # Server list -│ │ ├── streamInfo.extractor.js # Stream URL extraction (with session cookies) -│ │ ├── streamResolver.extractor.js # 🆕 Stream URL resolution, M3U8 parser, server normalization -│ │ ├── spotlight.extractor.js # Spotlight anime -│ │ ├── trending.extractor.js # Trending anime -│ │ ├── topTen.extractor.js # Top 10 -│ │ ├── topAnimeRankings.extractor.js # 🆕 Top rankings from sidebar -│ │ ├── upcomingAnime.extractor.js # 🆕 Upcoming anime -│ │ ├── recentlyUpdatedTabs.extractor.js # 🆕 Recently updated with tab filtering -│ │ ├── completedAnime.extractor.js # 🆕 Completed anime from top-table -│ │ ├── schedule.extractor.js # Schedule -│ │ ├── random.extractor.js # Random anime -│ │ ├── newRelease.extractor.js # New releases + Latest Updated -│ │ ├── mostPopular.extractor.js # Most popular -│ │ ├── genre.extractor.js # Genre filter -│ │ ├── filter.extractor.js # Advanced filter (with source/season params) -│ │ ├── suggestion.extractor.js # Suggestions -│ │ ├── seasons.extractor.js # Seasons from watch page sidebar -│ │ ├── watchOrder.extractor.js # Watch order from watch page sidebar -│ │ └── download.extractor.js # Download links -│ │ -│ └── helper/ -│ ├── cache.helper.js # LRU cache with configurable TTL -│ ├── mirror.helper.js # Multi-mirror fallback (5 domains, 404-safe) -│ ├── extractPages.helper.js # Page fetching with mirror fallback -│ ├── formatTitle.helper.js # Title formatter -│ ├── countPages.helper.js # Pagination counter -│ ├── parseListItem.helper.js # 🆕 Shared list item parser with self-anchor detection -│ └── pagination.helper.js # Pagination metadata generator -│ -│ └── middleware/ -│ └── creatorInfo.js # Creator attribution injection -│ -├── test.js # Test suite (38 tests) -├── agents/ # AI agent prompts (6 agents) -└── CHANGELOG.md # Version history -``` +/** + * The system follows a layered architecture with clean separation between + * routing, business logic (controllers), and data extraction (scrapers). + * Vercel provides the serverless runtime; Express handles middleware and + * routing; Cheerio handles server-side HTML parsing. + * + * ┌─────────────────────────────────────────────────────────────────────┐ + * │ CLIENT (Browser / App) │ + * └──────────────────────────┬──────────────────────────────────────────┘ + * │ HTTPS + * ▼ + * ┌─────────────────────────────────────────────────────────────────────┐ + * │ VERCEL EDGE / CDN │ + * │ (TLS termination, static assets) │ + * └──────────────────────────┬──────────────────────────────────────────┘ + * │ + * ▼ + * ┌─────────────────────────────────────────────────────────────────────┐ + * │ api/index.js (Serverless Handler) │ + * │ │ + * │ ┌───────────────────────────────────────────────────────────────┐ │ + * │ │ server.js (Express App) │ │ + * │ │ │ │ + * │ │ ┌─────────┐ ┌────────────┐ ┌───────────────────────────┐ │ │ + * │ │ │ CORS │ │Compression │ │ Rate Limiter │ │ │ + * │ │ │ Middleware│ │ (gzip) │ │ (100 req / 15 min) │ │ │ + * │ │ └─────────┘ └────────────┘ └───────────────────────────┘ │ │ + * │ │ │ │ + * │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + * │ │ │ ROUTING LAYER │ │ │ + * │ │ │ src/routes/apiRoutes.js │ │ │ + * │ │ │ src/routes/category.route.js │ │ │ + * │ │ └──────────────────────┬───────────────────────────────────┘ │ │ + * │ │ │ │ │ + * │ │ ▼ │ │ + * │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + * │ │ │ CONTROLLER LAYER (28 controllers) │ │ │ + * │ │ │ Validation · Parameter parsing · Response formatting │ │ │ + * │ │ └──────────────────────┬───────────────────────────────────┘ │ │ + * │ │ │ │ │ + * │ │ ▼ │ │ + * │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + * │ │ │ EXTRACTOR / SCRAPER LAYER (28 extractors) │ │ │ + * │ │ │ HTML fetching · Cheerio parsing · Data transformation │ │ │ + * │ │ └──────────────────────┬───────────────────────────────────┘ │ │ + * │ │ │ │ │ + * │ │ ▼ │ │ + * │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + * │ │ │ HELPER LAYER │ │ │ + * │ │ │ cache.helper.js — LRU cache with per-endpoint TTL │ │ │ + * │ │ │ mirror.helper.js — Multi-mirror failover (5 domains) │ │ │ + * │ │ │ pagination.helper.js — Pagination builder │ │ │ + * │ │ └──────────────────────┬───────────────────────────────────┘ │ │ + * │ │ │ │ │ + * │ │ ▼ │ │ + * │ │ ┌──────────────────────────────────────────────────────────┐ │ │ + * │ │ │ CONFIG LAYER │ │ │ + * │ │ │ header.config.js — Default HTTP headers │ │ │ + * │ │ │ ids.config.js — Genre / Type / Status ID mappings │ │ │ + * │ │ └──────────────────────────────────────────────────────────┘ │ │ + * │ └───────────────────────────────────────────────────────────────┘ │ + * └─────────────────────────────────────────────────────────────────────┘ + * │ + * ▼ HTTP / HTTPS + * ┌─────────────────────────────────────────────────────────────────────┐ + * │ MIRROR POOL (5 domains, auto-failover) │ + * │ │ + * │ 1. anikototv.to (Primary) │ + * │ 2. anikoto.cz (Regional CZ) │ + * │ 3. anikoto.me (Short TLD) │ + * │ 4. anikoto.net (Network) │ + * │ 5. anikototv.se (Nordic .se) │ + * └─────────────────────────────────────────────────────────────────────┘ + */ -## Tech Stack +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 2 — REQUEST FLOW +// ═══════════════════════════════════════════════════════════════════════════ -| Component | Technology | -|-----------|------------| -| Runtime | Node.js | -| Framework | Express.js | -| Scraping | Cheerio + Axios | -| Deployment | Vercel (Serverless) | -| Caching | LRU cache with configurable TTL | -| Compression | gzip (level 6, 1024 byte threshold) | -| Mirror Fallback | 5 mirror domains with health checks | -| Middleware | Creator info attribution injection | -| Static Files | Express static middleware | +/** + * Every inbound request passes through the following pipeline: + * + * Client + * │ + * ▼ + * ┌──────────────────────────────┐ + * │ 1. TLS Termination (Vercel)│ + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 2. CORS Middleware │ ◄── Allow / Block origins + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 3. Compression Middleware │ ◄── gzip (threshold: 1KB) + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 4. Rate Limiter │ ◄── 100 req / 15 min / IP + * │ (429 on exceed) │ + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 5. Route Matching │ ◄── apiRoutes.js / category.route.js + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 6. Controller │ ◄── Validate params, call extractor + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 7. Cache Lookup │ ◄── LRU hit? → Return cached data + * │ (if miss) │ + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 8. Mirror Selection │ ◄── mirror.helper.js (round-robin + fallback) + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 9. HTTP Fetch (Axios) │ ◄── Request source HTML from mirror + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 10. HTML Parse (Cheerio) │ ◄── Extractor scrapes structured data + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 11. Cache Store │ ◄── Write to LRU with endpoint TTL + * └──────────────┬───────────────┘ + * ▼ + * ┌──────────────────────────────┐ + * │ 12. JSON Response │ ◄── Express res.json() + * └──────────────────────────────┘ + */ -## Request Flow +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 3 — DIRECTORY LAYOUT +// ═══════════════════════════════════════════════════════════════════════════ -``` -Client Request - ↓ -Vercel Routes (/api/* → server.js) - ↓ -Express Middleware - ↓ -├── CORS -├── Security Headers -├── Compression (gzip) -├── Rate Limiting -├── Creator Info Injection - ↓ -Express Router (apiRoutes.js) - ↓ -Controller (e.g., search.controller.js) - ↓ -Extractor (e.g., search.extractor.js) - ↓ -HTTP Request to anikototv.to (with headers) - ↓ -Cheerio parses HTML response - ↓ -Returns structured JSON with Creator Attribution - ↓ -Client Response -``` +/** + * AniKotoAPI/ + * ├── server.js # Express entry point, middleware chain + * ├── package.json # Dependencies & npm scripts + * ├── api/ + * │ └── index.js # Vercel serverless function handler + * ├── src/ + * │ ├── configs/ + * │ │ ├── header.config.js # Default HTTP headers for upstream requests + * │ │ └── ids.config.js # Genre / Type / Status numeric ID mappings + * │ ├── controllers/ # 28 route handlers (one per endpoint) + * │ │ ├── homeInfo.controller.js + * │ │ ├── animeInfo.controller.js + * │ │ ├── search.controller.js + * │ │ ├── episodeList.controller.js + * │ │ ├── episodeListAjax.controller.js + * │ │ ├── streamInfo.controller.js + * │ │ ├── streamResolver.controller.js + * │ │ ├── schedule.controller.js + * │ │ ├── spotlight.controller.js + * │ │ ├── trending.controller.js + * │ │ ├── topten.controller.js + * │ │ ├── suggestion.controller.js + * │ │ ├── random.controller.js + * │ │ ├── popular.controller.js + * │ │ ├── filter.controller.js + * │ │ ├── watchPage.controller.js + * │ │ ├── azList.controller.js + * │ │ ├── newRelease.controller.js + * │ │ ├── status.controller.js + * │ │ ├── trendingSidebar.controller.js + * │ │ ├── seasons.controller.js + * │ │ ├── watchOrder.controller.js + * │ │ ├── download.controller.js + * │ │ ├── upcomingAnime.controller.js + * │ │ ├── topAnimeRankings.controller.js + * │ │ ├── recentlyUpdatedTabs.controller.js + * │ │ ├── completedAnime.controller.js + * │ │ └── category.controller.js + * │ ├── extractors/ # 28 HTML scrapers (one per data source) + * │ │ ├── homeInfo.extractor.js + * │ │ ├── animeInfo.extractor.js + * │ │ ├── search.extractor.js + * │ │ ├── episodeList.extractor.js + * │ │ ├── episodeListAjax.extractor.js + * │ │ ├── streamInfo.extractor.js + * │ │ ├── streamResolver.extractor.js + * │ │ ├── schedule.extractor.js + * │ │ ├── spotlight.extractor.js + * │ │ ├── trending.extractor.js + * │ │ ├── topten.extractor.js + * │ │ ├── suggestion.extractor.js + * │ │ ├── random.extractor.js + * │ │ ├── popular.extractor.js + * │ │ ├── filter.extractor.js + * │ │ ├── watchPage.extractor.js + * │ │ ├── azList.extractor.js + * │ │ ├── newRelease.extractor.js + * │ │ ├── status.extractor.js + * │ │ ├── trendingSidebar.extractor.js + * │ │ ├── seasons.extractor.js + * │ │ ├── watchOrder.extractor.js + * │ │ ├── download.extractor.js + * │ │ ├── upcomingAnime.extractor.js + * │ │ ├── topAnimeRankings.extractor.js + * │ │ ├── recentlyUpdatedTabs.extractor.js + * │ │ ├── completedAnime.extractor.js + * │ │ └── parseListItem.helper.js + * │ ├── helper/ + * │ │ ├── cache.helper.js # LRU cache with per-endpoint TTL + * │ │ ├── mirror.helper.js # Multi-mirror failover (5 domains) + * │ │ ├── pagination.helper.js # Pagination builder + * │ │ └── parseListItem.helper.js + * │ └── routes/ + * │ ├── apiRoutes.js # All route definitions + OpenAPI spec + * │ └── category.route.js # Genre / Type / Status sub-routes + * ├── docs/ # Documentation (you are here) + * │ ├── index.md + * │ ├── endpoints.md + * │ ├── streaming.md + * │ ├── examples.md + * │ ├── architecture.md + * │ └── testing.md + * ├── public/ # Static files served by Vercel + * │ ├── index.html + * │ ├── tos.html + * │ ├── privacy.html + * │ ├── manifest.json + * │ ├── robots.txt + * │ └── sitemap.xml + * ├── agents/ # AI agent role configurations + * │ ├── api-tester.md + * │ ├── backend-architect.md + * │ ├── devops-automator.md + * │ ├── performance-benchmarker.md + * │ ├── security-architect.md + * │ └── technical-writer.md + * └── test.js # Integration test suite (35 tests) + */ -## Streaming Flow +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 4 — COMPONENT RESPONSIBILITIES +// ═══════════════════════════════════════════════════════════════════════════ -``` -/api/episodes/:animeId - ↓ - Returns: server_ids, animeId, totalEpisodes - ↓ -/api/servers?ids={server_ids} - ↓ - Returns: link_id, type (sub/dub), name (HD-1, Vidstream-2, etc.) - ↓ -/api/stream?id={link_id} - ↓ - Returns: url (embed URL), skipData (intro/outro timestamps) - ↓ -/api/stream/resolve?id={link_id} ← 🆕 Resolve to actual video URL - ↓ - Returns: url (m3u8/mp4), type, server name, subtitles - ↓ -/api/stream/qualities?url={m3u8_url} ← 🆕 Get quality options - ↓ - Returns: qualities array with resolution, bandwidth, codec - ↓ -/api/stream/proxy?url={m3u8_url} ← 🆕 CORS-free proxy - ↓ - Returns: rewritten M3U8 with proxy URLs -``` +/** + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ server.js │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ • Creates the Express application instance (ESM) │ + * │ • Registers global middleware: CORS, compression, JSON parser │ + * │ • Applies rate limiting middleware (100 req / 15 min / IP) │ + * │ • Mounts route modules at /api │ + * │ • Exports the app for both local dev and Vercel serverless │ + * │ • Handles 404 fallback for unmatched routes │ + * └────────────────────────────────────────────────────────────────────────┘ + * + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ api/index.js (Vercel Serverless Handler) │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ • Wraps the Express app into a Vercel-compatible serverless fn │ + * │ • Receives the raw Vercel event + context │ + * │ • Delegates to Express for request handling │ + * │ • Manages cold-start optimization │ + * └────────────────────────────────────────────────────────────────────────┘ + * + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ Controllers (28 handlers) │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ One controller per API endpoint. Responsibilities: │ + * │ • Extract and validate request parameters (query, params, body) │ + * │ • Invoke the corresponding extractor function │ + * │ • Wrap result in a standard JSON envelope │ + * │ • Catch errors and return structured error responses │ + * │ • No direct HTTP fetching — all scraping lives in extractors │ + * └────────────────────────────────────────────────────────────────────────┘ + * + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ Extractors (28 scrapers) │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ One extractor per data source on the target site. Responsibilities: │ + * │ • Build the upstream URL (via mirror.helper) │ + * │ • Fetch raw HTML using Axios with configured headers │ + * │ • Parse HTML with Cheerio and extract structured data │ + * │ • Return normalized JavaScript objects ready for JSON serialization │ + * │ • No Express coupling — pure data extraction │ + * └────────────────────────────────────────────────────────────────────────┘ + * + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ Helpers │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ cache.helper.js │ + * │ • Wraps an LRU Cache (Map-based, no external deps) │ + * │ • Provides get/set/delete/clear with per-endpoint TTL │ + * │ • TTLs range from 3 min (trending) to 60 min (genres) │ + * │ • Cache key = full request URL for deterministic lookups │ + * │ │ + * │ mirror.helper.js │ + * │ • Maintains ordered list of 5 mirror domains │ + * │ • Round-robin primary selection │ + * │ • Automatic failover on HTTP error or timeout │ + * │ • Caches successful mirror for 1 hour per domain │ + * │ │ + * │ pagination.helper.js │ + * │ • Accepts current page, total pages, per-page count │ + * │ • Returns { page, totalPages, perPage, hasNext, hasPrev } │ + * │ │ + * │ parseListItem.helper.js │ + * │ • Shared logic for parsing repeated list-item DOM structures │ + * │ • Used by multiple extractors (trending, popular, search, etc.) │ + * └────────────────────────────────────────────────────────────────────────┘ + * + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ Configs │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ header.config.js │ + * │ • Default User-Agent, Accept, Accept-Language headers │ + * │ • Ensures upstream site returns full HTML (not bot-blocked) │ + * │ │ + * │ ids.config.js │ + * │ • Numeric ID mappings for genres (e.g., action=1, comedy=2) │ + * │ • Type IDs (e.g., TV=1, Movie=2, OVA=3) │ + * │ • Status IDs (e.g., Airing=1, Completed=2, Upcoming=3) │ + * │ • Used by filter and category controllers │ + * └────────────────────────────────────────────────────────────────────────┘ + * + * ┌────────────────────────────────────────────────────────────────────────┐ + * │ Routes │ + * ├────────────────────────────────────────────────────────────────────────┤ + * │ apiRoutes.js │ + * │ • Defines all 28 endpoint paths with HTTP methods │ + * │ • Maps paths to controller functions │ + * │ • Embeds OpenAPI 3.0 spec for auto-documentation │ + * │ │ + * │ category.route.js │ + * │ • Sub-router for /api/genre, /api/type, /api/status │ + * │ • Groups related endpoints under a shared prefix │ + * └────────────────────────────────────────────────────────────────────────┘ + */ -## Caching Strategy +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 5 — DATA FLOW +// ═══════════════════════════════════════════════════════════════════════════ -- **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 -- **Eviction:** Automatic when TTL expires +/** + * The data flow is strictly unidirectional: + * + * External Site (anikototv.to) + * │ + * │ Raw HTML + * ▼ + * ┌──────────────┐ + * │ AxIOS │ HTTP GET with browser-like headers + * └──────┬───────┘ + * │ + * ▼ + * ┌──────────────┐ + * │ Cheerio │ DOM traversal & data extraction + * └──────┬───────┘ + * │ + * │ Structured JS objects + * ▼ + * ┌──────────────┐ + * │ Extractor │ Normalizes data shapes across endpoints + * └──────┬───────┘ + * │ + * ▼ + * ┌──────────────┐ + * │ Controller │ Wraps in { success, data } envelope + * └──────┬───────┘ + * │ + * ▼ + * ┌──────────────┐ + * │ LRU Cache │ Stores for endpoint-specific TTL + * └──────┬───────┘ + * │ + * │ JSON + * ▼ + * ┌──────────────┐ + * │ Express │ GZIP compression + CORS headers + * └──────┬───────┘ + * │ + * ▼ + * ┌──────────────┐ + * │ Vercel CDN │ Edge caching + TLS + * └──────┬───────┘ + * │ + * ▼ + * Client + * + * Key data transformation points: + * + * 1. Raw HTML → Cheerio DOM (Axios + Cheerio.load) + * 2. Cheerio DOM → Plain objects (extractor logic) + * 3. Plain objects → JSON envelope (controller) + * 4. JSON string → GZIP bytes (compression middleware) + * 5. GZIP bytes → HTTP response (Vercel edge) + */ -## Anti-Bot Protection +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 6 — MIRROR FAILOVER STRATEGY +// ═══════════════════════════════════════════════════════════════════════════ -The source site monitors AJAX responses for missing `data-ep-id` and `data-link-id` attributes. If these are missing in the `/ajax/server/list` response, it triggers a reCAPTCHA challenge. +/** + * The API scrapes from a network of 5 mirror domains, all serving the same + * content. The mirror.helper.js module manages automatic failover: + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ MIRROR DOMAINS │ + * ├──────┬──────────────────┬──────────────┬────────────────────────────┤ + * │ # │ Domain │ Role │ Notes │ + * ├──────┼──────────────────┼──────────────┼────────────────────────────┤ + * │ 1 │ anikototv.to │ Primary │ Default fallback │ + * │ 2 │ anikoto.cz │ Regional │ Central Europe │ + * │ 3 │ anikoto.me │ Short TLD │ Compact URL │ + * │ 4 │ anikoto.net │ Network │ Alternative DNS │ + * │ 5 │ anikototv.se │ Nordic │ Sweden / Nordic region │ + * └──────┴──────────────────┴──────────────┴────────────────────────────┘ + * + * Failover Algorithm: + * + * 1. On request, select the next mirror (round-robin pointer). + * 2. Attempt HTTP GET via Axios with 5-second timeout. + * 3. On success (2xx): + * • Cache the successful mirror domain (TTL: 1 hour). + * • Return HTML to the caller. + * 4. On failure (4xx/5xx/timeout/network error): + * • Log the failure. + * • Move to the next mirror in the list. + * • Repeat steps 2-4 up to 5 attempts (one per mirror). + * 5. If all mirrors fail: + * • Throw an error propagated to the controller. + * • Controller returns { success: false, error: "..." }. + * + * Mirror Health Cache: + * • Key: domain string + * • Value: timestamp of last successful fetch + * • TTL: 1 hour + * • Effect: Recently successful mirrors are tried first. + * + * Edge Cases: + * • Circular failover prevents infinite loops (max 5 attempts). + * • Timeout per mirror: 5000ms (configurable). + * • All mirrors share the same origin content; no consistency risk. + */ -**Solution:** The API properly passes `data-ids` to the server list endpoint and parses the JSON response to extract the required attributes. +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 7 — CACHING STRATEGY +// ═══════════════════════════════════════════════════════════════════════════ -## Vercel Configuration +/** + * An in-memory LRU (Least Recently Used) cache sits between controllers + * and extractors, eliminating redundant upstream requests. + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ CACHE TTL MATRIX │ + * ├──────────────────────────────┬───────────────────────────────────────┤ + * │ Endpoint Category │ TTL │ + * ├──────────────────────────────┼───────────────────────────────────────┤ + * │ Trending / Spotlight │ 3 minutes │ + * │ Anime Info / Search │ 5–10 minutes │ + * │ Episodes / Stream Info │ 5 minutes │ + * │ Genres / Types / Status │ 60 minutes │ + * │ Mirror health │ 60 minutes │ + * │ Home Page │ 5 minutes │ + * │ Schedule │ 10 minutes │ + * │ Watch Order │ 60 minutes │ + * └──────────────────────────────┴───────────────────────────────────────┘ + * + * Cache Key Format: + * `{endpoint}:{normalized_params}` + * + * Examples: + * "animeInfo:one-piece" + * "search:naruto" + * "episodeList:one-piece-100" + * "filter:action:page-2" + * + * LRU Configuration: + * • Max entries: Unbounded (Map-based, no eviction by count). + * • Eviction: TTL-based only (per-endpoint expiration). + * • Storage: JavaScript Map (in-process memory). + * • Persistence: None (lost on cold start; acceptable for scraping). + * + * Cache Flow: + * + * Controller + * │ + * ▼ + * cache.get(key) + * │ + * ├── HIT → return cached data (no extractor call) + * │ + * └── MISS → call extractor + * │ + * ▼ + * cache.set(key, data, ttl) + * │ + * ▼ + * return data + * + * Thread Safety: + * Node.js is single-threaded per event loop tick. The LRU Cache Map + * operations (get/set/delete) are atomic. No locking required. + * + * Memory Impact: + * Average cached entry: ~2 KB JSON. + * Typical active entries: ~200–500. + * Peak memory: ~1 MB (negligible). + */ -```json -{ - "version": 2, - "builds": [ - { - "src": "server.js", - "use": "@vercel/node" - } - ], - "routes": [ - { - "src": "/api/(.*)", - "dest": "server.js" - }, - { - "src": "/(.*)", - "dest": "server.js" - } - ] -} -``` +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 8 — RATE LIMITING & COMPRESSION +// ═══════════════════════════════════════════════════════════════════════════ -- `/api/*` routes to Express for API handling -- `/*` routes to Express for static file serving -- Static files served from `process.cwd()` (Vercel serverless compatible) +/** + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ RATE LIMITING │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ • Algorithm: Sliding window counter (per IP) │ + * │ • Limit: 100 requests per 15-minute window │ + * │ • Storage: In-memory (Map keyed by IP address) │ + * │ • Response: HTTP 429 Too Many Requests │ + * │ • Headers: X-RateLimit-Limit, X-RateLimit-Remaining, │ + * │ X-RateLimit-Reset │ + * │ │ + * │ Notes: │ + * │ • Vercel serverless instances are ephemeral; the rate limit map │ + * │ resets on cold start. This is acceptable for a public scraping │ + * │ API — the limit deters abuse but does not need to be absolute. │ + * │ • IP is extracted from req.ip (trusts Vercel's X-Forwarded-For). │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ GZIP COMPRESSION │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ • Library: compression 1.7 (Express middleware) │ + * │ • Algorithm: gzip (deflate fallback) │ + * │ • Threshold: 1 KB (responses smaller than this are sent raw) │ + * │ • Level: Default (6) — balanced speed / ratio │ + * │ │ + * │ Typical Compression Ratios: │ + * │ • JSON responses: 70–85% reduction │ + * │ • Large list endpoints: 60–75% reduction │ + * │ • Streaming data: 50–65% reduction │ + * │ │ + * │ Bypass: │ + * │ • Clients can send Accept-Encoding: identity to opt out │ + * │ • Vercel CDN may serve pre-compressed responses at edge │ + * └──────────────────────────────────────────────────────────────────────┘ + */ -## Environment Variables +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 9 — ERROR HANDLING STRATEGY +// ═══════════════════════════════════════════════════════════════════════════ -| Variable | Description | -|----------|-------------| -| `ANIKOTO_CHECK_SERVER_TS` | Anti-bot check timestamp from source site | +/** + * Error responses follow a consistent envelope format: + * + * { + * "success": false, + * "error": { + * "code": 404, + * "message": "Anime not found" + * } + * } + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ ERROR CATEGORIES │ + * ├─────────────────────────────┬────────────────────────────────────────┤ + * │ Category │ Handling │ + * ├─────────────────────────────┼────────────────────────────────────────┤ + * │ 400 Bad Request │ Missing/invalid query params │ + * │ 404 Not Found │ Unknown route or missing resource │ + * │ 429 Too Many Requests │ Rate limit exceeded │ + * │ 500 Internal Server Error │ Unhandled exceptions │ + * │ 502 Bad Gateway │ All mirrors failed upstream fetch │ + * │ 503 Service Unavailable │ Upstream site unreachable │ + * └─────────────────────────────┴────────────────────────────────────────┘ + * + * Error Propagation Chain: + * + * Source Site Error / Timeout + * │ + * ▼ + * Axios throws (ECONNREFUSED, ETIMEDOUT, 4xx/5xx status) + * │ + * ▼ + * Extractor catches and re-throws with context: + * { message: "...", originalError: err } + * │ + * ▼ + * Controller catches and formats JSON response: + * res.status(502).json({ success: false, error: { code, message } }) + * + * Global Error Handler: + * • An Express error-handling middleware (4 params) catches any + * unhandled exceptions that escape controller-level try/catch. + * • Returns 500 with a generic error message (no stack trace in prod). + * + * Timeout Handling: + * • Axios request timeout: 10,000ms (10 seconds) + * • Mirror failover timeout: 5,000ms per mirror + * • Total worst-case latency: 5 mirrors × 5s = 25s (exceeds Vercel limit) + * • Mitigation: First successful mirror is cached; subsequent requests + * hit the cached mirror first, reducing average latency to <1s. + * + * Source Site Changes: + * • If anikototv.to changes its HTML structure, extractors will return + * empty or malformed data. + * • Controllers detect missing required fields and return appropriate + * error messages rather than empty objects. + * • Monitoring via test.js integration suite (35 tests) catches + * structural regressions. + */ -## Response Format +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 10 — SECURITY CONSIDERATIONS +// ═══════════════════════════════════════════════════════════════════════════ -All API responses follow this structure: +/** + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ CORS POLICY │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ • Configured via the 'cors' Express middleware. │ + * │ • Default: Allow all origins (*) for public API usage. │ + * │ • Restrictable via environment variable CORS_ORIGIN. │ + * │ • Credentials: Not required (stateless API). │ + * │ • Methods: GET only. │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ INPUT VALIDATION │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ • Query parameters are sanitized before use in URL construction. │ + * │ • Path parameters are URL-encoded where necessary. │ + * │ • No user-supplied data is interpolated into HTML templates. │ + * │ • Search queries are passed as-is to upstream (no injection). │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ SECRETS MANAGEMENT │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ • No API keys, tokens, or secrets in the codebase. │ + * │ • Vercel environment variables (if any) are not committed. │ + * │ • .gitignore excludes node_modules, .env, .vercel. │ + * │ • All upstream requests use public, non-authenticated endpoints. │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ DEPENDENCY SECURITY │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ • npm audit runs in CI pipeline. │ + * │ • Dependencies pinned to exact versions in package.json. │ + * │ • Minimal dependency footprint (6 runtime deps). │ + * │ • No file system writes by the application (Vercel constraint). │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ RATE LIMITING AS SECURITY │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ • 100 req/15min per IP prevents scraping of the scraper. │ + * │ • Degrades gracefully — returns 429 with Retry-After header. │ + * │ • In-memory only — no persistence layer to protect. │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ TRANSPORT SECURITY │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ • All client ↔ API communication over HTTPS (Vercel default). │ + * │ • API ↔ upstream mirrors also uses HTTPS. │ + * │ • HTTP requests to mirrors are auto-upgraded to HTTPS by Axios. │ + * └──────────────────────────────────────────────────────────────────────┘ + */ -```json -{ - "success": true, - "results": { ... } -} -``` +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 11 — DEPLOYMENT (VERCEL SERVERLESS) +// ═══════════════════════════════════════════════════════════════════════════ -Error responses: +/** + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ VERCEL ARCHITECTURE │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ ┌────────────┐ ┌──────────────────┐ ┌────────────────┐ │ + * │ │ Global CDN │────▶│ Edge Network │────▶│ Lambda (Node) │ │ + * │ │ (300+ PoP) │ │ TLS + Routing │ │ Serverless Fn │ │ + * │ └────────────┘ └──────────────────┘ └────────┬───────┘ │ + * │ │ │ + * │ ┌─────────▼────────┐ │ + * │ │ /tmp (writable) │ │ + * │ │ 512 MB limit │ │ + * │ └──────────────────┘ │ + * │ │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * Serverless Function Configuration: + * • Entry point: api/index.js + * • Runtime: Node.js 20.x (ESM) + * • Memory: 1024 MB (default) + * • Max duration: 10 seconds (Hobby), 60 seconds (Pro) + * • Max payload: 4.5 MB request, 5 MB response + * + * Filesystem Constraints: + * • READ-ONLY: /var/task, /var/runtime, project root + * • WRITABLE: /tmp only (ephemeral, 512 MB, not shared across instances) + * • The API never writes to disk; this constraint is irrelevant here. + * + * Cold Start Behavior: + * • First request after idle: ~300–800ms cold start overhead. + * • Subsequent requests (warm): <50ms overhead. + * • LRU cache is lost on cold start (in-memory only). + * • Cache repopulates naturally on first request per endpoint. + * + * Deployment Pipeline: + * + * 1. Push to GitHub (main branch) + * 2. Vercel auto-detects Node.js project + * 3. npm install --production + * 4. Vercel builds the serverless function + * 5. Deploys to preview URL (PR) or production URL (main) + * 6. Health check: GET /api/status → { success: true } + * + * Environment Variables: + * • None required (API is fully self-contained). + * • Optional: CORS_ORIGIN (restrict CORS in production). + * + * Monitoring: + * • Vercel Analytics (built-in) + * • Vercel Logs (real-time streaming) + * • test.js integration suite (manual or CI-triggered) + * + * Rollback: + * • Vercel retains previous deployments. + * • One-click rollback via Vercel dashboard or CLI. + */ -```json -{ - "success": false, - "message": "Error description" -} -``` +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 12 — TESTING STRATEGY +// ═══════════════════════════════════════════════════════════════════════════ -## Dependencies +/** + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ INTEGRATION TESTS (test.js) │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ • 35 end-to-end tests covering all 28+ endpoints. │ + * │ • Each test hits the live API and validates: │ + * │ - HTTP status code (200, or expected error code) │ + * │ - Response shape (success: true, data present) │ + * │ - Data integrity (required fields exist, types correct) │ + * │ - No empty arrays where content is expected │ + * │ │ + * │ Run: node test.js │ + * │ │ + * │ Test Categories: │ + * │ • Home page data completeness │ + * │ • Anime info by slug (valid + invalid) │ + * │ • Search with various queries (empty, exact, partial) │ + * │ • Episode list and AJAX loading │ + * │ • Stream info and resolver │ + * │ • Schedule, spotlight, trending │ + * │ • Top 10, suggestions, random │ + * │ • Popular, filter, A-Z list │ + * │ • New release, status, trending sidebar │ + * │ • Seasons, watch order, download │ + * │ • Upcoming, rankings, recently updated │ + * │ • Completed anime, category (genre/type/status) │ + * │ • Error cases (404, invalid params) │ + * │ │ + * │ Exit Codes: │ + * │ 0 — All tests passed │ + * │ 1 — One or more tests failed │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ AGENT CONFIGURATIONS (agents/) │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ api-tester.md — Automated endpoint testing persona │ + * │ backend-architect.md — Architecture review persona │ + * │ devops-automator.md — CI/CD and deployment persona │ + * │ performance-benchmarker.md — Load testing and profiling persona │ + * │ security-architect.md — Security audit persona │ + * │ technical-writer.md — Documentation persona │ + * │ │ + * │ These files define specialized AI agent roles for code review, │ + * │ testing, and documentation tasks. │ + * └──────────────────────────────────────────────────────────────────────┘ + */ -| Package | Version | Purpose | -|---------|---------|---------| -| express | ^4.21.0 | Web framework | -| cheerio | ^1.0.0-rc.12 | HTML parsing | -| axios | ^1.8.0 | HTTP requests | -| dotenv | ^16.4.0 | Environment variables | -| compression | ^1.7.4 | Response compression | +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION 13 — VERSIONING & CHANGELOG NOTES +// ═══════════════════════════════════════════════════════════════════════════ + +/** + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ VERSION HISTORY │ + * ├──────────────────────────────────────────────────────────────────────┤ + * │ │ + * │ v2.2.0 (Current) │ + * │ • 28 endpoints, 28 extractors │ + * │ • LRU cache with per-endpoint TTL │ + * │ • 5-mirror failover system │ + * │ • Vercel serverless deployment │ + * │ • 35 integration tests │ + * │ • OpenAPI 3.0 spec embedded in apiRoutes.js │ + * │ │ + * └──────────────────────────────────────────────────────────────────────┘ + * + * Semantic Versioning: + * MAJOR — Breaking changes to API response shapes or endpoint removal + * MINOR — New endpoints, new data fields (backward-compatible) + * PATCH — Bug fixes, extractor updates, dependency bumps + */ + +// ═══════════════════════════════════════════════════════════════════════════ +// SECTION — APPENDIX: QUICK REFERENCE +// ═══════════════════════════════════════════════════════════════════════════ + +/** + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ ENDPOINT COUNT BY CATEGORY │ + * ├─────────────────────────────┬────────────────────────────────────────┤ + * │ Discovery (browse) │ home, trending, popular, spotlight, │ + * │ │ topten, suggestion, random, schedule │ + * │ Search & Filter │ search, filter, azList, category │ + * │ Anime Detail │ animeInfo, watchPage, status │ + * │ Episode & Stream │ episodeList, episodeListAjax, │ + * │ │ streamInfo, streamResolver, download │ + * │ Lists │ newRelease, trendingSidebar, │ + * │ │ topAnimeRankings, recentlyUpdated, │ + * │ │ completedAnime, upcomingAnime │ + * │ Meta │ seasons, watchOrder │ + * ├─────────────────────────────┼────────────────────────────────────────┤ + * │ TOTAL │ 28 endpoints │ + * └─────────────────────────────┴────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ DEPENDENCY SUMMARY (6 runtime packages) │ + * ├─────────────────────────────┬────────────────────────────────────────┤ + * │ express 4.21 │ Web framework │ + * │ cheerio 1.0 │ Server-side HTML parser │ + * │ axios 1.8 │ HTTP client │ + * │ compression 1.7 │ GZIP middleware │ + * │ lru-cache (built-in) │ In-memory cache (Map-based) │ + * │ cors (built-in) │ CORS middleware │ + * └─────────────────────────────┴────────────────────────────────────────┘ + * + * ┌──────────────────────────────────────────────────────────────────────┐ + * │ PERFORMANCE CHARACTERISTICS │ + * ├─────────────────────────────┬────────────────────────────────────────┤ + * │ Cold start (first req) │ ~300–800ms │ + * │ Warm request (cache hit) │ <10ms │ + * │ Warm request (cache miss) │ 500ms–2s (upstream fetch + parse) │ + * │ Worst case (all mirrors) │ ~25s (5 × 5s timeout) │ + * │ Response size (compressed) │ 2–50 KB depending on endpoint │ + * │ Memory per instance │ ~5–15 MB (Node + cache) │ + * └─────────────────────────────┴────────────────────────────────────────┘ + */ + +// ══════ END: architecture.md ══════ diff --git a/docs/endpoints.md b/docs/endpoints.md index 254245e..8176a4b 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -6,35 +6,42 @@ Base URL: `https://anikototvapi.vercel.app/api` ## Pagination -All list endpoints include pagination metadata in the response: +Most list endpoints return a `totalPages` field for pagination: ```json { "success": true, "results": { - "data": [...], + "totalPages": 296, + "data": [...] + } +} +``` + +Use the `page` query parameter to navigate: `?page=2` + +**Only `/api/filter` returns the full pagination object:** + +```json +{ + "results": { + "totalPages": 1, + "data": [], "pagination": { "currentPage": 1, - "totalPages": 294, - "totalItems": 8820, + "totalPages": 1, + "totalItems": 0, "itemsPerPage": 30, - "hasNext": true, + "hasNext": false, "hasPrev": false } } } ``` -| Field | Type | Description | -|:---|:---|:---| -| `currentPage` | `number` | Current page number (1-indexed) | -| `totalPages` | `number` | Total number of pages available | -| `totalItems` | `number` | Estimated total items across all pages | -| `itemsPerPage` | `number` | Number of items per page (default: 30) | -| `hasNext` | `boolean` | Whether there is a next page | -| `hasPrev` | `boolean` | Whether there is a previous page | +**Endpoints with `totalPages` pagination:** `/api/search`, `/api/most-popular`, `/api/new-release`, `/api/newly-added`, `/api/latest-updated`, `/api/az-list/:letter`, `/api/filter`, `/api/genre/:name`, `/api/type/:name`, `/api/status/:name` -**Endpoints with pagination:** `/api/search`, `/api/most-popular`, `/api/new-release`, `/api/newly-added`, `/api/latest-updated`, `/api/az-list/:letter`, `/api/filter`, `/api/genre/:name`, `/api/type/:name`, `/api/status/:name` +**Endpoints with flat arrays (no pagination):** `/api/spotlight`, `/api/trending`, `/api/top-ten`, `/api/suggestions`, `/api/recently-updated`, `/api/completed`, `/api/upcoming`, `/api/top-rankings`, `/api/mirrors`, `/api/schedule` --- @@ -136,12 +143,14 @@ Get detailed info about an anime. | Param | Type | Required | Description | |-------|------|----------|-------------| -| `id` | string | Yes | Anime slug (e.g., `road-of-naruto-ggjw8`) | +| `id` | string | Yes | Anime slug with random hash (e.g., `naruto-shippuuden-movie-6-road-to-ninja-w2wqq`) | + +> **Important:** Slugs now include random hashes (e.g., `one-piece-odmau`). Get valid slugs from `/search` or `/most-popular` results. **Request:** ```bash -curl "https://anikototvapi.vercel.app/api/info?id=road-of-naruto-ggjw8" +curl "https://anikototvapi.vercel.app/api/info?id=naruto-shippuuden-movie-6-road-to-ninja-w2wqq" ``` **Response:** @@ -150,13 +159,26 @@ curl "https://anikototvapi.vercel.app/api/info?id=road-of-naruto-ggjw8" { "success": true, "results": { - "title": "Road of Naruto", - "type": "ONA", + "slug": "naruto-shippuuden-movie-6-road-to-ninja-w2wqq", + "animeId": 786, + "title": "Naruto: Shippuuden Movie 6: Road to Ninja", + "japaneseTitle": "Naruto: Shippuuden Movie 6 - Road to Ninja", + "altNames": "Naruto: Shippuuden Movie 6 - Road to Ninja;Naruto Movie 9", + "poster": "https://cdn.anipixcdn.co/thumbnail/43dd49b4fdb9bede653e94468ff8df1e.jpg", + "backgroundImage": "'", + "synopsis": "Returning home to Konohagakure, the young ninja celebrate defeating a group of supposed Akatsuki...", + "type": "Movie", + "premiered": "Summer 2012", + "aired": "Jul 28, 2012", "status": "Finished Airing", - "totalEpisodes": null, - "synopsis": "In celebration of 20 years of Naruto, Studio Pierrot posted an anniversary PV on their YouTube channel...", - "malId": "53236", - "genres": ["Action", "Fantasy", "Shounen"] + "malScore": "7.84", + "duration": "1 hr 39 min", + "episodes": "1", + "studios": ["Pierrot"], + "producers": ["TV Tokyo", "Shueisha"], + "genres": ["Action", "Comedy", "Supernatural"], + "rating": "PG-13", + "reviewCount": "0" } } ``` @@ -687,17 +709,36 @@ Returns paginated list of upcoming anime with sub/dub counts, type, and rating. ## GET /top-rankings -Get top-ranked anime (source site's top section). +Get top-ranked anime with sort options. | Param | Type | Required | Description | |-------|------|----------|-------------| +| `sort` | string | No | Sort mode: `day` (default), `week`, or `month` | | `page` | number | No | Page number (default: 1) | ```bash -curl "https://anikototvapi.vercel.app/api/top-rankings" +curl "https://anikototvapi.vercel.app/api/top-rankings?sort=week" ``` -Returns paginated list with rank numbers extracted from CSS classes. +**Response:** + +```json +{ + "success": true, + "results": [ + { + "rank": 1, + "slug": "demon-slayer-kimetsu-no-yaiba-infinity-castle-4tmun", + "poster": "https://cdn.anipixcdn.co/thumbnail/...", + "title": "Demon Slayer: Kimetsu no Yaiba Infinity Castle", + "japaneseTitle": "Kimetsu no Yaiba Movie: Mugen Jou-hen", + "sub": 1, + "dub": 1, + "type": "Movie" + } + ] +} +``` --- @@ -730,7 +771,9 @@ Get completed anime sorted by score. curl "https://anikototvapi.vercel.app/api/completed" ``` -Returns paginated list of finished anime from `section.top-table[data-name='completed']`. +Returns flat array of finished anime. + +> **Note:** The `type` field in completed results contains the **air date** (e.g., `"2016-05-14"`), not the anime type (TV/Movie/etc). --- @@ -796,3 +839,214 @@ curl "https://anikototvapi.vercel.app/api/stream/ts-proxy?url=https://example.co ``` Returns binary video segment with `Content-Type: video/mp2t` and `Access-Control-Allow-Origin: *`. + +--- + +## System Endpoints + +### GET /health + +Health check endpoint. No parameters required. + +```bash +curl "https://anikototvapi.vercel.app/api/health" +``` + +**Response:** + +```json +{ + "success": true, + "results": { + "status": "healthy", + "version": "2.2.0", + "uptime": "0h 1m 21s", + "uptimeSeconds": 81, + "timestamp": "2026-07-30T02:20:46.153Z", + "node": "v26.4.0", + "memory": { + "used": "99MB", + "total": "150MB" + } + } +} +``` + +--- + +### GET /stats + +API statistics endpoint. No parameters required. + +```bash +curl "https://anikototvapi.vercel.app/api/stats" +``` + +**Response:** + +```json +{ + "success": true, + "results": { + "uptime": "81s", + "requests": { + "total": 37, + "errors": 9, + "successRate": "75.7%" + }, + "cache": { + "type": "in-memory", + "ttl": "5 minutes", + "description": "Map-based cache with TTL expiration" + }, + "endpoints": 38, + "timestamp": "2026-07-30T02:20:46.205Z" + } +} +``` + +--- + +### GET /cache/stats + +Cache statistics endpoint. No parameters required. + +```bash +curl "https://anikototvapi.vercel.app/api/cache/stats" +``` + +**Response:** + +```json +{ + "success": true, + "results": { + "hits": 0, + "misses": 28, + "sets": 26, + "deletes": 0, + "size": 26, + "hitRate": "0.00%" + } +} +``` + +--- + +### GET /mirrors + +Mirror status endpoint. No parameters required. + +```bash +curl "https://anikototvapi.vercel.app/api/mirrors" +``` + +**Response:** + +```json +{ + "success": true, + "results": [ + { + "name": "Primary", + "url": "https://anikototv.to", + "priority": 1, + "status": "online", + "latency": "6115ms", + "isCurrent": true + }, + { + "name": "Regional CZ", + "url": "https://anikoto.cz", + "priority": 2, + "status": "online", + "latency": "672ms", + "isCurrent": false + } + ] +} +``` + +--- + +### POST /mirrors/reset + +Reset mirror cache. No parameters required. + +```bash +curl -X POST "https://anikototvapi.vercel.app/api/mirrors/reset" +``` + +**Response:** + +```json +{ + "success": true, + "message": "Mirror cache reset" +} +``` + +--- + +### GET /openapi + +OpenAPI 3.0.3 specification. No parameters required. + +```bash +curl "https://anikototvapi.vercel.app/api/openapi" +``` + +Returns the full OpenAPI spec document with all endpoint definitions, tags, and server information. + +--- + +## Response Patterns + +| Pattern | Format | Endpoints | +|:---|:---|:---| +| **Flat array** | `results: [...]` | `/spotlight`, `/trending`, `/top-ten`, `/suggestions`, `/recently-updated`, `/completed`, `/upcoming`, `/top-rankings`, `/mirrors`, `/schedule` | +| **Paginated data** | `results: { totalPages, data: [...] }` | `/search`, `/most-popular`, `/new-release`, `/newly-added`, `/latest-updated`, `/genre/:name`, `/type/:name`, `/status/:name`, `/az-list/:letter` | +| **Full pagination** | `results: { totalPages, data: [...], pagination: {...} }` | `/filter` only | +| **Three-period** | `results: { today/week/month: [...] }` or `{ day/week/month: [...] }` | `/top-ten`, `/trending-sidebar` | +| **Single object** | `results: {...}` | `/home`, `/info`, `/episodes`, `/episodes-ajax`, `/random`, `/health`, `/stats`, `/cache/stats`, `/stream-qualities` | + +--- + +## Common Workflows + +### Streaming Workflow + +``` +1. GET /episodes/{animeId} → Get episode list with server_ids +2. GET /servers?ids={ids} → Get available servers with link_ids +3. GET /stream?id={link_id} → Get embed URL + skip data +4. GET /stream/resolve?id={link_id} → Get actual m3u8/mp4 URL +5. GET /stream/qualities?url={m3u8} → Get available qualities +6. GET /stream/proxy?url={m3u8} → Get CORS-free proxied playlist +``` + +### Discovery Workflow + +``` +1. GET / → Homepage: spotlights, trending, genres +2. GET /search?q= → Search by keyword +3. GET /info?id= → Get anime details +4. GET /episodes/ → Get episode list +5. GET /seasons/ → Get all seasons +6. GET /watch-order/ → Get watch order for franchises +``` + +### Filter & Browse Workflow + +``` +1. GET /genre/{name} → Browse by genre +2. GET /type/{name} → Browse by type (TV, Movie, OVA) +3. GET /status/{name} → Browse by status (airing, completed) +4. GET /filter?... → Advanced multi-parameter filter +5. GET /az-list/{letter} → Browse alphabetically +6. GET /top-rankings?sort=day → Top ranked anime +``` + +--- + +// ══════ END: endpoints.md ══════ diff --git a/docs/examples.md b/docs/examples.md index 54471bc..db28b35 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,337 +1,1209 @@ # Code Examples -All examples are tested and working with the live API. +> **AniKotoAPI** — Complete code examples in cURL, JavaScript, Python, Node.js, and HTML/JS player integrations. +> All examples are tested and working with the live API at `https://anikototvapi.vercel.app/api`. --- -## cURL +// ═══════════════════════════════════════════════════════════════ +// ═══ SECTION 1: cURL Examples ═══ +// ═══════════════════════════════════════════════════════════════ + +## cURL Examples + +--- + +### Health Check + +```bash +curl -s "https://anikototvapi.vercel.app/api/health" | python3 -m json.tool +``` + +**Expected Output:** + +```json +{ + "success": true, + "results": { + "status": "healthy", + "version": "2.2.0", + "uptime": "0h 1m 21s", + "uptimeSeconds": 81, + "timestamp": "2026-07-30T02:20:46.153Z", + "node": "v26.4.0", + "memory": { "used": "99MB", "total": "150MB" } + } +} +``` + +> **Tip:** Use this to verify the API is online before making other requests. + +--- ### Homepage ```bash -curl "https://anikototvapi.vercel.app/api" +curl -s "https://anikototvapi.vercel.app/api" | python3 -m json.tool ``` -### Search +--- + +### Search for Anime ```bash -curl "https://anikototvapi.vercel.app/api/search?keyword=naruto" +# Search for "naruto" — returns Road of Naruto, Naruto: Shippuuden Movie 6, The Last: Naruto the Movie, etc. +curl -s "https://anikototvapi.vercel.app/api/search?keyword=naruto" | python3 -m json.tool ``` -### Anime Info +**Expected Output (partial):** + +```json +{ + "success": true, + "results": { + "totalPages": 1, + "data": [ + { + "slug": "road-of-naruto-ggjw8/ep-1", + "animeId": "7174", + "title": "Road of Naruto", + "japaneseTitle": "Road of Naruto", + "sub": 1, "dub": 0, "total": 0, + "type": "ONA", + "rating": "8.55", + "genres": ["Action", "Fantasy", "Shounen"] + }, + { + "slug": "naruto-shippuuden-movie-6-road-to-ninja-w2wqq/ep-1", + "animeId": "786", + "title": "Naruto: Shippuuden Movie 6: Road to Ninja", + "japaneseTitle": "Naruto: Shippuuden Movie 6 - Road to Ninja", + "sub": 1, "dub": 1, "total": 0, + "type": "Movie", + "rating": "7.84", + "genres": ["Action", "Comedy", "Supernatural"] + }, + { + "slug": "the-last-naruto-the-movie-whib1/ep-1", + "animeId": "1765", + "title": "The Last: Naruto the Movie", + "japaneseTitle": "The Last: Naruto the Movie", + "sub": 1, "dub": 1, "total": 0, + "type": "Movie", + "rating": "7.78", + "genres": ["Action", "Comedy", "Supernatural"] + } + ] + } +} +``` + +--- + +### Get Anime Info ```bash -curl "https://anikototvapi.vercel.app/api/info?id=naruto-eybxz" +# Get full info for "Naruto: Shippuuden Movie 6: Road to Ninja" +curl -s "https://anikototvapi.vercel.app/api/info?id=naruto-shippuuden-movie-6-road-to-ninja-w2wqq" | python3 -m json.tool ``` -### Episodes +**Expected Output:** + +```json +{ + "success": true, + "results": { + "slug": "naruto-shippuuden-movie-6-road-to-ninja-w2wqq", + "animeId": 786, + "title": "Naruto: Shippuuden Movie 6: Road to Ninja", + "japaneseTitle": "Naruto: Shippuuden Movie 6 - Road to Ninja", + "altNames": "Naruto: Shippuuden Movie 6 - Road to Ninja;Naruto Movie 9", + "poster": "https://cdn.anipixcdn.co/thumbnail/43dd49b4fdb9bede653e94468ff8df1e.jpg", + "synopsis": "Returning home to Konohagakure, the young ninja celebrate defeating a group of supposed Akatsuki...", + "type": "Movie", + "premiered": "Summer 2012", + "aired": "Jul 28, 2012", + "status": "Finished Airing", + "malScore": "7.84", + "duration": "1 hr 39 min", + "episodes": "1", + "studios": ["Pierrot"], + "producers": ["TV Tokyo", "Shueisha"], + "genres": ["Action", "Comedy", "Supernatural"], + "rating": "PG-13" + } +} +``` + +--- + +### Get Episodes ```bash -curl "https://anikototvapi.vercel.app/api/episodes/958" +# Get episode list for animeId 21 (22 episodes, first ep ID "302") +curl -s "https://anikototvapi.vercel.app/api/episodes/21" | python3 -m json.tool ``` -### Servers +**Expected Output (partial):** + +```json +{ + "success": true, + "results": { + "animeId": 21, + "slug": "21", + "totalEpisodes": 22, + "episodes": [ + { + "id": "302", + "episode_no": 1, + "slug": "1", + "title": "", + "active": true, + "href": "#", + "server_ids": "dXNCT3hNQzk3THhSTW8ySnM5...", + "timestamp": "1729197616", + "mal_id": "20" + } + ] + } +} +``` + +> **Tip:** Extract `server_ids` from the first episode to fetch available streaming servers. + +--- + +### Get Servers ```bash -SERVER_IDS=$(curl -s "https://anikototvapi.vercel.app/api/episodes/958" | python3 -c "import sys,json; print(json.load(sys.stdin)['results']['episodes'][0]['server_ids'])") -curl "https://anikototvapi.vercel.app/api/servers?ids=$SERVER_IDS" +# Step 1: Get server_ids from episodes +SERVER_IDS=$(curl -s "https://anikototvapi.vercel.app/api/episodes/21" \ + | python3 -c "import sys,json; print(json.load(sys.stdin)['results']['episodes'][0]['server_ids'])") + +# Step 2: Get servers using that server_ids +curl -s "https://anikototvapi.vercel.app/api/servers?ids=$SERVER_IDS" | python3 -m json.tool ``` -### Stream URL +**Expected Output:** + +```json +{ + "success": true, + "results": [ + { + "type": "sub", + "ep_id": "110289", + "link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOHZGS2FSWVgvbVJraVpIV1dQRjRoN01hOFUvYmxsWXFYNGtiR0h5OWdGQWc9PQ", + "cmid": "animixplay-fqs", + "sv_id": "1", + "name": "HD-1" + }, + { + "type": "sub", + "ep_id": "110289", + "link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOEZ4cFNpMDdQbnV1S3dNdklpRkhWbzRsVmgxSGx4YWx3LytPcnZXU0RCVHc9PQ", + "cmid": "animixplay-fqs", + "sv_id": "2", + "name": "Vidstream-2" + }, + { + "type": "sub", + "ep_id": "110289", + "link_id": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOGZ4cFNpMDdQbnV1S3dNdklpRkhWbzRsVmgxSGx4YWx3LytPcnZXU0RCVHc9PQ", + "cmid": "animixplay-fqs", + "sv_id": "3", + "name": "VidCloud-1" + } + ] +} +``` + +> **Note:** Servers are HD-1 (sub), Vidstream-2 (sub), VidCloud-1 (sub). Use `link_id` for streaming. + +--- + +### Get Stream URL ```bash -LINK_ID=$(curl -s "https://anikototvapi.vercel.app/api/episodes/958" | python3 -c "import sys,json; print(json.load(sys.stdin)['results']['episodes'][0]['server_ids'])" | xargs -I{} curl -s "https://anikototvapi.vercel.app/api/servers?ids={}" | python3 -c "import sys,json; print(json.load(sys.stdin)['results'][0]['link_id'])") -curl "https://anikototvapi.vercel.app/api/stream?id=$LINK_ID" +# Get stream URL from a link_id +LINK_ID="MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOHZGS2FSWVgvbVJraVpIV1dQRjRoN01hOFUvYmxsWXFYNGtiR0h5OWdGQWc9PQ" + +curl -s "https://anikototvapi.vercel.app/api/stream?id=$LINK_ID" | python3 -m json.tool ``` -### Random Anime +**Expected Output:** + +```json +{ + "success": true, + "results": { + "linkId": "MTF1dkFtaW9BRTZPbzJJRElFZUZrOWdjeldjOERLaWNMMXFNbVB3WUJqOHZGS2FSWVgvbVJraVpIV1dQRjRoN01hOFUvYmxsWXFYNGtiR0h5OWdGQWc9PQ", + "url": "https://megaplay.buzz/stream/s-5/94736/sub", + "skipData": { + "intro": [0, 0], + "outro": [0, 0] + } + } +} +``` + +> **Tip:** The `url` field is the direct embed URL. Use `/stream/resolve` to get the actual m3u8/mp4 URL. + +--- + +### Resolve Stream URL (embed → m3u8/mp4) ```bash -curl "https://anikototvapi.vercel.app/api/random" +# Resolve embed URL to actual playable stream URL +curl -s "https://anikototvapi.vercel.app/api/stream/resolve?id=$LINK_ID" | python3 -m json.tool ``` -### Schedule +**Expected Output:** + +```json +{ + "success": true, + "results": { + "url": "https://vid-tube.site/hls/b32de9d4a3230c95f52948373e6e1549.m3u8", + "type": "hls", + "server": "HD-1", + "subtitles": [] + } +} +``` + +--- + +### Full Streaming Pipeline (cURL one-liner) ```bash -curl "https://anikototvapi.vercel.app/api/schedule" +# Complete pipeline: episodes → servers → stream URL → resolve +curl -s "https://anikototvapi.vercel.app/api/episodes/21" \ + | python3 -c " +import sys, json, urllib.request + +data = json.load(sys.stdin) +server_ids = data['results']['episodes'][0]['server_ids'] + +servers = json.load(urllib.request.urlopen(f'https://anikototvapi.vercel.app/api/servers?ids={server_ids}')) +link_id = servers['results'][0]['link_id'] + +stream = json.load(urllib.request.urlopen(f'https://anikototvapi.vercel.app/api/stream?id={link_id}')) +print('Embed URL:', stream['results']['url']) +print('Skip intro:', stream['results']['skipData']['intro']) +" ``` -### Suggestions - -```bash -curl "https://anikototvapi.vercel.app/api/suggestions?keyword=one+piece" -``` - -### Top 10 - -```bash -curl "https://anikototvapi.vercel.app/api/top-ten" -``` +--- ### Filter by Genre ```bash -curl "https://anikototvapi.vercel.app/api/genre/action" -``` - -### Filter with Parameters - -```bash -curl "https://anikototvapi.vercel.app/api/filter?keyword=&genre[]=1&type=tv&status=aired" +# Get all Action anime +curl -s "https://anikototvapi.vercel.app/api/genre/action?page=1" | python3 -m json.tool ``` --- -## JavaScript (Browser) +### Filter by Type -### Homepage - -```javascript -async function getHomepage() { - const res = await fetch('https://anikototvapi.vercel.app/api'); - const data = await res.json(); - - console.log('Spotlights:', data.results.spotlights.length); - console.log('Trending:', data.results.trending.length); - console.log('Top Airing:', data.results.topAiring.length); - console.log('Genres:', data.results.genres.length); - - return data.results; -} - -getHomepage(); +```bash +# Get all Movies +curl -s "https://anikototvapi.vercel.app/api/type/movie?page=1" | python3 -m json.tool ``` -### Search +--- + +### Filter by Status + +```bash +# Get currently airing anime +curl -s "https://anikototvapi.vercel.app/api/status/currently-airing?page=1" | python3 -m json.tool +``` + +--- + +### Advanced Filter + +```bash +# Filter: Action TV anime, currently airing, sorted by score +curl -s "https://anikototvapi.vercel.app/api/filter?keyword=&genre=action&type=tv&status=currently-airing&sort=score&page=1" | python3 -m json.tool +``` + +> **Important:** The `keyword` param is required by the source site. Pass empty string `""` if not searching. + +--- + +### Get Top 10 (Day/Week/Month) + +```bash +curl -s "https://anikototvapi.vercel.app/api/top-ten" | python3 -m json.tool +``` + +--- + +### Get Trending + +```bash +curl -s "https://anikototvapi.vercel.app/api/trending" | python3 -m json.tool +``` + +--- + +### Random Anime + +```bash +# Returns a random anime — e.g. "Cheating Craft" (animeId 4879) +curl -s "https://anikototvapi.vercel.app/api/random" | python3 -m json.tool +``` + +**Expected Output:** + +```json +{ + "success": true, + "results": { + "slug": "cheating-craft-xhaj1", + "animeId": 4879, + "title": "Cheating Craft", + "japaneseTitle": "Cheat Kusushi no Slow Life: Isekai ni Tsukurou Drugstore", + "poster": "https://cdn.anipixcdn.co/thumbnail/...", + "type": "TV", + "synopsis": "An anime about students who use cheating techniques to pass exams...", + "rating": "6.50 6.50 /10", + "genres": ["Comedy", "School"] + } +} +``` + +--- + +### Search Suggestions (autocomplete) + +```bash +curl -s "https://anikototvapi.vercel.app/api/suggestions?keyword=naruto" | python3 -m json.tool +``` + +--- + +### AZ List + +```bash +# Browse anime starting with "A" +curl -s "https://anikototvapi.vercel.app/api/az-list/a?page=1" | python3 -m json.tool +``` + +--- + +### Most Popular + +```bash +curl -s "https://anikototvapi.vercel.app/api/most-popular?page=1" | python3 -m json.tool +``` + +--- + +### New Releases + +```bash +curl -s "https://anikototvapi.vercel.app/api/new-release?page=1" | python3 -m json.tool +``` + +--- + +### Watch Page + +```bash +curl -s "https://anikototvapi.vercel.app/api/watch?slug=one-piece-odmau&ep=1165" | python3 -m json.tool +``` + +--- + +### Proxy M3U8 for CORS-free Playback + +```bash +# Rewrite an M3U8 playlist so all URLs go through the API proxy +curl -s "https://anikototvapi.vercel.app/api/stream/proxy?url=https://example.com/playlist.m3u8" +``` + +> **Tip:** Use this endpoint with HLS.js to avoid CORS issues in the browser. + +--- + +// ═══════════════════════════════════════════════════════════════ +// ═══ SECTION 2: JavaScript (fetch) Examples ═══ +// ═══════════════════════════════════════════════════════════════ + +## JavaScript (Browser Fetch) Examples + +--- + +### Search and Display Results ```javascript async function searchAnime(keyword) { - const res = await fetch(`https://anikototvapi.vercel.app/api/search?keyword=${encodeURIComponent(keyword)}`); + const res = await fetch( + `https://anikototvapi.vercel.app/api/search?keyword=${encodeURIComponent(keyword)}` + ); const data = await res.json(); - - return data.results.data.map(anime => ({ + + if (!data.success) { + console.error('Search failed:', data.message); + return []; + } + + const results = data.results.data.map(anime => ({ title: anime.title, + japaneseTitle: anime.japaneseTitle, slug: anime.slug, + animeId: anime.animeId, + type: anime.type, sub: anime.sub, dub: anime.dub, - type: anime.type + rating: anime.rating, + genres: anime.genres })); -} -searchAnime('naruto').then(results => { - results.forEach(a => console.log(`${a.title} (${a.type}) — Sub: ${a.sub}, Dub: ${a.dub}`)); -}); -``` + results.forEach(a => { + console.log(`📺 ${a.title} (${a.type}) — Sub: ${a.sub}, Dub: ${a.dub} | Rating: ${a.rating}`); + }); -### Anime Info - -```javascript -async function getAnimeInfo(slug) { - const res = await fetch(`https://anikototvapi.vercel.app/api/info?id=${slug}`); - const data = await res.json(); - - const info = data.results; - console.log(`Title: ${info.title}`); - console.log(`Type: ${info.type}`); - console.log(`Status: ${info.status}`); - console.log(`Episodes: ${info.totalEpisodes || 'Unknown'}`); - console.log(`MAL ID: ${info.malId}`); - - return info; -} - -getAnimeInfo('naruto-eybxz'); -``` - -### Full Streaming Flow - -```javascript -async function getStreamUrl(animeId) { - // Step 1: Get episodes - const episodesRes = await fetch(`https://anikototvapi.vercel.app/api/episodes/${animeId}`); - const episodesData = await episodesRes.json(); - const episodes = episodesData.results.episodes; - - if (!episodes || episodes.length === 0) { - throw new Error('No episodes found'); - } - - // Use first episode's server_ids - const serverIds = episodes[0].server_ids; - - // Step 2: Get servers - const serversRes = await fetch(`https://anikototvapi.vercel.app/api/servers?ids=${serverIds}`); - const serversData = await serversRes.json(); - const servers = serversData.results; - - if (!servers || servers.length === 0) { - throw new Error('No servers found'); - } - - // Pick first server - const linkId = servers[0].link_id; - - // Step 3: Get stream URL - const streamRes = await fetch(`https://anikototvapi.vercel.app/api/stream?id=${linkId}`); - const streamData = await streamRes.json(); - - return streamData.results.url; + return results; } // Usage -getStreamUrl(958) - .then(url => { - console.log('Stream URL:', url); - // Use with video player - }) - .catch(err => console.error('Error:', err.message)); +searchAnime('naruto'); +// Output: +// 📺 Road of Naruto (ONA) — Sub: 1, Dub: 0 | Rating: 8.55 +// 📺 Naruto: Shippuuden Movie 6: Road to Ninja (Movie) — Sub: 1, Dub: 1 | Rating: 7.84 +// 📺 The Last: Naruto the Movie (Movie) — Sub: 1, Dub: 1 | Rating: 7.78 ``` -### Random Anime +--- + +### Full Streaming Workflow (episodes → servers → stream → resolve) ```javascript -async function getRandomAnime() { - const res = await fetch('https://anikototvapi.vercel.app/api/random'); - const data = await res.json(); - - const anime = data.results; - console.log(`Random: ${anime.title} (${anime.type})`); - console.log('Genres:', anime.genres.join(', ')); - - return anime; +async function getStreamUrl(animeId, episodeNumber = 1) { + const BASE = 'https://anikototvapi.vercel.app/api'; + + try { + // Step 1: Get episodes + const epRes = await fetch(`${BASE}/episodes/${animeId}`); + const epData = await epRes.json(); + const episodes = epData.results.episodes; + + if (!episodes || episodes.length === 0) { + throw new Error(`No episodes found for animeId ${animeId}`); + } + + const episode = episodes.find(e => e.episode_no === episodeNumber) || episodes[0]; + console.log(`Found episode ${episode.episode_no} (id: ${episode.id})`); + + // Step 2: Get servers + const srvRes = await fetch(`${BASE}/servers?ids=${episode.server_ids}`); + const srvData = await srvRes.json(); + const servers = srvData.results; + + if (!servers || servers.length === 0) { + throw new Error('No servers available'); + } + + console.log(`Available servers: ${servers.map(s => s.name).join(', ')}`); + + // Pick HD-1 by default, or first available + const server = servers.find(s => s.name === 'HD-1') || servers[0]; + console.log(`Using server: ${server.name} (${server.type})`); + + // Step 3: Get embed URL + const streamRes = await fetch(`${BASE}/stream?id=${server.link_id}`); + const streamData = await streamRes.json(); + const embedUrl = streamData.results.url; + console.log(`Embed URL: ${embedUrl}`); + + // Step 4: Resolve to actual m3u8/mp4 + const resolveRes = await fetch(`${BASE}/stream/resolve?id=${server.link_id}`); + const resolveData = await resolveRes.json(); + const streamUrl = resolveData.results.url; + const streamType = resolveData.results.type; // "hls" or "mp4" + + console.log(`✅ Resolved: ${streamUrl} (${streamType})`); + + return { + url: streamUrl, + type: streamType, + server: server.name, + skipIntro: streamData.results.skipData.intro, + skipOutro: streamData.results.skipData.outro + }; + + } catch (err) { + console.error('❌ Streaming error:', err.message); + return null; + } } -getRandomAnime(); +// Usage: Get stream URL for animeId 21, episode 1 +getStreamUrl(21, 1); +// Output: +// Found episode 1 (id: 302) +// Available servers: HD-1, Vidstream-2, VidCloud-1 +// Using server: HD-1 (sub) +// Embed URL: https://megaplay.buzz/stream/s-5/94736/sub +// ✅ Resolved: https://vid-tube.site/hls/b32de9d4a3230c95f52948373e6e1549.m3u8 (hls) ``` -### Filter by Genre +--- + +### Pagination Handling ```javascript -async function getAnimeByGenre(genre, page = 1) { - const res = await fetch(`https://anikototvapi.vercel.app/api/genre/${genre}?page=${page}`); +async function fetchAllSearchResults(keyword, maxPages = 5) { + const BASE = 'https://anikototvapi.vercel.app/api'; + const allResults = []; + + for (let page = 1; page <= maxPages; page++) { + try { + const res = await fetch(`${BASE}/search?keyword=${encodeURIComponent(keyword)}&page=${page}`); + const data = await res.json(); + + if (!data.success || !data.results.data.length) { + console.log(`No more results after page ${page - 1}`); + break; + } + + allResults.push(...data.results.data); + console.log(`Page ${page}: ${data.results.data.length} results`); + + // Stop if we've reached the last page + if (page >= data.results.totalPages) { + console.log(`Reached last page (${data.results.totalPages})`); + break; + } + + // Respect rate limits — wait 500ms between requests + await new Promise(r => setTimeout(r, 500)); + + } catch (err) { + console.error(`Error on page ${page}:`, err.message); + break; + } + } + + console.log(`Total results: ${allResults.length}`); + return allResults; +} + +// Usage +fetchAllSearchResults('naruto', 3); +``` + +--- + +### Error Handling with try/catch + +```javascript +async function safeFetch(endpoint, params = {}) { + const BASE = 'https://anikototvapi.vercel.app/api'; + const url = new URL(`${BASE}${endpoint}`); + + Object.entries(params).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + + try { + const res = await fetch(url.toString()); + + if (!res.ok) { + throw new Error(`HTTP ${res.status}: ${res.statusText}`); + } + + const data = await res.json(); + + if (!data.success) { + throw new Error(data.message || 'API returned success: false'); + } + + return data.results; + + } catch (err) { + if (err.name === 'TypeError' && err.message.includes('fetch')) { + console.error('Network error — API may be down:', err.message); + } else { + console.error('API error:', err.message); + } + return null; + } +} + +// Usage +const anime = await safeFetch('/info', { id: 'cheating-craft-xhaj1' }); +if (anime) { + console.log(`Found: ${anime.title} (${anime.type})`); +} + +const stream = await safeFetch('/stream', { id: 'MTF1dkFtaW9BRTZPbzJJRElFZUZr...' }); +if (stream) { + console.log(`Stream: ${stream.url}`); +} +``` + +--- + +### Auto-Complete Search Suggestions + +```javascript +async function getSearchSuggestions(keyword) { + if (!keyword || keyword.length < 2) return []; + + const res = await fetch( + `https://anikototvapi.vercel.app/api/suggestions?keyword=${encodeURIComponent(keyword)}` + ); const data = await res.json(); - - return data.results.data.map(anime => ({ + + if (!data.success) return []; + + return data.results.map(anime => ({ title: anime.title, - sub: anime.sub, - dub: anime.dub + japaneseTitle: anime.japaneseTitle, + slug: anime.slug, + type: anime.type, + poster: anime.poster })); } -getAnimeByGenre('action').then(anime => { - console.log('Action Anime:'); - anime.forEach(a => console.log(`- ${a.title} (Sub: ${a.sub}, Dub: ${a.dub})`)); +// Debounced search for input fields +let debounceTimer; +document.getElementById('search-input')?.addEventListener('input', (e) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(async () => { + const suggestions = await getSearchSuggestions(e.target.value); + console.log(`Found ${suggestions.length} suggestions:`); + suggestions.forEach(s => { + console.log(` ${s.title} (${s.type}) — ${s.slug}`); + }); + }, 300); // 300ms debounce }); ``` --- -## Node.js (with axios) - -### Homepage +### Trending and Top-10 Display ```javascript -const axios = require('axios'); +async function displayTrending() { + const [trendingRes, topTenRes] = await Promise.all([ + fetch('https://anikototvapi.vercel.app/api/trending'), + fetch('https://anikototvapi.vercel.app/api/top-ten') + ]); -async function getHomepage() { - const { data } = await axios.get('https://anikototvapi.vercel.app/api'); - - console.log('Spotlights:', data.results.spotlights.length); - console.log('Trending:', data.results.trending.length); - - return data.results; -} + const trending = await trendingRes.json(); + const topTen = await topTenRes.json(); -getHomepage(); -``` + console.log('=== Trending Anime ==='); + trending.results.slice(0, 5).forEach((anime, i) => { + console.log(` ${i + 1}. ${anime.title} — Sub: ${anime.sub}, Dub: ${anime.dub}`); + }); -### Search + console.log('\n=== Top 10 Today ==='); + topTen.results.today.slice(0, 5).forEach(anime => { + console.log(` #${anime.rank} ${anime.name}`); + }); -```javascript -const axios = require('axios'); - -async function searchAnime(keyword) { - const { data } = await axios.get('https://anikototvapi.vercel.app/api/search', { - params: { keyword } + console.log('\n=== Top 10 This Week ==='); + topTen.results.week.slice(0, 5).forEach(anime => { + console.log(` #${anime.rank} ${anime.name}`); }); - - return data.results.data; } -searchAnime('naruto').then(results => { - results.forEach(a => console.log(`${a.title} (${a.type})`)); -}); +displayTrending(); ``` -### Full Streaming Flow +--- + +// ═══════════════════════════════════════════════════════════════ +// ═══ SECTION 3: Python (requests) Examples ═══ +// ═══════════════════════════════════════════════════════════════ + +## Python (requests) Examples + +--- + +### Search for Anime + +```python +import requests + +BASE = "https://anikototvapi.vercel.app/api" + +def search_anime(keyword, page=1): + """Search for anime by keyword.""" + resp = requests.get(f"{BASE}/search", params={"keyword": keyword, "page": page}) + resp.raise_for_status() + data = resp.json() + + if not data["success"]: + raise Exception(data.get("message", "Search failed")) + + results = data["results"]["data"] + print(f"Found {len(results)} results (page {page}/{data['results']['totalPages']}):") + for anime in results: + print(f" {anime['title']} ({anime['type']}) — Sub: {anime['sub']}, Dub: {anime['dub']}") + + return results + +# Usage +results = search_anime("naruto") +# Output: +# Found 3 results (page 1/1): +# Road of Naruto (ONA) — Sub: 1, Dub: 0 +# Naruto: Shippuuden Movie 6: Road to Ninja (Movie) — Sub: 1, Dub: 1 +# The Last: Naruto the Movie (Movie) — Sub: 1, Dub: 1 +``` + +--- + +### Get Anime Details + +```python +import requests + +BASE = "https://anikototvapi.vercel.app/api" + +def get_anime_info(slug): + """Get full anime information by slug.""" + resp = requests.get(f"{BASE}/info", params={"id": slug}) + resp.raise_for_status() + data = resp.json() + + info = data["results"] + print(f"Title: {info['title']}") + print(f"Japanese: {info['japaneseTitle']}") + print(f"Type: {info['type']}") + print(f"Status: {info['status']}") + print(f"Episodes: {info.get('episodes', 'Unknown')}") + print(f"Score: {info['malScore']}") + print(f"Genres: {', '.join(info['genres'])}") + print(f"Studios: {', '.join(info['studios'])}") + print(f"Synopsis: {info['synopsis'][:100]}...") + + return info + +# Usage +info = get_anime_info("naruto-shippuuden-movie-6-road-to-ninja-w2wqq") +``` + +--- + +### Streaming Workflow (Python) + +```python +import requests + +BASE = "https://anikototvapi.vercel.app/api" + +def get_stream_url(anime_id, episode_number=1): + """Complete streaming workflow: episodes → servers → stream → resolve.""" + + # Step 1: Get episodes + ep_resp = requests.get(f"{BASE}/episodes/{anime_id}") + ep_resp.raise_for_status() + episodes = ep_resp.json()["results"]["episodes"] + + if not episodes: + raise Exception(f"No episodes found for animeId {anime_id}") + + episode = next( + (e for e in episodes if e["episode_no"] == episode_number), + episodes[0] + ) + print(f"Episode {episode['episode_no']} (id: {episode['id']})") + + # Step 2: Get servers + srv_resp = requests.get(f"{BASE}/servers", params={"ids": episode["server_ids"]}) + srv_resp.raise_for_status() + servers = srv_resp.json()["results"] + + if not servers: + raise Exception("No servers available") + + # Prefer HD-1 + server = next((s for s in servers if s["name"] == "HD-1"), servers[0]) + print(f"Server: {server['name']} ({server['type']})") + + # Step 3: Get embed URL + stream_resp = requests.get(f"{BASE}/stream", params={"id": server["link_id"]}) + stream_resp.raise_for_status() + embed_url = stream_resp.json()["results"]["url"] + print(f"Embed URL: {embed_url}") + + # Step 4: Resolve to actual stream + resolve_resp = requests.get(f"{BASE}/stream/resolve", params={"id": server["link_id"]}) + resolve_resp.raise_for_status() + resolved = resolve_resp.json()["results"] + + print(f"✅ Stream: {resolved['url']} ({resolved['type']})") + return resolved + +# Usage +stream = get_stream_url(21, 1) +# Output: +# Episode 1 (id: 302) +# Server: HD-1 (sub) +# Embed URL: https://megaplay.buzz/stream/s-5/94736/sub +# ✅ Stream: https://vid-tube.site/hls/...m3u8 (hls) +``` + +--- + +### Filter and Paginate + +```python +import requests +import time + +BASE = "https://anikototvapi.vercel.app/api" + +def filter_anime(genre=None, anime_type=None, status=None, sort=None, max_pages=3): + """Filter anime with multiple parameters and pagination.""" + all_results = [] + + for page in range(1, max_pages + 1): + params = {"keyword": "", "page": page} + if genre: + params["genre"] = genre + if anime_type: + params["type"] = anime_type + if status: + params["status"] = status + if sort: + params["sort"] = sort + + resp = requests.get(f"{BASE}/filter", params=params) + resp.raise_for_status() + data = resp.json() + + if not data["success"]: + break + + page_data = data["results"]["data"] + if not page_data: + break + + all_results.extend(page_data) + print(f"Page {page}: {len(page_data)} results") + + # Check pagination info if available + pagination = data["results"].get("pagination", {}) + if pagination and not pagination.get("hasNext", True): + break + + time.sleep(0.5) # Rate limit respect + + print(f"\nTotal: {len(all_results)} anime found") + for anime in all_results[:5]: + print(f" {anime['title']} ({anime['type']}) — Rating: {anime.get('rating', 'N/A')}") + + return all_results + +# Usage: Action TV anime, currently airing +filter_anime(genre="action", anime_type="tv", status="currently-airing") +``` + +--- + +### Batch Processing + +```python +import requests +import json +import time + +BASE = "https://anikototvapi.vercel.app/api" + +def batch_fetch_anime_info(slugs, delay=0.5): + """Fetch info for multiple anime with rate limiting.""" + results = [] + + for i, slug in enumerate(slugs): + try: + resp = requests.get(f"{BASE}/info", params={"id": slug}, timeout=10) + resp.raise_for_status() + data = resp.json() + + if data["success"]: + info = data["results"] + results.append({ + "slug": slug, + "title": info["title"], + "type": info["type"], + "status": info["status"], + "episodes": info.get("episodes"), + "score": info["malScore"], + "genres": info["genres"] + }) + print(f"[{i+1}/{len(slugs)}] ✅ {info['title']}") + else: + print(f"[{i+1}/{len(slugs)}] ❌ {slug}: {data.get('message', 'Unknown error')}") + + except Exception as e: + print(f"[{i+1}/{len(slugs)}] ❌ {slug}: {e}") + + time.sleep(delay) + + return results + +# Usage: Fetch info for multiple anime +slugs = [ + "cheating-craft-xhaj1", + "road-of-naruto-ggjw8", + "naruto-shippuuden-movie-6-road-to-ninja-w2wqq" +] +anime_list = batch_fetch_anime_info(slugs) + +# Save to JSON +with open("anime_batch.json", "w") as f: + json.dump(anime_list, f, indent=2) +print(f"\nSaved {len(anime_list)} anime to anime_batch.json") +``` + +--- + +### Random Anime Discovery + +```python +import requests + +BASE = "https://anikototvapi.vercel.app/api" + +def discover_random(count=5): + """Discover random anime for recommendations.""" + discovered = [] + + for i in range(count): + resp = requests.get(f"{BASE}/random") + resp.raise_for_status() + anime = resp.json()["results"] + discovered.append(anime) + print(f"{i+1}. {anime['title']} ({anime['type']}) — {', '.join(anime['genres'])}") + + return discovered + +# Usage +discover_random() +``` + +--- + +// ═══════════════════════════════════════════════════════════════ +// ═══ SECTION 4: Node.js (axios) Examples ═══ +// ═══════════════════════════════════════════════════════════════ + +## Node.js (axios) Examples + +--- + +### Server-Side Streaming Workflow ```javascript const axios = require('axios'); -async function getStreamUrl(animeId) { - const BASE = 'https://anikototvapi.vercel.app/api'; - +const BASE = 'https://anikototvapi.vercel.app/api'; + +async function getStreamUrl(animeId, episodeNumber = 1) { // Step 1: Get episodes - const { data: episodesData } = await axios.get(`${BASE}/episodes/${animeId}`); - const episodes = episodesData.results.episodes; - + const { data: epData } = await axios.get(`${BASE}/episodes/${animeId}`); + const episodes = epData.results.episodes; + if (!episodes || episodes.length === 0) { - throw new Error('No episodes found'); + throw new Error(`No episodes found for animeId ${animeId}`); } - - const serverIds = episodes[0].server_ids; - + + const episode = episodes.find(e => e.episode_no === episodeNumber) || episodes[0]; + console.log(`Episode ${episode.episode_no} (id: ${episode.id})`); + // Step 2: Get servers - const { data: serversData } = await axios.get(`${BASE}/servers`, { - params: { ids: serverIds } + const { data: srvData } = await axios.get(`${BASE}/servers`, { + params: { ids: episode.server_ids } }); - const servers = serversData.results; - + const servers = srvData.results; + if (!servers || servers.length === 0) { - throw new Error('No servers found'); + throw new Error('No servers available'); } - - const linkId = servers[0].link_id; - - // Step 3: Get stream URL + + const server = servers.find(s => s.name === 'HD-1') || servers[0]; + console.log(`Server: ${server.name} (${server.type})`); + + // Step 3: Get embed URL const { data: streamData } = await axios.get(`${BASE}/stream`, { - params: { id: linkId } + params: { id: server.link_id } }); - - return streamData.results.url; + console.log(`Embed: ${streamData.results.url}`); + + // Step 4: Resolve to actual stream + const { data: resolveData } = await axios.get(`${BASE}/stream/resolve`, { + params: { id: server.link_id } + }); + + const result = { + url: resolveData.results.url, + type: resolveData.results.type, + server: server.name, + skipIntro: streamData.results.skipData.intro, + skipOutro: streamData.results.skipData.outro + }; + + console.log(`✅ Stream: ${result.url} (${result.type})`); + return result; } // Usage -getStreamUrl(958) - .then(url => console.log('Stream URL:', url)) +getStreamUrl(21, 1) + .then(stream => console.log('Result:', stream)) .catch(err => console.error('Error:', err.message)); ``` -### Episode List +--- + +### Concurrent Requests + +```javascript +const axios = require('axios'); + +const BASE = 'https://anikototvapi.vercel.app/api'; + +async function fetchMultipleEndpoints() { + // Fetch multiple endpoints concurrently + const endpoints = [ + axios.get(`${BASE}/`), + axios.get(`${BASE}/trending`), + axios.get(`${BASE}/top-ten`), + axios.get(`${BASE}/random`), + axios.get(`${BASE}/suggestions`, { params: { keyword: 'naruto' } }) + ]; + + const [ + homeRes, + trendingRes, + topTenRes, + randomRes, + suggestRes + ] = await Promise.all(endpoints); + + console.log('=== Homepage ==='); + console.log(`Spotlights: ${homeRes.data.results.spotlights.length}`); + console.log(`Trending: ${homeRes.data.results.trending.length}`); + console.log(`Genres: ${homeRes.data.results.genres.length}`); + + console.log('\n=== Trending (top 3) ==='); + trendingRes.data.results.slice(0, 3).forEach((a, i) => { + console.log(` ${i + 1}. ${a.title} — Sub: ${a.sub}, Dub: ${a.dub}`); + }); + + console.log('\n=== Top 10 Today ==='); + topTenRes.data.results.today.slice(0, 3).forEach(a => { + console.log(` #${a.rank} ${a.name}`); + }); + + console.log('\n=== Random Anime ==='); + const random = randomRes.data.results; + console.log(` ${random.title} (${random.type}) — ${random.genres.join(', ')}`); + + console.log('\n=== Naruto Suggestions ==='); + suggestRes.data.results.forEach(s => { + console.log(` ${s.title} (${s.type}) — ${s.slug}`); + }); +} + +fetchMultipleEndpoints().catch(console.error); +``` + +--- + +### Error Handling with Retries + +```javascript +const axios = require('axios'); + +const BASE = 'https://anikototvapi.vercel.app/api'; + +async function fetchWithRetry(url, params = {}, retries = 3, delay = 1000) { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + const { data } = await axios.get(url, { + params, + timeout: 10000 + }); + + if (!data.success) { + throw new Error(data.message || 'API returned success: false'); + } + + return data.results; + + } catch (err) { + const isLastAttempt = attempt === retries; + const status = err.response?.status; + const isServerError = status && status >= 500; + + if (isLastAttempt || !isServerError) { + throw err; + } + + console.warn(`Attempt ${attempt} failed (${status}), retrying in ${delay}ms...`); + await new Promise(r => setTimeout(r, delay)); + delay *= 2; // Exponential backoff + } + } +} + +// Usage +async function main() { + try { + const anime = await fetchWithRetry(`${BASE}/info`, { id: 'cheating-craft-xhaj1' }); + console.log(`Found: ${anime.title} (${anime.type})`); + + const episodes = await fetchWithRetry(`${BASE}/episodes/${anime.animeId}`); + console.log(`Episodes: ${episodes.totalEpisodes}`); + } catch (err) { + console.error('Failed after retries:', err.message); + } +} + +main(); +``` + +--- + +### Episode List with Axios ```javascript const axios = require('axios'); async function getEpisodeList(animeId) { const { data } = await axios.get(`https://anikototvapi.vercel.app/api/episodes/${animeId}`); - + const { animeId: id, totalEpisodes, episodes } = data.results; - + console.log(`Anime ID: ${id}`); console.log(`Total Episodes: ${totalEpisodes}`); console.log(`Episodes available: ${episodes.length}`); - + episodes.forEach(ep => { - console.log(` Episode ${ep.episode_no}: server_ids available = ${!!ep.server_ids}`); + console.log(` Episode ${ep.episode_no}: server_ids = ${ep.server_ids ? 'available' : 'missing'}`); }); - + return data.results; } -getEpisodeList(958); +getEpisodeList(21); ``` -### Suggestions +--- + +### Suggestions with Axios ```javascript const axios = require('axios'); @@ -340,127 +1212,1029 @@ async function getSuggestions(keyword) { const { data } = await axios.get('https://anikototvapi.vercel.app/api/suggestions', { params: { keyword } }); - + return data.results; } -getSuggestions('one piece').then(suggestions => { +getSuggestions('naruto').then(suggestions => { suggestions.forEach(a => console.log(`${a.title} (${a.slug})`)); }); ``` -### Schedule +--- -```javascript -const axios = require('axios'); +// ═══════════════════════════════════════════════════════════════ +// ═══ SECTION 5: HTML/JS Player Integration ═══ +// ═══════════════════════════════════════════════════════════════ -async function getSchedule() { - const { data } = await axios.get('https://anikototvapi.vercel.app/api/schedule'); - - console.log('Scheduled anime:', data.results.length); - return data.results; -} +## HTML/JS Player Integration -getSchedule(); +--- + +### HLS.js Player with M3U8 Proxy + +```html + + +
+ + +