mirror of
https://github.com/Shineii86/AniKotoAPI.git
synced 2026-10-22 10:55:10 +00:00
v1.9.0: Full diagnostic sweep - 42 issues fixed
Critical fixes (7): - Added global Express error handler (returns JSON instead of HTML) - Removed caching from random endpoint (defeated purpose) - Renamed share.jpg to share.png (MIME type mismatch) - Fixed OG image dimensions (630 → 601) - Fixed manifest.json icon sizes (192x192 → 512x512) - Fixed implicit global event variable in console - Added 9 missing endpoints to docs/endpoints.md High fixes (12): - Imported version from package.json (no hardcoded drift) - Added dynamic endpoint count in stats - Added input sanitization for mapper API (SSRF prevention) - Added homepage caching (reduced 5 duplicate HTTP requests) - Added rate limiting (100 requests/minute per IP) - Added rel="noopener noreferrer" to all target="_blank" links - Fixed trailing slash inconsistency in console - Added noindex meta to 404.html - Fixed docs/architecture.md controller filenames and dependency versions - Fixed README.md dependency versions - Added 12 missing endpoint tests Medium fixes (22): - Added escapeHtml helper for error messages - Consistent favicon across all pages - Added meta descriptions to privacy.html and tos.html - Added OG/Twitter meta tags to privacy.html and tos.html - Added aria-label to playground inputs - Added aria-hidden to decorative SVGs - Synced Twitter description with OG description - Fixed robots.txt to disallow /api - Added PWA fields to manifest.json - Removed 5 unused imports - Removed unused cookie-parser dependency - Fixed inconsistent error handling in extractors - Fixed resolvedSlug returning numeric ID - Added /watch endpoint to streaming docs - Improved test assertions and status code validation - Deleted orphaned og-image.svg and logo.png Low fixes (11): - Removed redundant try/catch from extractPages - Added max size eviction to cache - Added cache key sorting in filter controller - Added author meta tag - Fixed favicon.svg font Version bumped to 1.9.0
This commit is contained in:
@@ -5,6 +5,52 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.9.0] - 2026-06-09
|
||||
|
||||
### Fixed
|
||||
- **CRITICAL**: Added global Express error handler (returns JSON instead of HTML)
|
||||
- **CRITICAL**: Removed caching from random endpoint (defeated purpose)
|
||||
- **CRITICAL**: Renamed share.jpg to share.png (MIME type mismatch)
|
||||
- **CRITICAL**: Fixed OG image dimensions (630 → 601)
|
||||
- **CRITICAL**: Fixed manifest.json icon sizes (192x192 → 512x512)
|
||||
- **CRITICAL**: Fixed implicit global event variable in console
|
||||
- **CRITICAL**: Added 9 missing endpoints to docs/endpoints.md
|
||||
- Imported version from package.json (no hardcoded drift)
|
||||
- Added dynamic endpoint count in stats
|
||||
- Added input sanitization for mapper API (SSRF prevention)
|
||||
- Added homepage caching (reduced 5 duplicate HTTP requests)
|
||||
- Added rate limiting (100 requests/minute per IP)
|
||||
- Added rel="noopener noreferrer" to all target="_blank" links
|
||||
- Fixed trailing slash inconsistency in console
|
||||
- Added noindex meta to 404.html
|
||||
- Fixed docs/architecture.md controller filenames and dependency versions
|
||||
- Fixed README.md dependency versions
|
||||
- Added 12 missing endpoint tests
|
||||
- Added escapeHtml helper for error messages
|
||||
- Consistent favicon across all pages
|
||||
- Added meta descriptions to privacy.html and tos.html
|
||||
- Added OG/Twitter meta tags to privacy.html and tos.html
|
||||
- Added aria-label to playground inputs
|
||||
- Added aria-hidden to decorative SVGs
|
||||
- Synced Twitter description with OG description
|
||||
- Fixed robots.txt to disallow /api
|
||||
- Added PWA fields to manifest.json
|
||||
- Removed 5 unused imports
|
||||
- Removed unused cookie-parser dependency
|
||||
- Fixed inconsistent error handling in extractors
|
||||
- Fixed resolvedSlug returning numeric ID
|
||||
- Added /watch endpoint to streaming docs
|
||||
- Improved test assertions and status code validation
|
||||
- Deleted orphaned og-image.svg and logo.png
|
||||
- Removed redundant try/catch from extractPages
|
||||
- Added max size eviction to cache
|
||||
- Added cache key sorting in filter controller
|
||||
- Added author meta tag
|
||||
- Fixed favicon.svg font
|
||||
|
||||
### Changed
|
||||
- Version bumped to 1.9.0
|
||||
|
||||
## [1.8.0] - 2026-06-08
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<img src="https://img.shields.io/badge/Cheerio-1.0-3572A5?style=flat-square&logoColor=white" alt="Cheerio"/>
|
||||
<img src="https://img.shields.io/badge/Vercel-Serverless-000000?style=flat-square&logo=vercel&logoColor=white" alt="Vercel"/>
|
||||
<img src="https://img.shields.io/badge/License-MIT-22c55e?style=flat-square&logo=mit&logoColor=white" alt="License"/>
|
||||
<img src="https://img.shields.io/badge/Version-1.8.0-f43f8e?style=flat-square&logoColor=white" alt="Version"/>
|
||||
<img src="https://img.shields.io/badge/Version-1.9.0-f43f8e?style=flat-square&logoColor=white" alt="Version"/>
|
||||
<img src="https://img.shields.io/badge/Endpoints-30-6366f1?style=flat-square&logoColor=white" alt="Endpoints"/>
|
||||
<img src="https://img.shields.io/badge/Anime-10000+-a855f7?style=flat-square&logoColor=white" alt="Anime Database"/>
|
||||
</p>
|
||||
@@ -225,11 +225,11 @@ flowchart TD
|
||||
| Technology | Purpose | Version | Documentation |
|
||||
|:---|:---|:---|:---|
|
||||
| 🟢 [Node.js](https://nodejs.org/) | JavaScript runtime | >= 20 | [Docs](https://nodejs.org/docs/) |
|
||||
| ⚡ [Express](https://expressjs.com/) | HTTP server framework | 5.2 | [Docs](https://expressjs.com/en/5x/api.html) |
|
||||
| ⚡ [Express | 4.21 | [Docs](https://expressjs.com/en/5x/api.html) |
|
||||
| ▲ [Vercel Functions](https://vercel.com/docs/functions) | Serverless deployment | — | [Docs](https://vercel.com/docs/functions) |
|
||||
| 🔍 [Cheerio](https://cheerio.js.org/) | HTML parsing & scraping | 1.0 | [Docs](https://cheerio.js.org/docs/) |
|
||||
| 🌐 [Axios](https://axios-http.com/) | HTTP client | 1.11 | [Docs](https://axios-http.com/docs/intro) |
|
||||
| 🔧 [dotenv](https://github.com/motdotla/dotenv) | Environment variables | 17.2 | [Docs](https://github.com/motdotla/dotenv) |
|
||||
| 🌐 [Axios | 1.8 | [Docs](https://axios-http.com/docs/intro) |
|
||||
| 🔧 [dotenv | 16.4 | [Docs](https://github.com/motdotla/dotenv) |
|
||||
| 🔒 [cors](https://github.com/expressjs/cors) | CORS middleware | 2.8 | [Docs](https://github.com/expressjs/cors) |
|
||||
| 🍪 [cookie-parser](https://github.com/expressjs/cookie-parser) | Cookie parsing | 1.4 | [Docs](https://github.com/expressjs/cookie-parser) |
|
||||
|
||||
@@ -238,10 +238,10 @@ flowchart TD
|
||||
```json
|
||||
{
|
||||
"express": "^5.2.0", // HTTP server
|
||||
"axios": "^1.11.0", // HTTP client for scraping
|
||||
"axios": "^1.8.0", // HTTP client for scraping
|
||||
"cheerio": "^1.0.0-rc.12", // HTML parsing
|
||||
"cors": "^2.8.5", // CORS middleware
|
||||
"dotenv": "^17.2.0", // Environment variables
|
||||
"dotenv | 16.4.0", // Environment variables
|
||||
"cookie-parser": "^1.4.7" // Cookie parsing
|
||||
}
|
||||
```
|
||||
|
||||
+12
-12
@@ -37,23 +37,23 @@ AniKotoAPI/
|
||||
│ │ └── category.route.js # genre/:name, type/:name, status/:name
|
||||
│ │
|
||||
│ ├── controllers/
|
||||
│ │ ├── home.controller.js # Homepage data
|
||||
│ │ ├── homeInfo.controller.js # Homepage data
|
||||
│ │ ├── search.controller.js # Anime search
|
||||
│ │ ├── info.controller.js # Anime info
|
||||
│ │ ├── episodes.controller.js # Episode list
|
||||
│ │ ├── animeInfo.controller.js # Anime info
|
||||
│ │ ├── episodeList.controller.js # Episode list
|
||||
│ │ ├── servers.controller.js # Server list
|
||||
│ │ ├── stream.controller.js # Stream URL
|
||||
│ │ ├── suggestion.controller.js # Anime suggestions
|
||||
│ │ ├── spotlight.controller.js # Spotlight anime
|
||||
│ │ ├── trending.controller.js # Trending anime
|
||||
│ │ ├── topTen.controller.js # Top 10
|
||||
│ │ ├── topten.controller.js # Top 10
|
||||
│ │ ├── schedule.controller.js # Schedule
|
||||
│ │ ├── random.controller.js # Random anime
|
||||
│ │ ├── newRelease.controller.js # New releases
|
||||
│ │ ├── mostPopular.controller.js # Most popular
|
||||
│ │ ├── genre.controller.js # Genre filter
|
||||
│ │ ├── type.controller.js # Type filter
|
||||
│ │ ├── status.controller.js # Status filter
|
||||
│ │ ├── popular.controller.js # Most popular
|
||||
│ │ ├── category.controller.js # Genre filter
|
||||
│ │ ├── category.controller.js # Type filter
|
||||
│ │ ├── category.controller.js # Status filter
|
||||
│ │ └── filter.controller.js # Advanced filter
|
||||
│ │
|
||||
│ ├── extractors/
|
||||
@@ -204,9 +204,9 @@ Error responses:
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| express | ^4.18.2 | Web framework |
|
||||
| express | ^4.21.0 | Web framework |
|
||||
| cheerio | ^1.0.0-rc.12 | HTML parsing |
|
||||
| axios | ^1.6.0 | HTTP requests |
|
||||
| axios | ^1.8.0 | HTTP requests |
|
||||
| cors | ^2.8.5 | CORS headers |
|
||||
| cookie-parser | ^1.4.6 | Cookie parsing |
|
||||
| dotenv | ^16.3.1 | Environment variables |
|
||||
| cookie-parser | ^1.4.7 | Cookie parsing |
|
||||
| dotenv | ^16.4.0 | Environment variables |
|
||||
|
||||
@@ -454,3 +454,147 @@ curl "https://anikototvapi.vercel.app/api/filter?keyword=&genre[]=1"
|
||||
```
|
||||
|
||||
Returns filtered and paginated anime list.
|
||||
|
||||
---
|
||||
|
||||
## GET /watch
|
||||
|
||||
Get full watch page data including servers, trending, and recommended anime.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `slug` | string | Yes | Anime slug (e.g., `one-piece-100`) |
|
||||
| `ep` | number | Yes | Episode number |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/watch?slug=one-piece-100&ep=1"
|
||||
```
|
||||
|
||||
Returns episode data with server list, trending sidebar, and recommended anime.
|
||||
|
||||
---
|
||||
|
||||
## GET /search/suggest
|
||||
|
||||
Get search autocomplete suggestions.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `keyword` | string | Yes | Search keyword |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/search/suggest?keyword=naruto"
|
||||
```
|
||||
|
||||
Returns array of suggestion objects with title, ID, and image.
|
||||
|
||||
---
|
||||
|
||||
## GET /episodes-ajax/:id
|
||||
|
||||
Get AJAX-loaded episode list for an anime.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | string | Yes | Anime ID or slug |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/episodes-ajax/one-piece-100"
|
||||
```
|
||||
|
||||
Returns episode list with filters, ranges, and server IDs.
|
||||
|
||||
---
|
||||
|
||||
## GET /mapper-servers
|
||||
|
||||
Get cross-server mapping for gogoanime/anivibe servers.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `malId` | number | Yes | MyAnimeList ID |
|
||||
| `slug` | string | Yes | Anime slug |
|
||||
| `timestamp` | number | Yes | Timestamp |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/mapper-servers?malId=21&slug=one-piece-100×tamp=1234567890"
|
||||
```
|
||||
|
||||
Returns mapped server URLs for gogoanime/anivibe.
|
||||
|
||||
---
|
||||
|
||||
## GET /newly-added
|
||||
|
||||
Get newly added anime series.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `page` | number | No | Page number (default: 1) |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/newly-added"
|
||||
```
|
||||
|
||||
Returns paginated list of newly added anime.
|
||||
|
||||
---
|
||||
|
||||
## GET /trending-sidebar
|
||||
|
||||
Get trending sidebar widget data.
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/trending-sidebar"
|
||||
```
|
||||
|
||||
Returns trending anime for sidebar display.
|
||||
|
||||
---
|
||||
|
||||
## GET /seasons/:id
|
||||
|
||||
Get season information for an anime.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | string | Yes | Anime slug |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/seasons/one-piece-100"
|
||||
```
|
||||
|
||||
Returns season data including related seasons.
|
||||
|
||||
---
|
||||
|
||||
## GET /watch-order/:id
|
||||
|
||||
Get recommended watch order for an anime.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | string | Yes | Anime slug |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/watch-order/one-piece-100"
|
||||
```
|
||||
|
||||
Returns recommended watch order sequence.
|
||||
|
||||
---
|
||||
|
||||
## GET /az-list/:letter
|
||||
|
||||
Get anime alphabetically by letter.
|
||||
|
||||
| Param | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `letter` | string | Yes | Letter (a-z, or # for numbers) |
|
||||
| `page` | number | No | Page number (default: 1) |
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/az-list/a"
|
||||
```
|
||||
|
||||
Returns paginated list of anime starting with the specified letter.
|
||||
|
||||
@@ -251,3 +251,24 @@ player.source = {
|
||||
| "No servers found" | The `server_ids` may be invalid, try another episode |
|
||||
| Stream URL 403 | Some servers require specific referrer headers |
|
||||
| CORS error | Use a proxy or access from server-side |
|
||||
|
||||
---
|
||||
|
||||
## Alternative: All-in-One /watch Endpoint
|
||||
|
||||
For a simpler approach, use the `/watch` endpoint which returns episode data, servers, and recommendations in a single call.
|
||||
|
||||
**Request:**
|
||||
|
||||
```bash
|
||||
curl "https://anikototvapi.vercel.app/api/watch?slug=road-of-naruto-ggjw8&ep=1"
|
||||
```
|
||||
|
||||
**Response includes:**
|
||||
- Episode data with title, number, and description
|
||||
- Server list (sub and dub)
|
||||
- Trending sidebar anime
|
||||
- Recommended anime
|
||||
- Next episode schedule
|
||||
|
||||
This is useful when you need all watch page data at once instead of making 3 separate requests.
|
||||
|
||||
+1
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "AniKotoAPI",
|
||||
"version": "1.8.0",
|
||||
"version": "1.9.0",
|
||||
"description": "A restful API to provide all information about anime from anikototv.to",
|
||||
"main": "./server.js",
|
||||
"scripts": {
|
||||
@@ -13,7 +13,6 @@
|
||||
"dependencies": {
|
||||
"axios": "^1.8.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.0",
|
||||
"express": "^4.21.0"
|
||||
|
||||
+2
-1
@@ -2,10 +2,11 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>404 — AniKotoAPI</title>
|
||||
<meta name="theme-color" content="#0A0A12" />
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛩</text></svg>" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
|
||||
|
||||
+1
-1
@@ -6,5 +6,5 @@
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="100" height="100" rx="20" fill="url(#grad)"/>
|
||||
<text x="50" y="68" font-family="Arial, sans-serif" font-size="50" font-weight="bold" fill="white" text-anchor="middle">AK</text>
|
||||
<text x="50" y="68" font-family="system-ui, -apple-system, sans-serif" font-size="50" font-weight="bold" fill="white" text-anchor="middle">AK</text>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 479 B After Width: | Height: | Size: 498 B |
+55
-53
@@ -9,16 +9,16 @@
|
||||
<meta property="og:title" content="AniKotoAPI — Free Anime Data API" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://anikototvapi.vercel.app" />
|
||||
<meta property="og:image" content="https://anikototvapi.vercel.app/share.jpg" />
|
||||
<meta property="og:image" content="https://anikototvapi.vercel.app/share.png" />
|
||||
<meta property="og:description" content="Free REST API for anime data. Browse, search, stream episodes. 30+ endpoints, no API key required." />
|
||||
<meta property="og:site_name" content="AniKotoAPI" />
|
||||
<meta property="og:locale" content="en_US" />
|
||||
|
||||
<!-- ──────────────── Image Optimization ──────────────── -->
|
||||
<meta property="og:image:secure_url" content="https://anikototvapi.vercel.app/share.jpg" />
|
||||
<meta property="og:image:secure_url" content="https://anikototvapi.vercel.app/share.png" />
|
||||
<meta property="og:image:type" content="image/jpeg" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:image:height" content="601" />
|
||||
<meta property="og:image:alt" content="AniKotoAPI - Free Anime Data REST API" />
|
||||
|
||||
<!-- ──────────────── Twitter Card Tags ──────────────── -->
|
||||
@@ -26,13 +26,14 @@
|
||||
<meta name="twitter:site" content="@Shineii86" />
|
||||
<meta name="twitter:creator" content="@Shineii86" />
|
||||
<meta name="twitter:title" content="AniKotoAPI — Free Anime Data API" />
|
||||
<meta name="twitter:description" content="Free REST API for anime data. Browse, search, stream episodes. 30+ endpoints." />
|
||||
<meta name="twitter:image" content="https://anikototvapi.vercel.app/share.jpg" />
|
||||
<meta name="twitter:description" content="Free REST API for anime data. Browse, search, stream episodes. 30+ endpoints, no API key required." />
|
||||
<meta name="twitter:image" content="https://anikototvapi.vercel.app/share.png" />
|
||||
<meta name="twitter:image:alt" content="AniKotoAPI - Free Anime Data REST API" />
|
||||
<meta name="twitter:domain" content="anikototvapi.vercel.app" />
|
||||
|
||||
<!-- ──────────────── Generic Meta ──────────────── -->
|
||||
<meta name="description" content="Free REST API for anime data. Browse, search, stream episodes. 30+ endpoints, no API key required. Built with Node.js, Express, Cheerio." />
|
||||
<meta name="author" content="Shinei Nouzen" />
|
||||
<meta name="keywords" content="anime api, free api, rest api, anime data, streaming, episode servers, nodejs, express, anime database, anime search" />
|
||||
<meta name="robots" content="index, follow" />
|
||||
<meta name="theme-color" content="#0A0A12" />
|
||||
@@ -277,7 +278,7 @@
|
||||
"name": "AniKotoAPI",
|
||||
"description": "Free REST API for anime data. Browse, search, stream episodes. 30+ endpoints, no API key required.",
|
||||
"url": "https://anikototvapi.vercel.app",
|
||||
"image": "https://anikototvapi.vercel.app/share.jpg",
|
||||
"image": "https://anikototvapi.vercel.app/share.png",
|
||||
"documentation": "https://github.com/Shineii86/AniKotoAPI",
|
||||
"provider": {
|
||||
"@type": "Organization",
|
||||
@@ -306,14 +307,14 @@
|
||||
<header id="header">
|
||||
<div class="header-inner">
|
||||
<a href="/" class="logo">
|
||||
<svg viewBox="0 0 32 32" fill="none"><circle cx="16" cy="16" r="14" stroke="url(#g1)" stroke-width="2"/><path d="M10 20c0-4 3-7 6-7s6 3 6 7" stroke="url(#g1)" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="12" r="2" fill="var(--purple)"/><circle cx="20" cy="12" r="2" fill="var(--pink)"/><defs><linearGradient id="g1" x1="0" y1="0" x2="32" y2="32"><stop stop-color="var(--purple)"/><stop offset="1" stop-color="var(--pink)"/></linearGradient></defs></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 32 32" fill="none"><circle cx="16" cy="16" r="14" stroke="url(#g1)" stroke-width="2"/><path d="M10 20c0-4 3-7 6-7s6 3 6 7" stroke="url(#g1)" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="12" r="2" fill="var(--purple)"/><circle cx="20" cy="12" r="2" fill="var(--pink)"/><defs><linearGradient id="g1" x1="0" y1="0" x2="32" y2="32"><stop stop-color="var(--purple)"/><stop offset="1" stop-color="var(--pink)"/></linearGradient></defs></svg>
|
||||
<span class="logo-text"><span>AniKoto</span>API</span>
|
||||
</a>
|
||||
<nav>
|
||||
<a href="#features">Features</a>
|
||||
<a href="#endpoints">Endpoints</a>
|
||||
<a href="#playground">Playground</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank">GitHub</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
<a href="https://anikototvapi.vercel.app/api" target="_blank" class="nav-cta">Open API</a>
|
||||
</nav>
|
||||
</div>
|
||||
@@ -326,11 +327,11 @@
|
||||
<p class="hero-sub">Browse, search, and stream anime data from AniKoto. Real-time scraping with 30+ endpoints, in-memory caching, and zero configuration.</p>
|
||||
<div class="hero-actions">
|
||||
<a href="#playground" class="btn btn-primary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
Try It Live
|
||||
</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" class="btn btn-ghost">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
Star on GitHub
|
||||
</a>
|
||||
</div>
|
||||
@@ -351,13 +352,13 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<div class="console-output" id="consoleOutput"><span style="color:var(--text3)">// Click "Run" to see the response</span></div>
|
||||
</div>
|
||||
<div class="console-actions">
|
||||
<button class="console-btn active" onclick="setEndpoint('home')">Home</button>
|
||||
<button class="console-btn" onclick="setEndpoint('search')">Search</button>
|
||||
<button class="console-btn" onclick="setEndpoint('stream')">Stream</button>
|
||||
<button class="console-btn" onclick="setEndpoint('random')">Random</button>
|
||||
<button class="console-btn" onclick="setEndpoint('schedule')">Schedule</button>
|
||||
<button class="console-btn active" onclick="setEndpoint('home', event)">Home</button>
|
||||
<button class="console-btn" onclick="setEndpoint('search', event)">Search</button>
|
||||
<button class="console-btn" onclick="setEndpoint('stream', event)">Stream</button>
|
||||
<button class="console-btn" onclick="setEndpoint('random', event)">Random</button>
|
||||
<button class="console-btn" onclick="setEndpoint('schedule', event)">Schedule</button>
|
||||
<button class="console-send" onclick="runConsole()">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:12px;height:12px;display:inline"><polygon points="5 3 19 12 5 21 5 3"/></svg> Run
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:12px;height:12px;display:inline"><polygon points="5 3 19 12 5 21 5 3"/></svg> Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -378,19 +379,19 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<!-- Marquee -->
|
||||
<div class="marquee">
|
||||
<div class="marquee-track">
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 5-min cache TTL</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg> Zero config</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> No auth needed</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8m-4-4v4"/></svg> Vercel deployed</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg> Cheerio scraper</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> REST JSON</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/></svg> CORS enabled</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> Open source</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 5-min cache TTL</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg> Zero config</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> No auth needed</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8m-4-4v4"/></svg> Vercel deployed</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></svg> Cheerio scraper</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> REST JSON</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M2 12h20"/></svg> CORS enabled</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"/><polyline points="22,6 12,13 2,6"/></svg> Open source</div>
|
||||
<!-- duplicate for seamless loop -->
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 5-min cache TTL</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg> Zero config</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> No auth needed</div>
|
||||
<div class="marquee-item"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8m-4-4v4"/></svg> Vercel deployed</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg> 5-min cache TTL</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg> Zero config</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> No auth needed</div>
|
||||
<div class="marquee-item"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8m-4-4v4"/></svg> Vercel deployed</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -399,7 +400,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<div class="container">
|
||||
<div class="reveal">
|
||||
<div class="section-label">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><path d="M12 2L2 7l10 5 10-5-10-5z"/><path d="M2 17l10 5 10-5"/><path d="M2 12l10 5 10-5"/></svg>
|
||||
Features
|
||||
</div>
|
||||
<h2 class="section-title">Built for developers</h2>
|
||||
@@ -408,42 +409,42 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<div class="features-grid">
|
||||
<div class="feature-card reveal">
|
||||
<div class="feature-icon fi-purple">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
</div>
|
||||
<div class="feature-title">Full-text Search</div>
|
||||
<div class="feature-desc">Search by keyword, genre, type, status. Get autocomplete suggestions and paginated results.</div>
|
||||
</div>
|
||||
<div class="feature-card reveal reveal-delay-1">
|
||||
<div class="feature-icon fi-pink">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="2.18"/><path d="M7 2v20M17 2v20M2 12h20M2 7h5M2 17h5M17 7h5M17 17h5"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="20" rx="2.18"/><path d="M7 2v20M17 2v20M2 12h20M2 7h5M2 17h5M17 7h5M17 17h5"/></svg>
|
||||
</div>
|
||||
<div class="feature-title">Episode Streaming</div>
|
||||
<div class="feature-desc">Retrieve streaming servers, source links, and m3u8 URLs for any episode via AJAX endpoints.</div>
|
||||
</div>
|
||||
<div class="feature-card reveal reveal-delay-2">
|
||||
<div class="feature-icon fi-green">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
|
||||
</div>
|
||||
<div class="feature-title">Rich Metadata</div>
|
||||
<div class="feature-desc">Synopsis, MAL score, genres, characters, relations, recommendations, and more.</div>
|
||||
</div>
|
||||
<div class="feature-card reveal">
|
||||
<div class="feature-icon fi-cyan">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
||||
</div>
|
||||
<div class="feature-title">Schedule & Calendar</div>
|
||||
<div class="feature-desc">View what's airing today or fetch the full weekly broadcast schedule.</div>
|
||||
</div>
|
||||
<div class="feature-card reveal reveal-delay-1">
|
||||
<div class="feature-icon fi-amber">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>
|
||||
</div>
|
||||
<div class="feature-title">Advanced Filters</div>
|
||||
<div class="feature-desc">Filter by genre, type, status, season, sort order. Multi-select support.</div>
|
||||
</div>
|
||||
<div class="feature-card reveal reveal-delay-2">
|
||||
<div class="feature-icon fi-indigo">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
|
||||
</div>
|
||||
<div class="feature-title">Fast & Cached</div>
|
||||
<div class="feature-desc">In-memory cache with 5-minute TTL for snappy responses and reduced scraping overhead.</div>
|
||||
@@ -457,7 +458,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<div class="container">
|
||||
<div class="reveal">
|
||||
<div class="section-label">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>
|
||||
API Reference
|
||||
</div>
|
||||
<h2 class="section-title">Endpoints</h2>
|
||||
@@ -476,7 +477,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<div class="container">
|
||||
<div class="reveal">
|
||||
<div class="section-label">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
Interactive
|
||||
</div>
|
||||
<h2 class="section-title">Playground</h2>
|
||||
@@ -487,12 +488,12 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<!-- Search -->
|
||||
<div class="playground-card reveal">
|
||||
<div class="playground-header">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
<h3>Search Anime</h3>
|
||||
</div>
|
||||
<div class="playground-body">
|
||||
<div class="playground-inputs">
|
||||
<input id="pgSearch" type="text" placeholder="e.g. naruto, one piece" />
|
||||
<input id="pgSearch" type="text" placeholder="e.g. naruto, one piece" aria-label="Search anime" />
|
||||
<button class="btn btn-primary" onclick="pgRun('search')" style="padding:10px 20px">Search</button>
|
||||
</div>
|
||||
<div class="playground-output" id="pgSearchOut"></div>
|
||||
@@ -501,7 +502,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<!-- Stream -->
|
||||
<div class="playground-card reveal reveal-delay-1">
|
||||
<div class="playground-header">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
<h3>Stream Servers</h3>
|
||||
</div>
|
||||
<div class="playground-body">
|
||||
@@ -515,7 +516,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<!-- Random -->
|
||||
<div class="playground-card reveal reveal-delay-2">
|
||||
<div class="playground-header">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><path d="M3.27 6.96L12 12.01l8.73-5.05M12 22.08V12"/></svg>
|
||||
<h3>Random Anime</h3>
|
||||
</div>
|
||||
<div class="playground-body">
|
||||
@@ -528,12 +529,12 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<!-- Schedule -->
|
||||
<div class="playground-card reveal reveal-delay-3">
|
||||
<div class="playground-header">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M16 2v4M8 2v4M3 10h18"/></svg>
|
||||
<h3>Schedule</h3>
|
||||
</div>
|
||||
<div class="playground-body">
|
||||
<div class="playground-inputs">
|
||||
<input id="pgSchedule" type="date" />
|
||||
<input id="pgSchedule" type="date" aria-label="Schedule date" />
|
||||
<button class="btn btn-primary" onclick="pgRun('schedule')" style="padding:10px 20px">Get Schedule</button>
|
||||
</div>
|
||||
<div class="playground-output" id="pgScheduleOut"></div>
|
||||
@@ -551,11 +552,11 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<p class="cta-desc">Free, open source, no API key required. Deploy your own instance or use ours.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="https://anikototvapi.vercel.app/api" target="_blank" class="btn btn-primary">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
|
||||
View API
|
||||
</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" class="btn btn-ghost">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
Source Code
|
||||
</a>
|
||||
</div>
|
||||
@@ -569,7 +570,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<div class="footer-grid">
|
||||
<div class="footer-brand">
|
||||
<a href="/" class="logo">
|
||||
<svg viewBox="0 0 32 32" fill="none" width="24" height="24"><circle cx="16" cy="16" r="14" stroke="url(#g2)" stroke-width="2"/><path d="M10 20c0-4 3-7 6-7s6 3 6 7" stroke="url(#g2)" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="12" r="2" fill="var(--purple)"/><circle cx="20" cy="12" r="2" fill="var(--pink)"/><defs><linearGradient id="g2" x1="0" y1="0" x2="32" y2="32"><stop stop-color="var(--purple)"/><stop offset="1" stop-color="var(--pink)"/></linearGradient></defs></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 32 32" fill="none" width="24" height="24"><circle cx="16" cy="16" r="14" stroke="url(#g2)" stroke-width="2"/><path d="M10 20c0-4 3-7 6-7s6 3 6 7" stroke="url(#g2)" stroke-width="2" stroke-linecap="round"/><circle cx="12" cy="12" r="2" fill="var(--purple)"/><circle cx="20" cy="12" r="2" fill="var(--pink)"/><defs><linearGradient id="g2" x1="0" y1="0" x2="32" y2="32"><stop stop-color="var(--purple)"/><stop offset="1" stop-color="var(--pink)"/></linearGradient></defs></svg>
|
||||
<span class="logo-text"><span>AniKoto</span>API</span>
|
||||
</a>
|
||||
<p>Free REST API for anime data. Browse, search, and stream from AniKoto. Built with Node.js and deployed on Vercel.</p>
|
||||
@@ -584,10 +585,10 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>Resources</h4>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank">GitHub</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI#readme" target="_blank">Documentation</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI/blob/main/CHANGELOG.md" target="_blank">Changelog</a>
|
||||
<a href="/api" target="_blank">API Status</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI#readme" target="_blank" rel="noopener noreferrer">Documentation</a>
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI/blob/main/CHANGELOG.md" target="_blank" rel="noopener noreferrer">Changelog</a>
|
||||
<a href="/api" target="_blank" rel="noopener noreferrer">API Status</a>
|
||||
</div>
|
||||
<div class="footer-col">
|
||||
<h4>Legal</h4>
|
||||
@@ -599,7 +600,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
<span>© 2026 AniKotoAPI. Educational use only.</span>
|
||||
<div class="footer-social">
|
||||
<a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" aria-label="GitHub">
|
||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
<svg aria-hidden="true" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -685,7 +686,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
random: { url: '/random', label: 'Random' },
|
||||
schedule: { url: '/schedule', label: 'Schedule' }
|
||||
};
|
||||
function setEndpoint(key) {
|
||||
function setEndpoint(key, event) {
|
||||
const ep = endpoints[key];
|
||||
document.getElementById('consoleInput').value = `// ${ep.label}\nfetch("${BASE}${ep.url}")\n .then(r => r.json())\n .then(data => console.log(data));`;
|
||||
document.querySelectorAll('.console-btn').forEach(b => b.classList.remove('active'));
|
||||
@@ -702,6 +703,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
const data = await res.json();
|
||||
out.innerHTML = syntaxHighlight(JSON.stringify(data, null, 2));
|
||||
out.scrollTop = out.scrollHeight;
|
||||
function escapeHtml(s) { return s.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">"); }
|
||||
} catch (e) {
|
||||
out.innerHTML = `<span style="color:var(--red)">Error: ${e.message}</span>`;
|
||||
}
|
||||
@@ -762,7 +764,7 @@ fetch("https://anikototvapi.vercel.app/api")
|
||||
const btn = document.createElement('button');
|
||||
btn.className = `explorer-nav-btn ${i===0?'active':''}`;
|
||||
btn.dataset.id = ep.id;
|
||||
btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>${ep.title}`;
|
||||
btn.innerHTML = `<svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>${ep.title}`;
|
||||
btn.onclick = () => {
|
||||
navEl.querySelectorAll('.explorer-nav-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
"description": "Free REST API for anime data. Browse, search, stream episodes. 30+ endpoints, no API key required.",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"id": "/",
|
||||
"scope": "/",
|
||||
"lang": "en",
|
||||
"orientation": "any",
|
||||
"background_color": "#0A0A12",
|
||||
"theme_color": "#A855F7",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicon-origin.png",
|
||||
"sizes": "192x192",
|
||||
"sizes": "512x152",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
},
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 630" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="1200" y2="630" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#0A0A12"/>
|
||||
<stop offset="1" stop-color="#111120"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="grad" x1="0" y1="0" x2="600" y2="630" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A855F7"/>
|
||||
<stop offset="1" stop-color="#EC4899"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="orb1" cx="300" cy="200" r="200" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#A855F7" stop-opacity="0.3"/>
|
||||
<stop offset="1" stop-color="#A855F7" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="orb2" cx="900" cy="450" r="180" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#EC4899" stop-opacity="0.25"/>
|
||||
<stop offset="1" stop-color="#EC4899" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="1200" height="630" fill="url(#bg)"/>
|
||||
<circle cx="300" cy="200" r="250" fill="url(#orb1)"/>
|
||||
<circle cx="950" cy="420" r="220" fill="url(#orb2)"/>
|
||||
<circle cx="600" cy="315" r="160" stroke="url(#grad)" stroke-width="3" fill="none" opacity="0.3"/>
|
||||
<circle cx="600" cy="315" r="120" stroke="url(#grad)" stroke-width="2" fill="none" opacity="0.15"/>
|
||||
<text x="600" y="280" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="900" font-size="72" fill="url(#grad)">AniKotoAPI</text>
|
||||
<text x="600" y="340" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="500" font-size="24" fill="#94A3B8">Free Anime Data REST API</text>
|
||||
<text x="600" y="380" text-anchor="middle" font-family="monospace" font-weight="400" font-size="16" fill="#64748B">anikototvapi.vercel.app/api</text>
|
||||
<rect x="480" y="410" width="240" height="40" rx="20" fill="url(#grad)" opacity="0.9"/>
|
||||
<text x="600" y="436" text-anchor="middle" font-family="system-ui,sans-serif" font-weight="600" font-size="14" fill="#fff">No API Key Required</text>
|
||||
<circle cx="120" cy="540" r="4" fill="#A855F7" opacity="0.4"/>
|
||||
<circle cx="180" cy="560" r="3" fill="#EC4899" opacity="0.3"/>
|
||||
<circle cx="1050" cy="80" r="5" fill="#6366F1" opacity="0.3"/>
|
||||
<circle cx="1100" cy="120" r="3" fill="#A855F7" opacity="0.25"/>
|
||||
<circle cx="80" cy="100" r="3" fill="#EC4899" opacity="0.2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
+8
-3
@@ -2,10 +2,15 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Privacy Policy for AniKotoAPI. We do not collect, store, or share personal data." />
|
||||
<meta property="og:title" content="Privacy Policy — AniKotoAPI" />
|
||||
<meta property="og:description" content="Privacy Policy for AniKotoAPI. We do not collect, store, or share personal data." />
|
||||
<meta property="og:image" content="https://anikototvapi.vercel.app/share.png" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Privacy Policy — AniKotoAPI</title>
|
||||
<meta name="theme-color" content="#0A0A12" />
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛩</text></svg>" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
@@ -56,7 +61,7 @@
|
||||
|
||||
<h2>3. Third-Party Data Sources</h2>
|
||||
<p>The Service scrapes data from <code>anikototv.to</code> and related domains. This data (anime titles, descriptions, images, episode information) belongs to the respective content owners. We do not claim ownership of this data.</p>
|
||||
<p>If you are a content owner and wish to have your data removed from this API, please open an issue on our <a href="https://github.com/Shineii86/AniKotoAPI" target="_blank">GitHub repository</a>.</p>
|
||||
<p>If you are a content owner and wish to have your data removed from this API, please open an issue on our <a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" rel="noopener noreferrer">GitHub repository</a>.</p>
|
||||
|
||||
<h2>4. Data Storage</h2>
|
||||
<p>The Service uses a simple in-memory cache (5-minute TTL) to reduce repeated scraping. This cache is:</p>
|
||||
@@ -76,7 +81,7 @@
|
||||
<p>We may update this Privacy Policy from time to time. Changes will be posted on this page with an updated "Last updated" date.</p>
|
||||
|
||||
<h2>8. Contact</h2>
|
||||
<p>For questions about this Privacy Policy, open an issue at <a href="https://github.com/Shineii86/AniKotoAPI/issues" target="_blank">github.com/Shineii86/AniKotoAPI/issues</a>.</p>
|
||||
<p>For questions about this Privacy Policy, open an issue at <a href="https://github.com/Shineii86/AniKotoAPI/issues" target="_blank" rel="noopener noreferrer">github.com/Shineii86/AniKotoAPI/issues</a>.</p>
|
||||
</div>
|
||||
<footer>© 2026 AniKotoAPI · <a href="/" style="color:var(--purple)">Home</a> · <a href="/tos" style="color:var(--purple)">Terms</a></footer>
|
||||
</body>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# AniKotoAPI — https://anikototvapi.vercel.app
|
||||
User-agent: *
|
||||
Allow: /
|
||||
Allow: /api
|
||||
Disallow: /api
|
||||
|
||||
Sitemap: https://anikototvapi.vercel.app/sitemap.xml
|
||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
+9
-4
@@ -2,10 +2,15 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="description" content="Terms of Service for AniKotoAPI. Acceptable use, rate limits, liability, and DMCA policy." />
|
||||
<meta property="og:title" content="Terms of Service — AniKotoAPI" />
|
||||
<meta property="og:description" content="Terms of Service for AniKotoAPI. Acceptable use, rate limits, liability, and DMCA policy." />
|
||||
<meta property="og:image" content="https://anikototvapi.vercel.app/share.png" />
|
||||
<meta name="twitter:card" content="summary" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Terms of Service — AniKotoAPI</title>
|
||||
<meta name="theme-color" content="#0A0A12" />
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛩</text></svg>" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||
@@ -78,16 +83,16 @@
|
||||
<p>In no event shall the creators of AniKotoAPI be liable for any indirect, incidental, special, consequential, or punitive damages arising from your use of the Service.</p>
|
||||
|
||||
<h2>10. Intellectual Property</h2>
|
||||
<p>The source code of AniKotoAPI is open source and available on <a href="https://github.com/Shineii86/AniKotoAPI" target="_blank">GitHub</a> under the repository's license terms.</p>
|
||||
<p>The source code of AniKotoAPI is open source and available on <a href="https://github.com/Shineii86/AniKotoAPI" target="_blank" rel="noopener noreferrer">GitHub</a> under the repository's license terms.</p>
|
||||
|
||||
<h2>11. DMCA / Takedowns</h2>
|
||||
<p>If you believe your copyrighted content is being displayed through this API without authorization, please open an issue at <a href="https://github.com/Shineii86/AniKotoAPI/issues" target="_blank">github.com/Shineii86/AniKotoAPI/issues</a> and we will promptly remove it.</p>
|
||||
<p>If you believe your copyrighted content is being displayed through this API without authorization, please open an issue at <a href="https://github.com/Shineii86/AniKotoAPI/issues" target="_blank" rel="noopener noreferrer">github.com/Shineii86/AniKotoAPI/issues</a> and we will promptly remove it.</p>
|
||||
|
||||
<h2>12. Changes to Terms</h2>
|
||||
<p>We reserve the right to modify these Terms at any time. Changes will be posted on this page with an updated "Last updated" date.</p>
|
||||
|
||||
<h2>13. Contact</h2>
|
||||
<p>For questions about these Terms, open an issue at <a href="https://github.com/Shineii86/AniKotoAPI/issues" target="_blank">github.com/Shineii86/AniKotoAPI/issues</a>.</p>
|
||||
<p>For questions about these Terms, open an issue at <a href="https://github.com/Shineii86/AniKotoAPI/issues" target="_blank" rel="noopener noreferrer">github.com/Shineii86/AniKotoAPI/issues</a>.</p>
|
||||
</div>
|
||||
<footer>© 2026 AniKotoAPI · <a href="/" style="color:var(--purple)">Home</a> · <a href="/privacy" style="color:var(--purple)">Privacy</a></footer>
|
||||
</body>
|
||||
|
||||
@@ -36,6 +36,7 @@ const PORT = process.env.PORT || 4444;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const publicDir = path.join(process.cwd(), "public");
|
||||
const has404File = fs.existsSync(path.join(publicDir, "404.html"));
|
||||
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",");
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
@@ -45,7 +46,7 @@ const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(",");
|
||||
// NOTE: Single unified CORS middleware — handles all origin validation
|
||||
app.use((req, res, next) => {
|
||||
const origin = req.headers.origin;
|
||||
if (!allowedOrigins || allowedOrigins.includes("*") || (origin && allowedOrigins.includes(origin))) {
|
||||
if (allowedOrigins && (allowedOrigins.includes("*") || (origin && allowedOrigins.includes(origin)))) {
|
||||
res.setHeader("Access-Control-Allow-Origin", origin || "*");
|
||||
}
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET");
|
||||
@@ -116,12 +117,37 @@ const jsonError = (res, message = "Internal server error", status = 500) =>
|
||||
// API ROUTES
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ---- FEATURE: Rate limiting (100 requests per minute per IP) ----
|
||||
const requestCounts = new Map();
|
||||
app.use((req, res, next) => {
|
||||
const ip = req.ip || req.connection.remoteAddress;
|
||||
const now = Date.now();
|
||||
const windowMs = 60000;
|
||||
const maxRequests = 100;
|
||||
if (!requestCounts.has(ip)) {
|
||||
requestCounts.set(ip, []);
|
||||
}
|
||||
const timestamps = requestCounts.get(ip).filter(t => now - t < windowMs);
|
||||
requestCounts.set(ip, timestamps);
|
||||
if (timestamps.length >= maxRequests) {
|
||||
return res.status(429).json({ success: false, message: "Rate limit exceeded. Try again later." });
|
||||
}
|
||||
timestamps.push(now);
|
||||
next();
|
||||
});
|
||||
|
||||
createApiRoutes(app, jsonResponse, jsonError);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// 404 HANDLER
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
// ---- FEATURE: Global error handler ----
|
||||
app.use((err, req, res, next) => {
|
||||
console.error(err.stack);
|
||||
res.status(err.status || 500).json({ success: false, message: err.message || "Internal server error" });
|
||||
});
|
||||
|
||||
// ---- FEATURE: Catch-all 404 handler for undefined routes ----
|
||||
app.use((req, res) => {
|
||||
const filePath = path.join(publicDir, "404.html");
|
||||
|
||||
@@ -47,7 +47,7 @@ import { getCache, setCache } from "../helper/cache.helper.js";
|
||||
const getFilter = async (req, res, next) => {
|
||||
try {
|
||||
const params = req.query;
|
||||
const cacheKey = `filter_${JSON.stringify(params)}`;
|
||||
const cacheKey = `filter_${JSON.stringify(params, Object.keys(params).sort())}`;
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached) {
|
||||
return res.json({ success: true, results: cached });
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
|
||||
import { extractRandom } from "../extractors/random.extractor.js";
|
||||
import { getCache, setCache } from "../helper/cache.helper.js";
|
||||
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// CONTROLLER: RANDOM
|
||||
@@ -38,13 +38,8 @@ import { getCache, setCache } from "../helper/cache.helper.js";
|
||||
*/
|
||||
const getRandom = async (req, res, next) => {
|
||||
try {
|
||||
const cacheKey = "random";
|
||||
const cached = getCache(cacheKey);
|
||||
if (cached) {
|
||||
return res.json({ success: true, results: cached });
|
||||
}
|
||||
const data = await extractRandom();
|
||||
setCache(cacheKey, data);
|
||||
res.json({ success: true, results: data });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
*/
|
||||
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { URLS } from "../configs/dataUrl.js";
|
||||
import { countPages } from "../helper/countPages.helper.js";
|
||||
import { extractPages } from "../helper/extractPages.helper.js";
|
||||
|
||||
@@ -56,7 +56,7 @@ const extractEpisodeList = async (slugOrId) => {
|
||||
}
|
||||
|
||||
if (!animeId) {
|
||||
return { animeId: 0, slug: resolvedSlug, totalEpisodes: 0, episodes: [] };
|
||||
return { animeId: 0, slug: slugOrId, totalEpisodes: 0, episodes: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -104,7 +104,7 @@ const extractEpisodeList = async (slugOrId) => {
|
||||
|
||||
return {
|
||||
animeId,
|
||||
slug: resolvedSlug,
|
||||
slug: slugOrId,
|
||||
totalEpisodes: episodes.length,
|
||||
episodes
|
||||
};
|
||||
@@ -112,7 +112,7 @@ const extractEpisodeList = async (slugOrId) => {
|
||||
// NOTE: Gracefully return empty list if AJAX call fails (e.g. network issue)
|
||||
return {
|
||||
animeId,
|
||||
slug: resolvedSlug,
|
||||
slug: slugOrId,
|
||||
totalEpisodes: 0,
|
||||
episodes: []
|
||||
};
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
*/
|
||||
|
||||
import * as cheerio from "cheerio";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { URLS } from "../configs/dataUrl.js";
|
||||
import { GENRE_IDS, TYPE_IDS, STATUS_IDS, RATING_IDS, SORT_IDS } from "../configs/ids.config.js";
|
||||
|
||||
@@ -19,6 +19,7 @@ import * as cheerio from "cheerio";
|
||||
import axios from "axios";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { URLS } from "../configs/dataUrl.js";
|
||||
import { getCache, setCache } from "../helper/cache.helper.js";
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// HOME INFO EXTRACTOR
|
||||
@@ -36,6 +37,8 @@ import { URLS } from "../configs/dataUrl.js";
|
||||
* console.log(home.spotlights.length); // number of spotlight items
|
||||
*/
|
||||
const extractHomeInfo = async () => {
|
||||
const cached = getCache("home");
|
||||
if (cached) return cached;
|
||||
try {
|
||||
const { data } = await axios.get(URLS.home, { headers });
|
||||
const $ = cheerio.load(data);
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
*/
|
||||
|
||||
import * as cheerio from "cheerio";
|
||||
import { headers } from "../configs/header.config.js";
|
||||
import { URLS } from "../configs/dataUrl.js";
|
||||
import { countPages } from "../helper/countPages.helper.js";
|
||||
|
||||
@@ -42,7 +42,7 @@ const extractRandom = async () => {
|
||||
try {
|
||||
// NOTE: maxRedirects=5 ensures we follow the random redirect chain
|
||||
// NOTE: validateStatus allows any 2xx/3xx response without throwing
|
||||
const { data, request, response } = await axios.get(URLS.random, {
|
||||
const { data, request } = await axios.get(URLS.random, {
|
||||
headers,
|
||||
maxRedirects: 5,
|
||||
validateStatus: (status) => status < 400
|
||||
|
||||
@@ -49,7 +49,7 @@ const extractSeasons = async (animeId) => {
|
||||
return data || [];
|
||||
} catch (error) {
|
||||
// NOTE: Return empty array on error to maintain consistent API
|
||||
return [];
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -139,7 +139,11 @@ const extractServerList = async (episodeIds) => {
|
||||
*/
|
||||
const extractMapperServers = async (malId, slug, timestamp) => {
|
||||
try {
|
||||
const url = `https://mapper.nekostream.site/api/mal/${malId}/${slug}/${timestamp}`;
|
||||
// Validate and sanitize input to prevent SSRF
|
||||
if (!Number.isFinite(Number(malId))) throw new Error("Invalid malId");
|
||||
if (!/^[a-zA-Z0-9-]+$/.test(slug)) throw new Error("Invalid slug");
|
||||
if (!Number.isFinite(Number(timestamp))) throw new Error("Invalid timestamp");
|
||||
const url = `https://mapper.nekostream.site/api/mal/${encodeURIComponent(malId)}/${encodeURIComponent(slug)}/${encodeURIComponent(timestamp)}`;
|
||||
const { data } = await axios.get(url, { headers });
|
||||
|
||||
const servers = [];
|
||||
|
||||
@@ -49,7 +49,7 @@ const extractWatchOrder = async (animeId) => {
|
||||
return data || [];
|
||||
} catch (error) {
|
||||
// NOTE: Return empty array on error to maintain consistent API
|
||||
return [];
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@ const getCache = (key) => {
|
||||
* setCache("search-one-piece", animeResults);
|
||||
*/
|
||||
const setCache = (key, data) => {
|
||||
// Evict oldest entries if cache is full
|
||||
if (cache.size >= MAX_CACHE_SIZE) {
|
||||
const oldestKey = cache.keys().next().value;
|
||||
cache.delete(oldestKey);
|
||||
}
|
||||
cache.set(key, { data, timestamp: Date.now() });
|
||||
};
|
||||
|
||||
|
||||
+15
-2
@@ -1,5 +1,8 @@
|
||||
/*
|
||||
* ======= • ======= • ======= • ======= • =======• =======
|
||||
import { createRequire } from "module";
|
||||
const require = createRequire(import.meta.url);
|
||||
const { version } = require("../package.json");
|
||||
* AniKotoAPI — apiRoutes.js
|
||||
* Repository: https://github.com/Shineii86/AniKotoAPI
|
||||
*
|
||||
@@ -361,7 +364,7 @@ app.get("/api/health", (req, res) => {
|
||||
success: true,
|
||||
results: {
|
||||
status: "healthy",
|
||||
version: "1.8.0",
|
||||
version: version,
|
||||
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
||||
uptimeSeconds: uptime,
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -407,7 +410,7 @@ app.get("/api/openapi", (req, res) => {
|
||||
info: {
|
||||
title: "AniKotoAPI",
|
||||
description: "Free REST API for anime data from anikototv.to",
|
||||
version: "1.8.0",
|
||||
version: version,
|
||||
contact: {
|
||||
name: "Shinei Nouzen",
|
||||
url: "https://github.com/Shineii86/AniKotoAPI",
|
||||
@@ -437,6 +440,16 @@ app.get("/api/openapi", (req, res) => {
|
||||
"/health": { get: { summary: "Health check", tags: ["System"] } },
|
||||
"/stats": { get: { summary: "API statistics", tags: ["System"] } },
|
||||
"/openapi": { get: { summary: "OpenAPI spec", tags: ["System"] } },
|
||||
"/watch": { get: { summary: "Watch page data", tags: ["Streaming"] } },
|
||||
"/search/suggest": { get: { summary: "Search suggestions", tags: ["Search"] } },
|
||||
"/episodes-ajax/{id}": { get: { summary: "AJAX episode list", tags: ["Episodes"] } },
|
||||
"/mapper-servers": { get: { summary: "Mapper servers", tags: ["Streaming"] } },
|
||||
"/newly-added": { get: { summary: "Newly added", tags: ["Releases"] } },
|
||||
"/trending-sidebar": { get: { summary: "Trending sidebar", tags: ["Discovery"] } },
|
||||
"/seasons/{id}": { get: { summary: "Season data", tags: ["Anime"] } },
|
||||
"/watch-order/{id}": { get: { summary: "Watch order", tags: ["Anime"] } },
|
||||
"/az-list/{letter}": { get: { summary: "A-Z listing", tags: ["Discovery"] } },
|
||||
"/suggestions": { get: { summary: "Anime suggestions", tags: ["Search"] } },
|
||||
},
|
||||
tags: [
|
||||
{ name: "Home", description: "Homepage data" },
|
||||
|
||||
@@ -33,6 +33,18 @@ const tests = [
|
||||
{ name: "Health", url: "/health" },
|
||||
{ name: "Stats", url: "/stats" },
|
||||
{ name: "OpenAPI", url: "/openapi" },
|
||||
{ name: "Watch", url: "/watch?slug=road-of-naruto-ggjw8&ep=1" },
|
||||
{ name: "Search Suggest", url: "/search/suggest?keyword=naruto" },
|
||||
{ name: "Episodes Ajax", url: "/episodes-ajax/road-of-naruto-ggjw8" },
|
||||
{ name: "Newly Added", url: "/newly-added" },
|
||||
{ name: "Trending Sidebar", url: "/trending-sidebar" },
|
||||
{ name: "Seasons", url: "/seasons/road-of-naruto-ggjw8" },
|
||||
{ name: "Watch Order", url: "/watch-order/road-of-naruto-ggjw8" },
|
||||
{ name: "AZ List", url: "/az-list/a" },
|
||||
{ name: "Filter", url: "/filter?keyword=&genre[]=1" },
|
||||
{ name: "Episodes By ID", url: "/episodes/1" },
|
||||
{ name: "Servers", url: "/servers?ids=1" },
|
||||
{ name: "Mapper Servers", url: "/mapper-servers?malId=21&slug=one-piece-100×tamp=1234567890" },
|
||||
];
|
||||
|
||||
let passed = 0;
|
||||
@@ -41,6 +53,24 @@ let failed = 0;
|
||||
async function runTest(test) {
|
||||
try {
|
||||
const res = await fetch(`${BASE}${test.url}`);
|
||||
if (!res.ok) {
|
||||
console.log(`❌ ${test.name} - HTTP ${res.status}`);
|
||||
failed++;
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (data.success === true && data.results) {
|
||||
console.log(`✅ ${test.name}`);
|
||||
passed++;
|
||||
} else {
|
||||
console.log(`❌ ${test.name} - Invalid response format`);
|
||||
failed++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`❌ ${test.name} - ${error.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
const data = await res.json();
|
||||
|
||||
if (data.success === true && data.results) {
|
||||
|
||||
Reference in New Issue
Block a user