Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1433afdca1 | ||
|
|
b73d8d09bf | ||
|
|
99d7c15750 | ||
|
|
faa4888f77 | ||
|
|
a1cc22e84a | ||
|
|
d2193670f4 | ||
|
|
20041a8984 | ||
|
|
14d5c22fc7 | ||
|
|
240e90fe51 | ||
|
|
18a73c324e |
@@ -0,0 +1,5 @@
|
|||||||
|
# Origins you want to allow
|
||||||
|
ALLOWED_ORIGINS=https://site1.com,https://site2.com
|
||||||
|
|
||||||
|
# Port (optional, defaults to 3000)
|
||||||
|
PORT=3000
|
||||||
@@ -1,5 +1,33 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.0
|
||||||
|
- Full landing page upgrade to match AniKotoAPI style
|
||||||
|
- Added canvas particle effects with cyan-colored floating particles
|
||||||
|
- Added scroll-reveal animations (IntersectionObserver)
|
||||||
|
- Added animated stat counters that count up on scroll
|
||||||
|
- Added infinite scrolling marquee with 8 feature badges
|
||||||
|
- Added live interactive API console with 5 preset endpoint buttons
|
||||||
|
- Added sidebar endpoint explorer with 16 endpoints and multi-language code tabs (cURL, JavaScript, Python)
|
||||||
|
- Added 4 interactive playground cards (Search, Info, Episodes, Trending)
|
||||||
|
- Added custom scrollbar styling
|
||||||
|
- Added JetBrains Mono font for code blocks
|
||||||
|
- Added 4-column grid footer with API, Resources, Legal sections
|
||||||
|
- Added fixed header with scroll-aware blur/glass effect
|
||||||
|
- Added ambient orb gradient animations
|
||||||
|
- Added CTA section with gradient overlay
|
||||||
|
- Color scheme updated to match MiruroAPI brand (cyan #38bdf8 + indigo #818cf8)
|
||||||
|
- All branding updated: AniKotoAPI references → MiruroAPI
|
||||||
|
- OG/Twitter meta tags updated for mirurotvapi.vercel.app
|
||||||
|
- JSON-LD structured data updated with image, category, programmingModel
|
||||||
|
- Real Miruro SVG favicons used in header and footer
|
||||||
|
|
||||||
|
## v1.2.0
|
||||||
|
- Fixed critical response format bug: all endpoints now return {success: true, results: data}
|
||||||
|
- Root cause: createApiRoutes() had (app, jsonResponse, jsonError) but was called with (jsonResponse, jsonError), shifting all params
|
||||||
|
- Every jsonResponse() call was actually calling jsonError(), returning {success: false, message: data}
|
||||||
|
- Health endpoint worked because it used res.json() directly, bypassing the wrapper
|
||||||
|
- All 18 endpoints verified working: health, stats, search, suggestions, filter, trending, popular, upcoming, recent, spotlight, schedule, info, characters, relations, recommendations, episodes, watch
|
||||||
|
|
||||||
## v1.1.0
|
## v1.1.0
|
||||||
- Added Swagger UI interactive docs at /docs
|
- Added Swagger UI interactive docs at /docs
|
||||||
- Added OpenAPI 3.0 spec at /openapi.json
|
- Added OpenAPI 3.0 spec at /openapi.json
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Contributing to MiruroAPI
|
||||||
|
|
||||||
|
Thank you for your interest in contributing! Here's how you can help.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Clone your fork: `git clone https://github.com/YOUR_USERNAME/MiruroAPI.git`
|
||||||
|
3. Create a branch: `git checkout -b feature/amazing-feature`
|
||||||
|
4. Make your changes
|
||||||
|
5. Commit: `git commit -m 'feat: add amazing feature'`
|
||||||
|
6. Push: `git push origin feature/amazing-feature`
|
||||||
|
7. Open a Pull Request
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Server runs on http://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
MiruroAPI/
|
||||||
|
├── server.js # Express entry point
|
||||||
|
├── src/
|
||||||
|
│ ├── helpers/ # AniList, pipe, cache integrations
|
||||||
|
│ └── routes/ # Express routes
|
||||||
|
├── public/ # Static files (landing page, Swagger UI)
|
||||||
|
└── docs/ # API documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding a New Endpoint
|
||||||
|
|
||||||
|
1. Create helper in `src/helpers/yourHelper.js` if needed
|
||||||
|
2. Add route in `src/routes/apiRoutes.js`
|
||||||
|
3. Update `docs/endpoints.md` with documentation
|
||||||
|
4. Test with `curl http://localhost:3000/api/your-endpoint`
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
- Use CommonJS (`require`/`module.exports`)
|
||||||
|
- Add JSDoc comments for new functions
|
||||||
|
- Follow existing patterns in the codebase
|
||||||
|
- Keep helpers focused on one task
|
||||||
|
|
||||||
|
## Commit Messages
|
||||||
|
|
||||||
|
Use [Conventional Commits](https://www.conventionalcommits.org/):
|
||||||
|
|
||||||
|
- `feat:` new feature
|
||||||
|
- `fix:` bug fix
|
||||||
|
- `docs:` documentation changes
|
||||||
|
- `refactor:` code refactoring
|
||||||
|
- `test:` adding tests
|
||||||
|
- `chore:` maintenance
|
||||||
|
|
||||||
|
## Pull Request Guidelines
|
||||||
|
|
||||||
|
- Keep PRs focused on one feature or fix
|
||||||
|
- Update CHANGELOG.md with your changes
|
||||||
|
- Test your changes locally before submitting
|
||||||
|
- Follow existing code style
|
||||||
|
|
||||||
|
## Reporting Issues
|
||||||
|
|
||||||
|
- Use GitHub Issues for bug reports
|
||||||
|
- Include steps to reproduce
|
||||||
|
- Include expected vs actual behavior
|
||||||
|
- Include your Node.js version
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing, you agree that your contributions will be licensed under the MIT License.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Miruro Assets
|
||||||
|
|
||||||
|
Scraped from official Miruro domains for MiruroAPI project.
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
- **Official**: https://www.miruro.com (primary hub)
|
||||||
|
- **Streaming**: https://miruro.tv, https://miruro.to, https://miruro.bz, https://miruro.ru
|
||||||
|
- **Status**: https://status.miruro.com/status/miruro
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
assets/
|
||||||
|
├── favicons/ # Favicons and app icons
|
||||||
|
│ ├── icon-dark-1024x1024.svg # Dark mode SVG favicon
|
||||||
|
│ ├── icon-light-1024x1024.svg # Light mode SVG favicon
|
||||||
|
│ ├── icon-512x512.png # Main app icon (PNG)
|
||||||
|
│ ├── favicon.ico # Classic ICO favicon
|
||||||
|
│ ├── apple-touch-icon-180x180.png # iOS home screen icon
|
||||||
|
│ └── maskable-icon-512x512.png # Android adaptive icon / OG image
|
||||||
|
├── logos/
|
||||||
|
│ └── status-logo.png # Status page logo
|
||||||
|
├── fonts/ # Inter + FontAwesome subset
|
||||||
|
│ ├── inter.woff2
|
||||||
|
│ ├── fa-brands-400.woff2
|
||||||
|
│ ├── fa-solid-900.woff2
|
||||||
|
│ └── fa-minimal.css
|
||||||
|
└── media/ # Testimonial avatars from homepage
|
||||||
|
└── *.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Social Links
|
||||||
|
|
||||||
|
- Twitter: https://twitter.com/miruro_official
|
||||||
|
- Discord: https://discord.com/invite/pjsdJp96mY
|
||||||
|
- Reddit: https://reddit.com/r/miruro
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- All streaming domains (tv, to, bz, ru) use identical assets
|
||||||
|
- SVG favicons support dark/light mode via `prefers-color-scheme`
|
||||||
|
- OG image is `maskable-icon-512x512.png`
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="1024" height="1024" viewBox="0 0 270.93333 270.93332" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-41.580053,-91.524707)">
|
||||||
|
<path style="fill:#ffffff;fill-opacity:1;stroke-width:1.70387"
|
||||||
|
d="m 247.27543,314.70152 c -6.10955,-6.70519 -16.14364,-12.9409 -13.93299,-23.42798 2.12389,-20.22967 4.00106,-40.51893 3.43613,-60.89302 0.32828,-23.84505 -0.7151,-47.80644 -4.67234,-71.32308 -21.29031,23.99166 -31.12328,55.278 -42.21767,84.73199 -3.89541,12.27935 -21.71766,7.83903 -21.86694,-4.43985 -6.30241,-22.41752 -15.57712,-44.12149 -29.46365,-62.93722 -3.96932,-3.43831 -12.49125,-22.4167 -14.54817,-9.93684 -7.96323,36.33701 -8.31833,73.85796 -6.24645,110.84721 2.19408,10.98054 -3.58307,20.65309 -14.39585,23.75435 -7.691633,1.63664 -18.686185,16.40367 -24.689738,6.4484 -5.549778,-6.39186 -6.386914,-14.77824 3.524397,-16.80023 3.650034,-4.06414 14.883155,-3.99688 13.428515,-10.68708 -0.363081,-6.16925 -0.502499,-12.34844 -0.722494,-18.52376 -6.011978,0.12421 -20.194572,9.14755 -21.048961,0.29803 4.558972,-3.34862 14.481259,-4.72681 17.663382,-6.27314 -5.30158,-0.36703 -12.065997,2.00733 -16.203654,-1.67557 2.654494,-5.22286 11.415092,-2.80527 16.759698,-3.75272 5.21548,0.98551 2.118507,-6.2941 3.203468,-9.41871 1.362186,-31.92446 2.830394,-64.401 12.138507,-95.17623 1.6606,-12.81687 17.56784,-17.5421 25.47531,-6.99678 20.47568,20.4649 38.19519,44.34874 47.58582,71.98892 4.61856,-4.03449 7.71667,-17.72071 12.32719,-24.95391 9.91397,-19.20957 21.02511,-38.64633 37.33735,-53.16525 7.54853,-8.78842 20.64347,-3.59999 20.62749,7.78422 8.57544,35.65229 6.90072,72.55248 7.54692,108.94394 6.47271,0.53048 13.50356,-0.74591 19.53337,1.55892 -0.40011,6.92584 -15.88203,1.54318 -17.9692,4.29818 5.80571,3.1568 22.13502,1.93763 20.18934,10.35691 -7.79577,3.09455 -24.35445,-11.17611 -22.65057,3.17853 1.34495,11.08 -7.66567,25.29061 3.96573,32.80224 7.93679,6.06024 16.23111,15.77249 3.76586,22.34018 -6.35101,7.76687 -12.98193,-5.80286 -17.8798,-8.95065 z m -36.07374,-34.0964 c -9.58671,-2.83824 -11.53728,-17.20921 -2.66126,-22.20068 9.07411,-6.48726 22.3771,4.08888 18.20146,14.33921 -1.92914,6.54286 -9.18288,9.79938 -15.5402,7.86147 z m -75.26977,-2.55526 c -11.469,-1.7362 -13.22099,-19.59021 -2.32646,-23.45757 10.02949,-4.7738 21.46818,7.15397 15.81668,16.84718 -2.45128,4.77109 -8.13415,7.97894 -13.49022,6.61039 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="1024" height="1024" viewBox="0 0 270.93333 270.93332" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-41.580053,-91.524707)">
|
||||||
|
<path style="fill:#000000;fill-opacity:1;stroke-width:1.70387"
|
||||||
|
d="m 247.27543,314.70152 c -6.10955,-6.70519 -16.14364,-12.9409 -13.93299,-23.42798 2.12389,-20.22967 4.00106,-40.51893 3.43613,-60.89302 0.32828,-23.84505 -0.7151,-47.80644 -4.67234,-71.32308 -21.29031,23.99166 -31.12328,55.278 -42.21767,84.73199 -3.89541,12.27935 -21.71766,7.83903 -21.86694,-4.43985 -6.30241,-22.41752 -15.57712,-44.12149 -29.46365,-62.93722 -3.96932,-3.43831 -12.49125,-22.4167 -14.54817,-9.93684 -7.96323,36.33701 -8.31833,73.85796 -6.24645,110.84721 2.19408,10.98054 -3.58307,20.65309 -14.39585,23.75435 -7.691633,1.63664 -18.686185,16.40367 -24.689738,6.4484 -5.549778,-6.39186 -6.386914,-14.77824 3.524397,-16.80023 3.650034,-4.06414 14.883155,-3.99688 13.428515,-10.68708 -0.363081,-6.16925 -0.502499,-12.34844 -0.722494,-18.52376 -6.011978,0.12421 -20.194572,9.14755 -21.048961,0.29803 4.558972,-3.34862 14.481259,-4.72681 17.663382,-6.27314 -5.30158,-0.36703 -12.065997,2.00733 -16.203654,-1.67557 2.654494,-5.22286 11.415092,-2.80527 16.759698,-3.75272 5.21548,0.98551 2.118507,-6.2941 3.203468,-9.41871 1.362186,-31.92446 2.830394,-64.401 12.138507,-95.17623 1.6606,-12.81687 17.56784,-17.5421 25.47531,-6.99678 20.47568,20.4649 38.19519,44.34874 47.58582,71.98892 4.61856,-4.03449 7.71667,-17.72071 12.32719,-24.95391 9.91397,-19.20957 21.02511,-38.64633 37.33735,-53.16525 7.54853,-8.78842 20.64347,-3.59999 20.62749,7.78422 8.57544,35.65229 6.90072,72.55248 7.54692,108.94394 6.47271,0.53048 13.50356,-0.74591 19.53337,1.55892 -0.40011,6.92584 -15.88203,1.54318 -17.9692,4.29818 5.80571,3.1568 22.13502,1.93763 20.18934,10.35691 -7.79577,3.09455 -24.35445,-11.17611 -22.65057,3.17853 1.34495,11.08 -7.66567,25.29061 3.96573,32.80224 7.93679,6.06024 16.23111,15.77249 3.76586,22.34018 -6.35101,7.76687 -12.98193,-5.80286 -17.8798,-8.95065 z m -36.07374,-34.0964 c -9.58671,-2.83824 -11.53728,-17.20921 -2.66126,-22.20068 9.07411,-6.48726 22.3771,4.08888 18.20146,14.33921 -1.92914,6.54286 -9.18288,9.79938 -15.5402,7.86147 z m -75.26977,-2.55526 c -11.469,-1.7362 -13.22099,-19.59021 -2.32646,-23.45757 10.02949,-4.7738 21.46818,7.15397 15.81668,16.84718 -2.45128,4.77109 -8.13415,7.97894 -13.49022,6.61039 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
/*!
|
||||||
|
* Font Awesome Free 6.4.0 - Minimal subset for miruro hub
|
||||||
|
* License: https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
|
||||||
|
*/
|
||||||
|
@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(fa-solid-900.woff2) format("woff2")}
|
||||||
|
@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(fa-brands-400.woff2) format("woff2")}
|
||||||
|
.fa,.fab,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:inline-block;font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}
|
||||||
|
.fas{font-family:"Font Awesome 6 Free";font-weight:900}
|
||||||
|
.fab{font-family:"Font Awesome 6 Brands";font-weight:400}
|
||||||
|
.fa-arrow-right:before{content:"\f08b"}
|
||||||
|
.fa-arrow-up:before{content:"\f176"}
|
||||||
|
.fa-bars:before{content:"\f0c9"}
|
||||||
|
.fa-chart-bar:before{content:"\f080"}
|
||||||
|
.fa-check-circle:before{content:"\f058"}
|
||||||
|
.fa-globe:before{content:"\f57c"}
|
||||||
|
.fa-language:before{content:"\f1ab"}
|
||||||
|
.fa-play-circle:before{content:"\f144"}
|
||||||
|
.fa-server:before{content:"\f233"}
|
||||||
|
.fa-shield-alt:before{content:"\f3ed"}
|
||||||
|
.fa-star:before{content:"\f699"}
|
||||||
|
.fa-times:before{content:"\f00d"}
|
||||||
|
.fa-users:before{content:"\e594"}
|
||||||
|
.fa-discord:before{content:"\f392"}
|
||||||
|
.fa-reddit:before{content:"\f1a2"}
|
||||||
|
.fa-twitter:before{content:"\f081"}
|
||||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,190 @@
|
|||||||
|
# Architecture
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
MiruroAPI/
|
||||||
|
├── server.js # Express entry point, port 3000
|
||||||
|
├── package.json # name: "miruro-api"
|
||||||
|
├── vercel.json # Routes /api/* and /* to server.js
|
||||||
|
├── Dockerfile # Docker support (node:20-alpine)
|
||||||
|
├── .dockerignore # Docker ignore file
|
||||||
|
│
|
||||||
|
├── public/ # Static files served from process.cwd()
|
||||||
|
│ ├── index.html # Premium landing page (SVG icons, live console)
|
||||||
|
│ ├── docs.html # Swagger UI interactive documentation
|
||||||
|
│ ├── openapi.json # OpenAPI 3.0 spec
|
||||||
|
│ ├── 404.html # Glitch animation error page
|
||||||
|
│ ├── manifest.json # PWA manifest (theme: #A855F7)
|
||||||
|
│ ├── robots.txt # Crawler directives
|
||||||
|
│ ├── sitemap.xml # 4 pages (/, /tos, /privacy, /api)
|
||||||
|
│ ├── icon-dark.svg # Miruro dark mode SVG icon
|
||||||
|
│ ├── icon-light.svg # Miruro light mode SVG icon
|
||||||
|
│ ├── icon-512x512.png # Miruro app icon
|
||||||
|
│ ├── favicon.ico # Classic favicon
|
||||||
|
│ ├── apple-touch-icon-180x180.png # iOS home screen icon
|
||||||
|
│ ├── og-image.png # OG/Twitter share image
|
||||||
|
│ ├── privacy.html # Privacy policy (served at /privacy)
|
||||||
|
│ └── tos.html # Terms of service (served at /tos)
|
||||||
|
│
|
||||||
|
├── assets/ # Scraped Miruro assets
|
||||||
|
│ ├── favicons/ # All favicon variants
|
||||||
|
│ ├── logos/ # Status page logo
|
||||||
|
│ ├── fonts/ # Inter + FontAwesome
|
||||||
|
│ └── media/ # Testimonial avatars
|
||||||
|
│
|
||||||
|
├── docs/ # API documentation
|
||||||
|
│ ├── index.md # Overview, quick start, features
|
||||||
|
│ ├── endpoints.md # Full API reference (18 endpoints)
|
||||||
|
│ ├── streaming.md # Streaming flow guide (3-step)
|
||||||
|
│ ├── examples.md # cURL, JavaScript, Python
|
||||||
|
│ └── architecture.md # This file
|
||||||
|
│
|
||||||
|
├── src/
|
||||||
|
│ ├── helpers/
|
||||||
|
│ │ ├── anilist.js # AniList GraphQL integration
|
||||||
|
│ │ ├── pipe.js # Miruro pipe integration
|
||||||
|
│ │ └── cache.js # In-memory Map cache with TTL
|
||||||
|
│ │
|
||||||
|
│ └── routes/
|
||||||
|
│ └── apiRoutes.js # All route definitions
|
||||||
|
│
|
||||||
|
├── test.js # Integration test suite
|
||||||
|
├── CHANGELOG.md # Version history
|
||||||
|
├── LICENSE # MIT License
|
||||||
|
└── README.md # Full API documentation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
| Component | Technology |
|
||||||
|
|-----------|------------|
|
||||||
|
| Runtime | Node.js |
|
||||||
|
| Framework | Express.js |
|
||||||
|
| Metadata | AniList GraphQL (`graphql.anilist.co`) |
|
||||||
|
| Streaming | Miruro pipe (`miruro.tv/api/secure/pipe`) |
|
||||||
|
| Deployment | Vercel (Serverless) |
|
||||||
|
| Caching | In-memory Map with 1-5 min TTL |
|
||||||
|
| Static Files | Express static middleware |
|
||||||
|
|
||||||
|
## Request Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Client Request
|
||||||
|
↓
|
||||||
|
Vercel Routes (/api/* → server.js)
|
||||||
|
↓
|
||||||
|
Express Router (apiRoutes.js)
|
||||||
|
↓
|
||||||
|
Helper (e.g., anilist.js or pipe.js)
|
||||||
|
↓
|
||||||
|
HTTP Request to AniList GraphQL / Miruro pipe
|
||||||
|
↓
|
||||||
|
Response decoded (base64url + gzip for pipe)
|
||||||
|
↓
|
||||||
|
Returns structured JSON
|
||||||
|
↓
|
||||||
|
Client Response
|
||||||
|
```
|
||||||
|
|
||||||
|
## Streaming Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
/api/episodes/:anilistId
|
||||||
|
↓
|
||||||
|
Returns: providers with sub/dub episode lists
|
||||||
|
↓
|
||||||
|
/api/watch/:provider/:anilistId/:category/:slug
|
||||||
|
↓
|
||||||
|
Returns: streams[] with M3U8 URLs, quality, codec, fansub
|
||||||
|
↓
|
||||||
|
Play M3U8 in HLS player (hls.js, video.js, native)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Caching Strategy
|
||||||
|
|
||||||
|
- **Type:** In-memory Map
|
||||||
|
- **TTL:** 1-5 minutes depending on endpoint
|
||||||
|
- **Max Size:** 100 entries with FIFO eviction
|
||||||
|
- **Key:** Full request URL
|
||||||
|
- **Behavior:** First request fetches from source, subsequent requests served from cache
|
||||||
|
- **Eviction:** Automatic when TTL expires or max size reached
|
||||||
|
|
||||||
|
## Data Sources
|
||||||
|
|
||||||
|
### AniList GraphQL
|
||||||
|
|
||||||
|
- **Endpoint:** `https://graphql.anilist.co`
|
||||||
|
- **Data:** Search, suggestions, info, characters, relations, recommendations, filter, schedule
|
||||||
|
- **Format:** Standard GraphQL queries
|
||||||
|
|
||||||
|
### Miruro Pipe
|
||||||
|
|
||||||
|
- **Endpoint:** `https://miruro.tv/api/secure/pipe`
|
||||||
|
- **Data:** Episodes, streaming sources (M3U8 URLs)
|
||||||
|
- **Encoding:** Base64url + gzip compressed request/response
|
||||||
|
- **Providers:** kiwi, pewe, bee, bonk, bun, ally, nun, twin, cog, moo, hop, telli
|
||||||
|
|
||||||
|
## Vercel Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [
|
||||||
|
{
|
||||||
|
"src": "server.js",
|
||||||
|
"use": "@vercel/node"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"src": "/api/(.*)",
|
||||||
|
"dest": "server.js"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/(.*)",
|
||||||
|
"dest": "server.js"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `/api/*` routes to Express for API handling
|
||||||
|
- `/*` routes to Express for static file serving
|
||||||
|
- Static files served from `process.cwd()` (Vercel serverless compatible)
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `PORT` | `3000` | Server port (Express mode only) |
|
||||||
|
| `ALLOWED_ORIGINS` | `*` | Comma-separated allowed origins |
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
All API responses follow this structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Error responses:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Error description"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
| Package | Version | Purpose |
|
||||||
|
|---------|---------|---------|
|
||||||
|
| express | ^4.21.0 | Web framework |
|
||||||
|
| axios | ^1.8.0 | HTTP requests |
|
||||||
|
| cors | ^2.8.5 | CORS headers |
|
||||||
|
| dotenv | ^16.4.0 | Environment variables |
|
||||||
@@ -0,0 +1,375 @@
|
|||||||
|
# API Endpoints Reference
|
||||||
|
|
||||||
|
Complete documentation for all 18 MiruroAPI endpoints.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Health Check](#health-check)
|
||||||
|
- [Stats](#stats)
|
||||||
|
- [Search](#search)
|
||||||
|
- [Suggestions](#suggestions)
|
||||||
|
- [Filter](#filter)
|
||||||
|
- [Trending](#trending)
|
||||||
|
- [Popular](#popular)
|
||||||
|
- [Upcoming](#upcoming)
|
||||||
|
- [Recent](#recent)
|
||||||
|
- [Spotlight](#spotlight)
|
||||||
|
- [Schedule](#schedule)
|
||||||
|
- [Anime Info](#anime-info)
|
||||||
|
- [Characters](#characters)
|
||||||
|
- [Relations](#relations)
|
||||||
|
- [Recommendations](#recommendations)
|
||||||
|
- [Episodes](#episodes)
|
||||||
|
- [Watch (Streaming)](#watch-streaming)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Health Check
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/health
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns API health status, version, uptime, memory usage, and available providers.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"status": "healthy",
|
||||||
|
"version": "1.2.0",
|
||||||
|
"uptime": "0h 0m 34s",
|
||||||
|
"node": "v24.14.1",
|
||||||
|
"memory": { "used": "13MB", "total": "15MB" },
|
||||||
|
"endpoints": 16,
|
||||||
|
"providers": ["kiwi","pewe","bee","bonk","bun","ally","nun","twin","cog","moo","hop","telli"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stats
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/stats
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns request statistics, cache info, and memory usage.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Search
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/search
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `query` | string | Yes | — | Search keyword |
|
||||||
|
| `page` | number | No | 1 | Page number |
|
||||||
|
| `per_page` | number | No | 20 | Results per page |
|
||||||
|
|
||||||
|
**Example:** `GET /api/search?query=naruto&per_page=2`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"page": 1,
|
||||||
|
"perPage": 2,
|
||||||
|
"total": 5000,
|
||||||
|
"hasNextPage": true,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"title": { "romaji": "NARUTO", "english": "Naruto", "native": "NARUTO -ナルト-" },
|
||||||
|
"coverImage": { "large": "https://s4.anilist.co/file/..." },
|
||||||
|
"format": "TV",
|
||||||
|
"episodes": 220,
|
||||||
|
"status": "FINISHED",
|
||||||
|
"averageScore": 80,
|
||||||
|
"genres": ["Action","Adventure","Comedy","Drama","Fantasy","Supernatural"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Suggestions
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/suggestions
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `query` | string | Yes | Search keyword |
|
||||||
|
|
||||||
|
**Example:** `GET /api/suggestions?query=naruto`
|
||||||
|
|
||||||
|
Returns fast autocomplete suggestions with title, poster, format, status, year, and episode count.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Filter
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/filter
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `genre` | string | No | — | Genre name (e.g. "Action") |
|
||||||
|
| `tag` | string | No | — | Tag name |
|
||||||
|
| `year` | number | No | — | Release year |
|
||||||
|
| `season` | string | No | — | FALL, WINTER, SPRING, SUMMER |
|
||||||
|
| `format` | string | No | — | TV, MOVIE, OVA, ONA, SPECIAL, MUSIC |
|
||||||
|
| `status` | string | No | — | RELEASING, FINISHED, NOT_YET_RELEASED, CANCELLED |
|
||||||
|
| `sort` | string | No | POPULARITY_DESC | Sort order |
|
||||||
|
| `page` | number | No | 1 | Page number |
|
||||||
|
| `per_page` | number | No | 20 | Results per page |
|
||||||
|
|
||||||
|
**Example:** `GET /api/filter?genre=Action&year=2024&season=WINTER&per_page=3`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Trending
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/trending
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `per_page` | number | No | 20 | Results per page |
|
||||||
|
|
||||||
|
**Example:** `GET /api/trending?per_page=5`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Popular
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/popular
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `per_page` | number | No | 20 | Results per page |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Upcoming
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/upcoming
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `per_page` | number | No | 20 | Results per page |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recent
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/recent
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `per_page` | number | No | 20 | Results per page |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spotlight
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/spotlight
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns featured/spotlight anime for the hero carousel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Schedule
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/schedule
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Default | Description |
|
||||||
|
|-------|------|----------|---------|-------------|
|
||||||
|
| `date` | string | No | today | Date in YYYY-MM-DD format |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Anime Info
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/info/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `id` | number | Yes | AniList anime ID |
|
||||||
|
|
||||||
|
**Example:** `GET /api/info/20`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"id": 20,
|
||||||
|
"idMal": 20,
|
||||||
|
"title": { "romaji": "NARUTO", "english": "Naruto", "native": "NARUTO -ナルト-" },
|
||||||
|
"description": "Naruto Uzumaki, a hyperactive and knuckle-headed ninja...",
|
||||||
|
"coverImage": { "large": "https://s4.anilist.co/file/..." },
|
||||||
|
"format": "TV",
|
||||||
|
"episodes": 220,
|
||||||
|
"duration": 23,
|
||||||
|
"status": "FINISHED",
|
||||||
|
"averageScore": 80,
|
||||||
|
"genres": ["Action","Adventure","Comedy","Drama","Fantasy","Supernatural"],
|
||||||
|
"studios": [{ "name": "Studio Pierrot", "isAnimationStudio": true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Characters
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/anime/:id/characters
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `id` | number | Yes | AniList anime ID |
|
||||||
|
|
||||||
|
**Example:** `GET /api/anime/20/characters`
|
||||||
|
|
||||||
|
Returns characters with voice actors.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Relations
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/anime/:id/relations
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `id` | number | Yes | AniList anime ID |
|
||||||
|
|
||||||
|
Returns related anime (sequels, prequels, side stories, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/anime/:id/recommendations
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `id` | number | Yes | AniList anime ID |
|
||||||
|
|
||||||
|
Returns recommended anime based on the given ID.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Episodes
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/episodes/:id
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `id` | number | Yes | AniList anime ID |
|
||||||
|
|
||||||
|
**Example:** `GET /api/episodes/20`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"providers": {
|
||||||
|
"kiwi": {
|
||||||
|
"meta": { "id": "1571", "title": "Naruto", "type": "TV" },
|
||||||
|
"episodes": {
|
||||||
|
"sub": [
|
||||||
|
{
|
||||||
|
"id": "watch/kiwi/20/sub/animepahe-1",
|
||||||
|
"number": 1,
|
||||||
|
"title": "Enter: Naruto Uzumaki!",
|
||||||
|
"airDate": "2002-10-03",
|
||||||
|
"audio": "sub",
|
||||||
|
"filler": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Watch (Streaming)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/watch/:provider/:anilistId/:category/:slug
|
||||||
|
```
|
||||||
|
|
||||||
|
| Param | Type | Required | Description |
|
||||||
|
|-------|------|----------|-------------|
|
||||||
|
| `provider` | string | Yes | Provider name (kiwi, pewe, etc.) |
|
||||||
|
| `anilistId` | number | Yes | AniList anime ID |
|
||||||
|
| `category` | string | Yes | sub or dub |
|
||||||
|
| `slug` | string | Yes | Episode slug from episodes response |
|
||||||
|
|
||||||
|
**Example:** `GET /api/watch/kiwi/20/sub/animepahe-1`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"url": "https://vault-01.uwucdn.top/stream/.../uwu.m3u8",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "360p",
|
||||||
|
"resolution": { "width": 640, "height": 360 },
|
||||||
|
"codec": "h264",
|
||||||
|
"audio": "sub",
|
||||||
|
"fansub": "df68",
|
||||||
|
"referer": "https://kwik.cx/e/..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"download": "https://pahe.win/LJmbA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
# Code Examples
|
||||||
|
|
||||||
|
All examples are tested and working with the live API.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## cURL
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/search?query=naruto"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Suggestions
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/suggestions?query=naruto"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anime Info
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/info/20"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Episodes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/episodes/20"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Characters
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/anime/20/characters"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream URL
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/watch/kiwi/20/sub/animepahe-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/filter?genre=Action&per_page=3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trending
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/trending?per_page=5"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Popular
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/popular?per_page=5"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schedule
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/schedule?date=2026-06-09"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/health"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## JavaScript (Browser)
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function searchAnime(query) {
|
||||||
|
const res = await fetch(`https://mirurotvapi.vercel.app/api/search?query=${encodeURIComponent(query)}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return data.results.results.map(anime => ({
|
||||||
|
id: anime.id,
|
||||||
|
title: anime.title.english || anime.title.romaji,
|
||||||
|
format: anime.format,
|
||||||
|
episodes: anime.episodes,
|
||||||
|
score: anime.averageScore
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAnime('naruto').then(results => {
|
||||||
|
results.forEach(a => console.log(`${a.title} (${a.format}) — Score: ${a.score}`));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anime Info
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function getAnimeInfo(anilistId) {
|
||||||
|
const res = await fetch(`https://mirurotvapi.vercel.app/api/info/${anilistId}`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const info = data.results;
|
||||||
|
console.log(`Title: ${info.title.english || info.title.romaji}`);
|
||||||
|
console.log(`Format: ${info.format}`);
|
||||||
|
console.log(`Status: ${info.status}`);
|
||||||
|
console.log(`Episodes: ${info.episodes || 'Unknown'}`);
|
||||||
|
console.log(`Score: ${info.averageScore}`);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAnimeInfo(20);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Streaming Flow
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function getStreamUrl(anilistId) {
|
||||||
|
// Step 1: Get episodes
|
||||||
|
const episodesRes = await fetch(`https://mirurotvapi.vercel.app/api/episodes/${anilistId}`);
|
||||||
|
const episodesData = await episodesRes.json();
|
||||||
|
const providers = episodesData.results.providers;
|
||||||
|
|
||||||
|
const provider = Object.keys(providers)[0];
|
||||||
|
const episodes = providers[provider].episodes.sub;
|
||||||
|
|
||||||
|
if (!episodes || episodes.length === 0) {
|
||||||
|
throw new Error('No episodes found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const episodeId = episodes[0].id;
|
||||||
|
const parts = episodeId.split('/');
|
||||||
|
const category = parts[3];
|
||||||
|
const slug = parts[4];
|
||||||
|
|
||||||
|
// Step 2: Get stream URLs
|
||||||
|
const streamRes = await fetch(`https://mirurotvapi.vercel.app/api/watch/${provider}/${anilistId}/${category}/${slug}`);
|
||||||
|
const streamData = await streamRes.json();
|
||||||
|
|
||||||
|
return streamData.results.streams[0].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
getStreamUrl(20)
|
||||||
|
.then(url => {
|
||||||
|
console.log('Stream URL:', url);
|
||||||
|
// Use with HLS.js player
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Error:', err.message));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Characters
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function getCharacters(anilistId) {
|
||||||
|
const res = await fetch(`https://mirurotvapi.vercel.app/api/anime/${anilistId}/characters`);
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
return data.results.edges.map(edge => ({
|
||||||
|
name: edge.node.name.full,
|
||||||
|
role: edge.role,
|
||||||
|
voiceActor: edge.voiceActors[0]?.name.full || 'N/A'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
getCharacters(20).then(chars => {
|
||||||
|
chars.forEach(c => console.log(`${c.name} (${c.role}) — VA: ${c.voiceActor}`));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Node.js (with axios)
|
||||||
|
|
||||||
|
### Search
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function searchAnime(query) {
|
||||||
|
const { data } = await axios.get('https://mirurotvapi.vercel.app/api/search', {
|
||||||
|
params: { query }
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.results.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchAnime('naruto').then(results => {
|
||||||
|
results.forEach(a => console.log(`${a.title.english || a.title.romaji}`));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Full Streaming Flow
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function getStreamUrl(anilistId) {
|
||||||
|
const BASE = 'https://mirurotvapi.vercel.app/api';
|
||||||
|
|
||||||
|
// Step 1: Get episodes
|
||||||
|
const { data: episodesData } = await axios.get(`${BASE}/episodes/${anilistId}`);
|
||||||
|
const providers = episodesData.results.providers;
|
||||||
|
|
||||||
|
const provider = Object.keys(providers)[0];
|
||||||
|
const episodes = providers[provider].episodes.sub;
|
||||||
|
|
||||||
|
if (!episodes || episodes.length === 0) {
|
||||||
|
throw new Error('No episodes found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const episodeId = episodes[0].id;
|
||||||
|
const parts = episodeId.split('/');
|
||||||
|
const category = parts[3];
|
||||||
|
const slug = parts[4];
|
||||||
|
|
||||||
|
// Step 2: Get stream URLs
|
||||||
|
const { data: streamData } = await axios.get(`${BASE}/watch/${provider}/${anilistId}/${category}/${slug}`);
|
||||||
|
|
||||||
|
return streamData.results.streams[0].url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
getStreamUrl(20)
|
||||||
|
.then(url => console.log('Stream URL:', url))
|
||||||
|
.catch(err => console.error('Error:', err.message));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Filter by Genre
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function filterAnime(genre, perPage = 5) {
|
||||||
|
const { data } = await axios.get('https://mirurotvapi.vercel.app/api/filter', {
|
||||||
|
params: { genre, per_page: perPage }
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.results.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
filterAnime('Action', 3).then(anime => {
|
||||||
|
anime.forEach(a => console.log(`${a.title.english || a.title.romaji} (Score: ${a.averageScore})`));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Schedule
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
async function getSchedule(date) {
|
||||||
|
const { data } = await axios.get('https://mirurotvapi.vercel.app/api/schedule', {
|
||||||
|
params: { date }
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Scheduled anime:', data.results.length);
|
||||||
|
return data.results;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSchedule('2026-06-09');
|
||||||
|
```
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# MiruroAPI Documentation
|
||||||
|
|
||||||
|
> Free REST API for anime streaming data. 18 endpoints, 12 providers, M3U8 URLs. No API key required.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MiruroAPI is a free, open-source REST API that provides anime data by aggregating from AniList GraphQL and Miruro streaming providers. It's built with Node.js and Express, and deployed on Vercel.
|
||||||
|
|
||||||
|
**Base URL:** `https://mirurotvapi.vercel.app/api`
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Search for anime
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/search?query=naruto"
|
||||||
|
|
||||||
|
# Get anime info
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/info/20"
|
||||||
|
|
||||||
|
# Get episodes
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/episodes/20"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Live Response — Search:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"page": 1,
|
||||||
|
"perPage": 20,
|
||||||
|
"total": 5000,
|
||||||
|
"hasNextPage": true,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"title": { "romaji": "NARUTO", "english": "Naruto" },
|
||||||
|
"coverImage": { "large": "https://s4.anilist.co/file/..." },
|
||||||
|
"format": "TV",
|
||||||
|
"episodes": 220,
|
||||||
|
"status": "FINISHED",
|
||||||
|
"averageScore": 80
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **18 Endpoints** — Search, info, episodes, streaming, characters, relations, and more
|
||||||
|
- **12 Streaming Providers** — M3U8 URLs with resolution, codec, fansub info
|
||||||
|
- **No API Key** — Just make requests, no registration needed
|
||||||
|
- **In-Memory Cache** — 1-5 minute TTL for fast responses
|
||||||
|
- **CORS Enabled** — Access from any domain
|
||||||
|
- **JSON Responses** — Standardized `{success, results}` format
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [API Endpoints Reference](endpoints.md) — Complete endpoint documentation with real responses
|
||||||
|
- [Streaming Flow Guide](streaming.md) — How to get stream URLs step by step
|
||||||
|
- [Code Examples](examples.md) — cURL, JavaScript, Python (all tested and working)
|
||||||
|
- [Architecture](architecture.md) — Project structure and design decisions
|
||||||
|
|
||||||
|
## Response Format
|
||||||
|
|
||||||
|
All endpoints return JSON in this format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Error responses:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "Error description"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rate Limiting
|
||||||
|
|
||||||
|
Rate limiting is enforced at 100 requests per minute per IP address. The API also uses a 1-5 minute in-memory cache. Repeated requests for the same data will be served from cache.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
This API is for **educational purposes only**. It fetches data from AniList GraphQL and Miruro streaming providers. We are not affiliated with or endorsed by Miruro. All content belongs to its respective owners.
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
# Streaming Flow Guide
|
||||||
|
|
||||||
|
This guide explains how to get streaming URLs from MiruroAPI in 3 steps.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Get Episodes → /api/episodes/:anilistId
|
||||||
|
Step 2: Get Stream URLs → /api/watch/:provider/:anilistId/:category/:slug
|
||||||
|
Step 3: Play M3U8 → Use HLS player
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Get Episodes
|
||||||
|
|
||||||
|
Each anime has episodes from multiple providers. Fetch the episode list with the AniList ID.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/episodes/20"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"providers": {
|
||||||
|
"kiwi": {
|
||||||
|
"meta": { "id": "1571", "title": "Naruto", "type": "TV" },
|
||||||
|
"episodes": {
|
||||||
|
"sub": [
|
||||||
|
{
|
||||||
|
"id": "watch/kiwi/20/sub/animepahe-1",
|
||||||
|
"number": 1,
|
||||||
|
"title": "Enter: Naruto Uzumaki!",
|
||||||
|
"image": "https://image.tmdb.org/t/p/original/...",
|
||||||
|
"airDate": "2002-10-03",
|
||||||
|
"audio": "sub",
|
||||||
|
"filler": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Key field:** `id` in each episode — Pass this to the next step.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Get Stream URLs
|
||||||
|
|
||||||
|
Use the episode `id` from Step 1 to get M3U8 streaming URLs.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl "https://mirurotvapi.vercel.app/api/watch/kiwi/20/sub/animepahe-1"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"results": {
|
||||||
|
"streams": [
|
||||||
|
{
|
||||||
|
"url": "https://vault-01.uwucdn.top/stream/.../uwu.m3u8",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "360p",
|
||||||
|
"resolution": { "width": 640, "height": 360 },
|
||||||
|
"codec": "h264",
|
||||||
|
"audio": "sub",
|
||||||
|
"fansub": "df68",
|
||||||
|
"isActive": false,
|
||||||
|
"referer": "https://kwik.cx/e/..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"download": "https://pahe.win/LJmbA"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Done!** Use `streams[0].url` in your HLS video player.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Play M3U8
|
||||||
|
|
||||||
|
### HLS.js (Browser)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
|
||||||
|
<video id="player" controls></video>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const url = 'https://vault-01.uwucdn.top/stream/.../uwu.m3u8';
|
||||||
|
const video = document.getElementById('player');
|
||||||
|
|
||||||
|
if (Hls.isSupported()) {
|
||||||
|
const hls = new Hls();
|
||||||
|
hls.loadSource(url);
|
||||||
|
hls.attachMedia(video);
|
||||||
|
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||||
|
video.src = url; // Native HLS (Safari)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## JavaScript Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
async function getStreamUrl(anilistId) {
|
||||||
|
// Step 1: Get episodes
|
||||||
|
const episodesRes = await fetch(`https://mirurotvapi.vercel.app/api/episodes/${anilistId}`);
|
||||||
|
const episodesData = await episodesRes.json();
|
||||||
|
const providers = episodesData.results.providers;
|
||||||
|
|
||||||
|
// Pick first provider with sub episodes
|
||||||
|
const provider = Object.keys(providers)[0];
|
||||||
|
const episodes = providers[provider].episodes.sub;
|
||||||
|
|
||||||
|
if (!episodes || episodes.length === 0) {
|
||||||
|
throw new Error('No episodes found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use first episode's id
|
||||||
|
const episodeId = episodes[0].id; // "watch/kiwi/20/sub/animepahe-1"
|
||||||
|
const parts = episodeId.split('/');
|
||||||
|
const category = parts[3]; // "sub"
|
||||||
|
const slug = parts[4]; // "animepahe-1"
|
||||||
|
|
||||||
|
// Step 2: Get stream URLs
|
||||||
|
const streamRes = await fetch(`https://mirurotvapi.vercel.app/api/watch/${provider}/${anilistId}/${category}/${slug}`);
|
||||||
|
const streamData = await streamRes.json();
|
||||||
|
|
||||||
|
return streamData.results.streams[0].url; // "https://...m3u8"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
getStreamUrl(20)
|
||||||
|
.then(url => console.log('Stream URL:', url))
|
||||||
|
.catch(err => console.error('Error:', err));
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Python Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
import requests
|
||||||
|
|
||||||
|
def get_stream_url(anilist_id):
|
||||||
|
base = "https://mirurotvapi.vercel.app/api"
|
||||||
|
|
||||||
|
# Step 1: Get episodes
|
||||||
|
episodes_res = requests.get(f"{base}/episodes/{anilist_id}")
|
||||||
|
episodes_data = episodes_res.json()
|
||||||
|
providers = episodes_data['results']['providers']
|
||||||
|
|
||||||
|
# Pick first provider
|
||||||
|
provider = list(providers.keys())[0]
|
||||||
|
episodes = providers[provider]['episodes']['sub']
|
||||||
|
|
||||||
|
if not episodes:
|
||||||
|
raise Exception("No episodes found")
|
||||||
|
|
||||||
|
episode_id = episodes[0]['id'] # "watch/kiwi/20/sub/animepahe-1"
|
||||||
|
parts = episode_id.split('/')
|
||||||
|
category = parts[3]
|
||||||
|
slug = parts[4]
|
||||||
|
|
||||||
|
# Step 2: Get stream URLs
|
||||||
|
stream_res = requests.get(f"{base}/watch/{provider}/{anilist_id}/{category}/{slug}")
|
||||||
|
stream_data = stream_res.json()
|
||||||
|
|
||||||
|
return stream_data['results']['streams'][0]['url']
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
url = get_stream_url(20)
|
||||||
|
print(f"Stream URL: {url}")
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sub & Dub
|
||||||
|
|
||||||
|
Providers return both `sub` and `dub` episode lists:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const eps = await fetch("/api/episodes/20").then(r => r.json());
|
||||||
|
const kiwi = eps.results.providers.kiwi.episodes;
|
||||||
|
|
||||||
|
// Get sub episodes
|
||||||
|
const subEps = kiwi.sub; // [{ id: "watch/kiwi/20/sub/animepahe-1", ... }]
|
||||||
|
|
||||||
|
// Get dub episodes (if available)
|
||||||
|
const dubEps = kiwi.dub; // [{ id: "watch/kiwi/20/dub/...", ... }]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Available Providers
|
||||||
|
|
||||||
|
| Provider | Sub | Dub | Download | Skip Times |
|
||||||
|
|:---|:---:|:---:|:---:|:---:|
|
||||||
|
| kiwi | ✅ | ❌ | ✅ | ❌ |
|
||||||
|
| pewe | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| bee | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| bonk | ✅ | ✅ | ✅ | ✅ |
|
||||||
|
| bun | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| ally | ✅ | ❌ | ✅ | ❌ |
|
||||||
|
| nun | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| twin | ✅ | ✅ | ❌ | ❌ |
|
||||||
|
| cog | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| moo | ✅ | ❌ | ✅ | ❌ |
|
||||||
|
| hop | ❌ | ✅ | ❌ | ❌ |
|
||||||
|
| telli | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| "No episodes found" | Check the AniList ID is correct (use `/api/search` first) |
|
||||||
|
| "No providers" | The anime may not be available on any provider |
|
||||||
|
| Stream URL 403 | Some servers require specific referrer headers |
|
||||||
|
| CORS error | Use a proxy or access from server-side |
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "miruro-api",
|
"name": "miruro-api",
|
||||||
"version": "1.0.0",
|
"version": "1.3.0",
|
||||||
"description": "RESTful API for miruro.com - Anime streaming data powered by AniList GraphQL and Miruro streaming providers",
|
"description": "RESTful API for miruro.tv - Anime streaming data powered by AniList GraphQL and Miruro streaming providers",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "node server.js",
|
"dev": "nodemon server.js",
|
||||||
"test": "node test.js"
|
"test": "node test.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -14,15 +14,25 @@
|
|||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^16.4.0"
|
"dotenv": "^16.4.0"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.0"
|
||||||
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"anime",
|
"anime",
|
||||||
"api",
|
"api",
|
||||||
"miruro",
|
"miruro",
|
||||||
"anilist",
|
"anilist",
|
||||||
|
"graphql",
|
||||||
"streaming",
|
"streaming",
|
||||||
"m3u8",
|
"m3u8",
|
||||||
"hls"
|
"hls",
|
||||||
|
"rest-api",
|
||||||
|
"express"
|
||||||
],
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/Shineii86/MiruroAPI"
|
||||||
|
},
|
||||||
"author": "Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ <157171073+Shineii86@users.noreply.github.com>",
|
"author": "Sʜɪɴᴇɪ Nᴏᴜᴢᴇɴ <157171073+Shineii86@users.noreply.github.com>",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<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 — MiruroAPI</title>
|
||||||
|
<meta name="theme-color" content="#0A0A12" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/icon-dark.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" />
|
||||||
|
<style>
|
||||||
|
:root{--bg:#0A0A12;--text:#E2E8F0;--text2:#94A3B8;--text3:#64748B;--purple:#A855F7;--pink:#EC4899;--indigo:#6366F1;--green:#22C55E;--red:#EF4444;--border:rgba(255,255,255,.06);--glass:rgba(255,255,255,.03)}
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);min-height:100vh;display:flex;align-items:center;justify-content:center;overflow:hidden}
|
||||||
|
.ambient{position:fixed;inset:0;pointer-events:none}
|
||||||
|
.orb{position:absolute;border-radius:50%;filter:blur(120px);opacity:.1}
|
||||||
|
.orb-1{width:500px;height:500px;background:var(--purple);top:-150px;left:-100px;animation:drift 24s ease-in-out infinite}
|
||||||
|
.orb-2{width:400px;height:400px;background:var(--pink);bottom:-100px;right:-80px;animation:drift 20s ease-in-out infinite reverse}
|
||||||
|
@keyframes drift{0%,100%{transform:translate(0,0) scale(1)}50%{transform:translate(40px,-30px) scale(1.08)}}
|
||||||
|
.container{position:relative;z-index:1;text-align:center;padding:40px;max-width:560px}
|
||||||
|
.code{font-size:clamp(6rem,18vw,14rem);font-weight:900;line-height:1;letter-spacing:-.06em;background:linear-gradient(135deg,var(--purple),var(--pink),var(--indigo));-webkit-background-clip:text;-webkit-text-fill-color:transparent;animation:float 4s ease-in-out infinite}
|
||||||
|
@keyframes float{0%,100%{transform:translateY(0)}50%{transform:translateY(-10px)}}
|
||||||
|
.title{font-size:1.4rem;font-weight:700;margin:16px 0 8px}
|
||||||
|
.desc{color:var(--text2);font-size:.95rem;margin-bottom:32px;line-height:1.7}
|
||||||
|
.actions{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-flex;align-items:center;gap:8px;padding:12px 24px;border-radius:10px;font-size:.88rem;font-weight:600;text-decoration:none;border:none;cursor:pointer;transition:all .25s;font-family:inherit}
|
||||||
|
.btn svg{width:16px;height:16px}
|
||||||
|
.btn-primary{background:linear-gradient(135deg,var(--purple),var(--pink));color:#fff;box-shadow:0 4px 24px rgba(168,85,247,.3)}
|
||||||
|
.btn-primary:hover{transform:translateY(-2px);box-shadow:0 8px 32px rgba(168,85,247,.4)}
|
||||||
|
.btn-ghost{background:var(--glass);border:1px solid rgba(255,255,255,.1);color:var(--text)}
|
||||||
|
.btn-ghost:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.15)}
|
||||||
|
.glitch{position:relative;display:inline-block}
|
||||||
|
.glitch::before,.glitch::after{content:attr(data-text);position:absolute;top:0;left:0;width:100%;height:100%;background:var(--bg)}
|
||||||
|
.glitch::before{left:2px;text-shadow:-2px 0 var(--pink);clip-path:inset(0 0 0 0);animation:glitch-1 2s infinite linear alternate-reverse}
|
||||||
|
.glitch::after{left:-2px;text-shadow:2px 0 var(--indigo);clip-path:inset(0 0 0 0);animation:glitch-2 3s infinite linear alternate-reverse}
|
||||||
|
@keyframes glitch-1{0%{clip-path:inset(40% 0 61% 0)}20%{clip-path:inset(92% 0 1% 0)}40%{clip-path:inset(43% 0 1% 0)}60%{clip-path:inset(25% 0 58% 0)}80%{clip-path:inset(54% 0 7% 0)}100%{clip-path:inset(58% 0 43% 0)}}
|
||||||
|
@keyframes glitch-2{0%{clip-path:inset(65% 0 13% 0)}20%{clip-path:inset(79% 0 2% 0)}40%{clip-path:inset(48% 0 38% 0)}60%{clip-path:inset(33% 0 2% 0)}80%{clip-path:inset(16% 0 62% 0)}100%{clip-path:inset(27% 0 42% 0)}}
|
||||||
|
.status-line{display:flex;align-items:center;justify-content:center;gap:8px;margin-top:24px;color:var(--text3);font-size:.78rem;font-family:'JetBrains Mono',monospace}
|
||||||
|
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--red);animation:pulse 1.5s infinite}
|
||||||
|
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.3}}
|
||||||
|
.scanline{position:fixed;inset:0;z-index:0;pointer-events:none;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.03) 2px,rgba(0,0,0,.03) 4px)}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="ambient"><div class="orb orb-1"></div><div class="orb orb-2"></div></div>
|
||||||
|
<div class="scanline"></div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="code"><span class="glitch" data-text="404">404</span></div>
|
||||||
|
<div class="title">Endpoint Not Found</div>
|
||||||
|
<div class="desc">The path you requested doesn't exist in the MiruroAPI.<br/>Check the documentation or try a valid endpoint.</div>
|
||||||
|
<div class="actions">
|
||||||
|
<a href="/" class="btn btn-primary">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a href="/api/health" class="btn btn-ghost">
|
||||||
|
<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>
|
||||||
|
API Status
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/Shineii86/MiruroAPI#readme" 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>
|
||||||
|
Docs
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="status-line"><span class="status-dot"></span> HTTP 404 · Not Found</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,134 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" prefix="og: https://ogp.me/ns#">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<!-- ──────────────── OG Meta ──────────────── -->
|
||||||
|
<meta property="og:title" content="MiruroAPI — Swagger UI Documentation">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="https://mirurotvapi.vercel.app/docs">
|
||||||
|
<meta property="og:image" content="https://mirurotvapi.vercel.app/og-image.png">
|
||||||
|
<meta property="og:description" content="Interactive Swagger UI documentation for MiruroAPI. Test all 16 endpoints directly from the browser.">
|
||||||
|
<meta property="og:site_name" content="MiruroAPI">
|
||||||
|
<meta property="og:locale" content="en_US">
|
||||||
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
<meta name="twitter:title" content="MiruroAPI — Swagger UI Documentation">
|
||||||
|
<meta name="twitter:description" content="Interactive Swagger UI documentation for MiruroAPI.">
|
||||||
|
<meta name="twitter:image" content="https://mirurotvapi.vercel.app/og-image.png">
|
||||||
|
<meta name="description" content="Interactive Swagger UI documentation for MiruroAPI. Test all 16 endpoints directly from the browser.">
|
||||||
|
<meta name="robots" content="index, follow">
|
||||||
|
<meta name="theme-color" content="#0b1622">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/icon-dark.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="512x512" href="/icon-512x512.png">
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon-180x180.png">
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="48x48">
|
||||||
|
<link rel="canonical" href="https://mirurotvapi.vercel.app/docs">
|
||||||
|
|
||||||
|
<title>MiruroAPI — Swagger UI</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #0b1622;
|
||||||
|
--bg2: #0f172a;
|
||||||
|
--accent: #38bdf8;
|
||||||
|
--accent2: #818cf8;
|
||||||
|
--text: #e2e8f0;
|
||||||
|
--muted: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ──────────────── BASE ──────────────── */
|
||||||
|
html { box-sizing: border-box; }
|
||||||
|
*, *::before, *::after { box-sizing: inherit; }
|
||||||
|
body {
|
||||||
|
margin: 0; padding: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ──────────────── CUSTOM HEADER ──────────────── */
|
||||||
|
.docs-header {
|
||||||
|
background: linear-gradient(135deg, var(--bg2), #1e1b4b);
|
||||||
|
border-bottom: 1px solid rgba(255,255,255,0.05);
|
||||||
|
padding: 20px 40px;
|
||||||
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
|
}
|
||||||
|
.docs-brand { display: flex; align-items: center; gap: 12px; text-decoration: none; }
|
||||||
|
.docs-logo {
|
||||||
|
width: 32px; height: 32px; border-radius: 8px;
|
||||||
|
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
color: white; font-weight: 900; font-size: 16px;
|
||||||
|
}
|
||||||
|
.docs-title { color: var(--text); font-weight: 700; font-size: 1em; }
|
||||||
|
.docs-subtitle { color: var(--muted); font-size: 0.8em; margin-left: 8px; }
|
||||||
|
.docs-links { display: flex; gap: 16px; }
|
||||||
|
.docs-links a {
|
||||||
|
color: var(--muted); text-decoration: none; font-size: 0.85em;
|
||||||
|
font-weight: 500; transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.docs-links a:hover { color: var(--accent); }
|
||||||
|
|
||||||
|
/* ──────────────── SWAGGER UI OVERRIDES ──────────────── */
|
||||||
|
.swagger-ui .topbar { display: none; }
|
||||||
|
.swagger-ui .info .title { color: var(--text) !important; }
|
||||||
|
.swagger-ui .info .description p { color: var(--muted) !important; }
|
||||||
|
.swagger-ui .scheme-container { background: rgba(15, 23, 42, 0.5) !important; box-shadow: none !important; }
|
||||||
|
.swagger-ui .opblock-tag { color: var(--accent) !important; border-color: rgba(255,255,255,0.05) !important; }
|
||||||
|
.swagger-ui .opblock .opblock-summary-description { color: var(--muted) !important; }
|
||||||
|
.swagger-ui .btn { box-shadow: none !important; }
|
||||||
|
.swagger-ui .btn.execute { background: linear-gradient(135deg, var(--accent), var(--accent2)) !important; }
|
||||||
|
.swagger-ui .model-box { background: rgba(15, 23, 42, 0.3) !important; }
|
||||||
|
.swagger-ui .scheme-container .schemes > select { color: var(--text); background: var(--bg2); }
|
||||||
|
|
||||||
|
/* ──────────────── BACK LINK ──────────────── */
|
||||||
|
.back-link {
|
||||||
|
display: inline-flex; align-items: center; gap: 6px;
|
||||||
|
color: var(--accent); text-decoration: none;
|
||||||
|
font-size: 0.85em; font-weight: 500;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
.back-link:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- ──────────────── CUSTOM HEADER ──────────────── -->
|
||||||
|
<div class="docs-header">
|
||||||
|
<div style="display: flex; align-items: center;">
|
||||||
|
<a href="/" class="docs-brand">
|
||||||
|
<img src="/icon-512x512.png" alt="Miruro" class="docs-logo" style="object-fit:contain; filter:brightness(0) invert();">
|
||||||
|
<span class="docs-title">MiruroAPI</span>
|
||||||
|
</a>
|
||||||
|
<span class="docs-subtitle">Swagger UI Documentation</span>
|
||||||
|
</div>
|
||||||
|
<div class="docs-links">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/openapi.json">OpenAPI Spec</a>
|
||||||
|
<a href="/api/health">Health</a>
|
||||||
|
<a href="https://github.com/Shineii86/MiruroAPI" target="_blank">GitHub</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ──────────────── SWAGGER UI ──────────────── -->
|
||||||
|
<div id="swagger-ui"></div>
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
||||||
|
<script>
|
||||||
|
SwaggerUIBundle({
|
||||||
|
url: "/openapi.json",
|
||||||
|
dom_id: "#swagger-ui",
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
layout: "BaseLayout",
|
||||||
|
deepLinking: true,
|
||||||
|
defaultModelsExpandDepth: -1,
|
||||||
|
docExpansion: "list",
|
||||||
|
filter: true,
|
||||||
|
tryItOutEnabled: true,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="1024" height="1024" viewBox="0 0 270.93333 270.93332" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-41.580053,-91.524707)">
|
||||||
|
<path style="fill:#ffffff;fill-opacity:1;stroke-width:1.70387"
|
||||||
|
d="m 247.27543,314.70152 c -6.10955,-6.70519 -16.14364,-12.9409 -13.93299,-23.42798 2.12389,-20.22967 4.00106,-40.51893 3.43613,-60.89302 0.32828,-23.84505 -0.7151,-47.80644 -4.67234,-71.32308 -21.29031,23.99166 -31.12328,55.278 -42.21767,84.73199 -3.89541,12.27935 -21.71766,7.83903 -21.86694,-4.43985 -6.30241,-22.41752 -15.57712,-44.12149 -29.46365,-62.93722 -3.96932,-3.43831 -12.49125,-22.4167 -14.54817,-9.93684 -7.96323,36.33701 -8.31833,73.85796 -6.24645,110.84721 2.19408,10.98054 -3.58307,20.65309 -14.39585,23.75435 -7.691633,1.63664 -18.686185,16.40367 -24.689738,6.4484 -5.549778,-6.39186 -6.386914,-14.77824 3.524397,-16.80023 3.650034,-4.06414 14.883155,-3.99688 13.428515,-10.68708 -0.363081,-6.16925 -0.502499,-12.34844 -0.722494,-18.52376 -6.011978,0.12421 -20.194572,9.14755 -21.048961,0.29803 4.558972,-3.34862 14.481259,-4.72681 17.663382,-6.27314 -5.30158,-0.36703 -12.065997,2.00733 -16.203654,-1.67557 2.654494,-5.22286 11.415092,-2.80527 16.759698,-3.75272 5.21548,0.98551 2.118507,-6.2941 3.203468,-9.41871 1.362186,-31.92446 2.830394,-64.401 12.138507,-95.17623 1.6606,-12.81687 17.56784,-17.5421 25.47531,-6.99678 20.47568,20.4649 38.19519,44.34874 47.58582,71.98892 4.61856,-4.03449 7.71667,-17.72071 12.32719,-24.95391 9.91397,-19.20957 21.02511,-38.64633 37.33735,-53.16525 7.54853,-8.78842 20.64347,-3.59999 20.62749,7.78422 8.57544,35.65229 6.90072,72.55248 7.54692,108.94394 6.47271,0.53048 13.50356,-0.74591 19.53337,1.55892 -0.40011,6.92584 -15.88203,1.54318 -17.9692,4.29818 5.80571,3.1568 22.13502,1.93763 20.18934,10.35691 -7.79577,3.09455 -24.35445,-11.17611 -22.65057,3.17853 1.34495,11.08 -7.66567,25.29061 3.96573,32.80224 7.93679,6.06024 16.23111,15.77249 3.76586,22.34018 -6.35101,7.76687 -12.98193,-5.80286 -17.8798,-8.95065 z m -36.07374,-34.0964 c -9.58671,-2.83824 -11.53728,-17.20921 -2.66126,-22.20068 9.07411,-6.48726 22.3771,4.08888 18.20146,14.33921 -1.92914,6.54286 -9.18288,9.79938 -15.5402,7.86147 z m -75.26977,-2.55526 c -11.469,-1.7362 -13.22099,-19.59021 -2.32646,-23.45757 10.02949,-4.7738 21.46818,7.15397 15.81668,16.84718 -2.45128,4.77109 -8.13415,7.97894 -13.49022,6.61039 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg width="1024" height="1024" viewBox="0 0 270.93333 270.93332" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g transform="translate(-41.580053,-91.524707)">
|
||||||
|
<path style="fill:#000000;fill-opacity:1;stroke-width:1.70387"
|
||||||
|
d="m 247.27543,314.70152 c -6.10955,-6.70519 -16.14364,-12.9409 -13.93299,-23.42798 2.12389,-20.22967 4.00106,-40.51893 3.43613,-60.89302 0.32828,-23.84505 -0.7151,-47.80644 -4.67234,-71.32308 -21.29031,23.99166 -31.12328,55.278 -42.21767,84.73199 -3.89541,12.27935 -21.71766,7.83903 -21.86694,-4.43985 -6.30241,-22.41752 -15.57712,-44.12149 -29.46365,-62.93722 -3.96932,-3.43831 -12.49125,-22.4167 -14.54817,-9.93684 -7.96323,36.33701 -8.31833,73.85796 -6.24645,110.84721 2.19408,10.98054 -3.58307,20.65309 -14.39585,23.75435 -7.691633,1.63664 -18.686185,16.40367 -24.689738,6.4484 -5.549778,-6.39186 -6.386914,-14.77824 3.524397,-16.80023 3.650034,-4.06414 14.883155,-3.99688 13.428515,-10.68708 -0.363081,-6.16925 -0.502499,-12.34844 -0.722494,-18.52376 -6.011978,0.12421 -20.194572,9.14755 -21.048961,0.29803 4.558972,-3.34862 14.481259,-4.72681 17.663382,-6.27314 -5.30158,-0.36703 -12.065997,2.00733 -16.203654,-1.67557 2.654494,-5.22286 11.415092,-2.80527 16.759698,-3.75272 5.21548,0.98551 2.118507,-6.2941 3.203468,-9.41871 1.362186,-31.92446 2.830394,-64.401 12.138507,-95.17623 1.6606,-12.81687 17.56784,-17.5421 25.47531,-6.99678 20.47568,20.4649 38.19519,44.34874 47.58582,71.98892 4.61856,-4.03449 7.71667,-17.72071 12.32719,-24.95391 9.91397,-19.20957 21.02511,-38.64633 37.33735,-53.16525 7.54853,-8.78842 20.64347,-3.59999 20.62749,7.78422 8.57544,35.65229 6.90072,72.55248 7.54692,108.94394 6.47271,0.53048 13.50356,-0.74591 19.53337,1.55892 -0.40011,6.92584 -15.88203,1.54318 -17.9692,4.29818 5.80571,3.1568 22.13502,1.93763 20.18934,10.35691 -7.79577,3.09455 -24.35445,-11.17611 -22.65057,3.17853 1.34495,11.08 -7.66567,25.29061 3.96573,32.80224 7.93679,6.06024 16.23111,15.77249 3.76586,22.34018 -6.35101,7.76687 -12.98193,-5.80286 -17.8798,-8.95065 z m -36.07374,-34.0964 c -9.58671,-2.83824 -11.53728,-17.20921 -2.66126,-22.20068 9.07411,-6.48726 22.3771,4.08888 18.20146,14.33921 -1.92914,6.54286 -9.18288,9.79938 -15.5402,7.86147 z m -75.26977,-2.55526 c -11.469,-1.7362 -13.22099,-19.59021 -2.32646,-23.45757 10.02949,-4.7738 21.46818,7.15397 15.81668,16.84718 -2.45128,4.77109 -8.13415,7.97894 -13.49022,6.61039 z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "MiruroAPI",
|
||||||
|
"short_name": "MiruroAPI",
|
||||||
|
"description": "Free REST API for anime streaming data. 18 endpoints, 12 providers, M3U8 URLs. No API key required.",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"id": "/",
|
||||||
|
"scope": "/",
|
||||||
|
"lang": "en",
|
||||||
|
"orientation": "any",
|
||||||
|
"background_color": "#0A0A12",
|
||||||
|
"theme_color": "#A855F7",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/icon-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png",
|
||||||
|
"purpose": "maskable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/icon-dark.svg",
|
||||||
|
"sizes": "any",
|
||||||
|
"type": "image/svg+xml",
|
||||||
|
"purpose": "any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"categories": ["developer", "entertainment"]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,88 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="description" content="Privacy Policy for MiruroAPI. We do not collect, store, or share personal data." />
|
||||||
|
<meta property="og:title" content="Privacy Policy — MiruroAPI" />
|
||||||
|
<meta property="og:description" content="Privacy Policy for MiruroAPI. We do not collect, store, or share personal data." />
|
||||||
|
<meta property="og:image" content="https://mirurotvapi.vercel.app/og-image.png" />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Privacy Policy — MiruroAPI</title>
|
||||||
|
<meta name="theme-color" content="#0A0A12" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/icon-dark.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" />
|
||||||
|
<style>
|
||||||
|
:root{--bg:#0A0A12;--bg2:#111120;--text:#E2E8F0;--text2:#94A3B8;--text3:#64748B;--purple:#A855F7;--pink:#EC4899;--border:rgba(255,255,255,.06);--glass:rgba(255,255,255,.03)}
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.8}
|
||||||
|
.ambient{position:fixed;inset:0;pointer-events:none;z-index:0}
|
||||||
|
.orb{position:absolute;border-radius:50%;filter:blur(120px);opacity:.08}
|
||||||
|
.orb-1{width:500px;height:500px;background:var(--purple);top:-150px;left:-100px}
|
||||||
|
.orb-2{width:400px;height:400px;background:var(--pink);bottom:-100px;right:-80px}
|
||||||
|
header{position:sticky;top:0;z-index:100;background:rgba(10,10,18,.88);backdrop-filter:blur(24px);border-bottom:1px solid var(--border);padding:0 24px}
|
||||||
|
.header-inner{max-width:720px;margin:0 auto;display:flex;align-items:center;height:60px}
|
||||||
|
.logo{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--text);font-weight:700;font-size:1rem}
|
||||||
|
.logo span{background:linear-gradient(135deg,var(--purple),var(--pink));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||||
|
.content{position:relative;z-index:1;max-width:720px;margin:0 auto;padding:60px 24px 80px}
|
||||||
|
h1{font-size:2rem;font-weight:800;letter-spacing:-.03em;margin-bottom:8px}
|
||||||
|
.updated{color:var(--text3);font-size:.82rem;margin-bottom:40px;font-family:'JetBrains Mono',monospace}
|
||||||
|
h2{font-size:1.2rem;font-weight:700;margin:32px 0 12px;padding-top:16px;border-top:1px solid var(--border)}
|
||||||
|
h2:first-of-type{border-top:none;padding-top:0}
|
||||||
|
p{color:var(--text2);font-size:.92rem;margin-bottom:16px}
|
||||||
|
ul{color:var(--text2);font-size:.92rem;margin:0 0 16px 20px}
|
||||||
|
li{margin-bottom:6px}
|
||||||
|
code{font-family:'JetBrains Mono',monospace;font-size:.82rem;background:rgba(168,85,247,.1);color:var(--purple);padding:2px 6px;border-radius:4px}
|
||||||
|
a{color:var(--purple);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
footer{position:relative;z-index:1;border-top:1px solid var(--border);padding:24px;text-align:center;color:var(--text3);font-size:.75rem}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="ambient"><div class="orb orb-1"></div><div class="orb orb-2"></div></div>
|
||||||
|
<header><div class="header-inner"><a href="/" class="logo">📺 <span>Miruro</span>API</a></div></header>
|
||||||
|
<div class="content">
|
||||||
|
<h1>Privacy Policy</h1>
|
||||||
|
<div class="updated">Last updated: 2026-06-09</div>
|
||||||
|
|
||||||
|
<h2>1. Overview</h2>
|
||||||
|
<p>MiruroAPI ("the Service") is a free, open-source REST API that provides anime data by aggregating information from AniList GraphQL and Miruro streaming providers. This Privacy Policy explains how we handle data when you use the Service.</p>
|
||||||
|
|
||||||
|
<h2>2. Data We Collect</h2>
|
||||||
|
<p>The Service is designed to be <strong>stateless and privacy-preserving</strong>. We do not collect, store, or process any personal data from API consumers.</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>No user accounts</strong> — The API requires no authentication, registration, or API keys.</li>
|
||||||
|
<li><strong>No cookies</strong> — We do not set tracking cookies on API responses.</li>
|
||||||
|
<li><strong>No analytics</strong> — We do not use Google Analytics, Mixpanel, or any third-party analytics on API endpoints.</li>
|
||||||
|
<li><strong>No logging of personal data</strong> — Server logs may contain IP addresses and request paths (standard Vercel/Node.js behavior), but these are not stored, sold, or shared.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>3. Third-Party Data Sources</h2>
|
||||||
|
<p>The Service fetches data from <code>graphql.anilist.co</code> (AniList) and <code>miruro.tv</code> (streaming providers). 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/MiruroAPI" target="_blank" rel="noopener noreferrer">GitHub repository</a>.</p>
|
||||||
|
|
||||||
|
<h2>4. Data Storage</h2>
|
||||||
|
<p>The Service uses a simple in-memory cache (1-5 minute TTL) to reduce repeated API calls. This cache is:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Purely temporary and reset on every server restart</li>
|
||||||
|
<li>Not persisted to any database</li>
|
||||||
|
<li>Not associated with any user identity</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>5. Data Sharing</h2>
|
||||||
|
<p>We do not sell, trade, or share any data with third parties. The API is provided as-is for educational and development purposes.</p>
|
||||||
|
|
||||||
|
<h2>6. Children's Privacy</h2>
|
||||||
|
<p>The Service is not directed at children under 13. We do not knowingly collect information from children under 13.</p>
|
||||||
|
|
||||||
|
<h2>7. Changes to This Policy</h2>
|
||||||
|
<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/MiruroAPI/issues" target="_blank" rel="noopener noreferrer">github.com/Shineii86/MiruroAPI/issues</a>.</p>
|
||||||
|
</div>
|
||||||
|
<footer>© 2026 MiruroAPI · <a href="/" style="color:var(--purple)">Home</a> · <a href="/tos" style="color:var(--purple)">Terms</a></footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# MiruroAPI — https://mirurotvapi.vercel.app
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
Disallow: /api
|
||||||
|
|
||||||
|
Sitemap: https://mirurotvapi.vercel.app/sitemap.xml
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://mirurotvapi.vercel.app/</loc>
|
||||||
|
<lastmod>2026-06-09</lastmod>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://mirurotvapi.vercel.app/api/health</loc>
|
||||||
|
<lastmod>2026-06-09</lastmod>
|
||||||
|
<changefreq>daily</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://mirurotvapi.vercel.app/privacy</loc>
|
||||||
|
<lastmod>2026-06-09</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.3</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://mirurotvapi.vercel.app/tos</loc>
|
||||||
|
<lastmod>2026-06-09</lastmod>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.3</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="description" content="Terms of Service for MiruroAPI. Acceptable use, rate limits, liability, and DMCA policy." />
|
||||||
|
<meta property="og:title" content="Terms of Service — MiruroAPI" />
|
||||||
|
<meta property="og:description" content="Terms of Service for MiruroAPI. Acceptable use, rate limits, liability, and DMCA policy." />
|
||||||
|
<meta property="og:image" content="https://mirurotvapi.vercel.app/og-image.png" />
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Terms of Service — MiruroAPI</title>
|
||||||
|
<meta name="theme-color" content="#0A0A12" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/icon-dark.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" />
|
||||||
|
<style>
|
||||||
|
:root{--bg:#0A0A12;--bg2:#111120;--text:#E2E8F0;--text2:#94A3B8;--text3:#64748B;--purple:#A855F7;--pink:#EC4899;--border:rgba(255,255,255,.06);--glass:rgba(255,255,255,.03)}
|
||||||
|
*{margin:0;padding:0;box-sizing:border-box}
|
||||||
|
body{font-family:'Inter',system-ui,sans-serif;background:var(--bg);color:var(--text);line-height:1.8}
|
||||||
|
.ambient{position:fixed;inset:0;pointer-events:none;z-index:0}
|
||||||
|
.orb{position:absolute;border-radius:50%;filter:blur(120px);opacity:.08}
|
||||||
|
.orb-1{width:500px;height:500px;background:var(--purple);top:-150px;left:-100px}
|
||||||
|
.orb-2{width:400px;height:400px;background:var(--pink);bottom:-100px;right:-80px}
|
||||||
|
header{position:sticky;top:0;z-index:100;background:rgba(10,10,18,.88);backdrop-filter:blur(24px);border-bottom:1px solid var(--border);padding:0 24px}
|
||||||
|
.header-inner{max-width:720px;margin:0 auto;display:flex;align-items:center;height:60px}
|
||||||
|
.logo{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--text);font-weight:700;font-size:1rem}
|
||||||
|
.logo span{background:linear-gradient(135deg,var(--purple),var(--pink));-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||||
|
.content{position:relative;z-index:1;max-width:720px;margin:0 auto;padding:60px 24px 80px}
|
||||||
|
h1{font-size:2rem;font-weight:800;letter-spacing:-.03em;margin-bottom:8px}
|
||||||
|
.updated{color:var(--text3);font-size:.82rem;margin-bottom:40px;font-family:'JetBrains Mono',monospace}
|
||||||
|
h2{font-size:1.2rem;font-weight:700;margin:32px 0 12px;padding-top:16px;border-top:1px solid var(--border)}
|
||||||
|
h2:first-of-type{border-top:none;padding-top:0}
|
||||||
|
p{color:var(--text2);font-size:.92rem;margin-bottom:16px}
|
||||||
|
ul{color:var(--text2);font-size:.92rem;margin:0 0 16px 20px}
|
||||||
|
li{margin-bottom:6px}
|
||||||
|
code{font-family:'JetBrains Mono',monospace;font-size:.82rem;background:rgba(168,85,247,.1);color:var(--purple);padding:2px 6px;border-radius:4px}
|
||||||
|
a{color:var(--purple);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
strong{color:var(--text)}
|
||||||
|
footer{position:relative;z-index:1;border-top:1px solid var(--border);padding:24px;text-align:center;color:var(--text3);font-size:.75rem}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="ambient"><div class="orb orb-1"></div><div class="orb orb-2"></div></div>
|
||||||
|
<header><div class="header-inner"><a href="/" class="logo">📺 <span>Miruro</span>API</a></div></header>
|
||||||
|
<div class="content">
|
||||||
|
<h1>Terms of Service</h1>
|
||||||
|
<div class="updated">Last updated: 2026-06-09</div>
|
||||||
|
|
||||||
|
<h2>1. Acceptance of Terms</h2>
|
||||||
|
<p>By accessing or using MiruroAPI ("the Service"), you agree to be bound by these Terms of Service. If you do not agree, do not use the Service.</p>
|
||||||
|
|
||||||
|
<h2>2. Description of Service</h2>
|
||||||
|
<p>MiruroAPI is a free, open-source REST API that provides anime data by aggregating information from AniList GraphQL and Miruro streaming providers. The Service is provided "as is" without warranties of any kind.</p>
|
||||||
|
|
||||||
|
<h2>3. Educational Purpose</h2>
|
||||||
|
<p>The Service is built and maintained for <strong>educational and learning purposes only</strong>. It demonstrates API design, GraphQL integration, and serverless deployment techniques.</p>
|
||||||
|
|
||||||
|
<h2>4. No Affiliation</h2>
|
||||||
|
<p>The Service is <strong>not affiliated with, endorsed by, or connected to</strong> miruro.tv, Miruro, or any of its subsidiaries or affiliates. All trademarks and content belong to their respective owners.</p>
|
||||||
|
|
||||||
|
<h2>5. Third-Party Content</h2>
|
||||||
|
<p>All anime data, images, descriptions, and other content displayed through the API belong to their respective copyright holders (AniList, Miruro). The Service does not host, store, or distribute any copyrighted media.</p>
|
||||||
|
|
||||||
|
<h2>6. Acceptable Use</h2>
|
||||||
|
<p>You agree to use the Service responsibly and in compliance with all applicable laws. You must not:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Use the Service for any illegal purpose</li>
|
||||||
|
<li>Attempt to disrupt or overload the Service</li>
|
||||||
|
<li>Scrape the Service to build a competing product</li>
|
||||||
|
<li>Redistribute copyrighted content obtained through the Service</li>
|
||||||
|
<li>Use the Service to infringe on the rights of others</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>7. Rate Limiting</h2>
|
||||||
|
<p>The Service uses in-memory caching with configurable TTL. Rate limiting is enforced at 100 requests per minute per IP address. Exceeding this limit will result in a 429 status code.</p>
|
||||||
|
|
||||||
|
<h2>8. Availability</h2>
|
||||||
|
<p>The Service is hosted on Vercel's free tier and may experience downtime. We do not guarantee 100% availability. The Service may be modified, updated, or discontinued at any time without notice.</p>
|
||||||
|
|
||||||
|
<h2>9. Limitation of Liability</h2>
|
||||||
|
<p>In no event shall the creators of MiruroAPI 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 MiruroAPI is open source and available on <a href="https://github.com/Shineii86/MiruroAPI" 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/MiruroAPI/issues" target="_blank" rel="noopener noreferrer">github.com/Shineii86/MiruroAPI/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/MiruroAPI/issues" target="_blank" rel="noopener noreferrer">github.com/Shineii86/MiruroAPI/issues</a>.</p>
|
||||||
|
</div>
|
||||||
|
<footer>© 2026 MiruroAPI · <a href="/" style="color:var(--purple)">Home</a> · <a href="/privacy" style="color:var(--purple)">Privacy</a></footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
services:
|
||||||
|
- type: web
|
||||||
|
name: MiruroAPI
|
||||||
|
env: node
|
||||||
|
buildCommand: npm install
|
||||||
|
startCommand: npm start
|
||||||
|
envVars:
|
||||||
|
- key: ALLOWED_ORIGINS
|
||||||
|
value: https://miruro.tv,https://miruro.to,https://miruro.bz,https://miruro.ru
|
||||||
@@ -70,37 +70,29 @@ app.use((req, res, next) => {
|
|||||||
|
|
||||||
app.use(express.static(path.join(__dirname, "public"), { redirect: false }));
|
app.use(express.static(path.join(__dirname, "public"), { redirect: false }));
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
// CLEAN URL ROUTES
|
||||||
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// ---- FEATURE: Clean URL routes (no .html extension) ----
|
||||||
|
const fs = require("fs");
|
||||||
|
const publicDir = path.join(__dirname, "public");
|
||||||
|
|
||||||
|
app.get("/tos", (req, res) => {
|
||||||
|
res.sendFile(path.join(publicDir, "tos.html"));
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/privacy", (req, res) => {
|
||||||
|
res.sendFile(path.join(publicDir, "privacy.html"));
|
||||||
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
// SWAGGER UI DOCS
|
// SWAGGER UI DOCS
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
// ---- FEATURE: Interactive Swagger UI documentation at /docs ----
|
// ---- FEATURE: Interactive Swagger UI documentation at /docs ----
|
||||||
app.get("/docs", (req, res) => {
|
app.get("/docs", (req, res) => {
|
||||||
res.send(`<!DOCTYPE html>
|
res.sendFile(path.join(publicDir, "docs.html"));
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>MiruroAPI — Swagger UI</title>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css">
|
|
||||||
<style>body{margin:0;background:#0b1622;}</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="swagger-ui"></div>
|
|
||||||
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
||||||
<script>
|
|
||||||
SwaggerUIBundle({
|
|
||||||
url: "/openapi.json",
|
|
||||||
dom_id: "#swagger-ui",
|
|
||||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
|
|
||||||
layout: "BaseLayout",
|
|
||||||
deepLinking: true,
|
|
||||||
defaultModelsExpandDepth: -1,
|
|
||||||
docExpansion: "list",
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
@@ -187,7 +179,11 @@ app.use((req, res) => {
|
|||||||
message: "Endpoint not found",
|
message: "Endpoint not found",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
res.sendFile(path.join(__dirname, "public", "index.html"));
|
const notFoundPath = path.join(publicDir, "404.html");
|
||||||
|
if (fs.existsSync(notFoundPath)) {
|
||||||
|
return res.status(404).sendFile(notFoundPath);
|
||||||
|
}
|
||||||
|
res.sendFile(path.join(publicDir, "index.html"));
|
||||||
});
|
});
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const { getCached, setCache } = require("../helpers/cache");
|
|||||||
* @param {function} jsonError - Standardized error response wrapper
|
* @param {function} jsonError - Standardized error response wrapper
|
||||||
* @returns {express.Router} Configured router
|
* @returns {express.Router} Configured router
|
||||||
*/
|
*/
|
||||||
const createApiRoutes = (app, jsonResponse, jsonError) => {
|
const createApiRoutes = (jsonResponse, jsonError) => {
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════
|
||||||
@@ -355,7 +355,7 @@ const createApiRoutes = (app, jsonResponse, jsonError) => {
|
|||||||
success: true,
|
success: true,
|
||||||
results: {
|
results: {
|
||||||
status: "healthy",
|
status: "healthy",
|
||||||
version: "1.1.0",
|
version: "1.2.0",
|
||||||
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
uptime: `${hours}h ${minutes}m ${seconds}s`,
|
||||||
uptimeSeconds: uptime,
|
uptimeSeconds: uptime,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
@@ -404,7 +404,7 @@ const createApiRoutes = (app, jsonResponse, jsonError) => {
|
|||||||
info: {
|
info: {
|
||||||
title: "MiruroAPI",
|
title: "MiruroAPI",
|
||||||
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
|
description: "Free REST API for anime streaming data — AniList GraphQL + Miruro streaming providers",
|
||||||
version: "1.1.0",
|
version: "1.2.0",
|
||||||
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
|
contact: { name: "Shineii86", url: "https://github.com/Shineii86/MiruroAPI" },
|
||||||
},
|
},
|
||||||
servers: [
|
servers: [
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* ======= • ======= • ======= • ======= • =======• =======
|
||||||
|
* MiruroAPI — test.js
|
||||||
|
* Repository: https://github.com/Shineii86/MiruroAPI
|
||||||
|
*
|
||||||
|
* @description
|
||||||
|
* Integration test suite for MiruroAPI endpoints.
|
||||||
|
* Tests all major endpoints for correct response format.
|
||||||
|
*
|
||||||
|
* @author Shinei Nouzen
|
||||||
|
* @license MIT
|
||||||
|
* ======= • ======= • ======= • ======= • =======• =======
|
||||||
|
*/
|
||||||
|
|
||||||
|
const BASE = process.env.API_URL || "https://mirurotvapi.vercel.app/api";
|
||||||
|
|
||||||
|
const tests = [
|
||||||
|
{ name: "Health", url: "/health" },
|
||||||
|
{ name: "Stats", url: "/stats" },
|
||||||
|
{ name: "OpenAPI", url: "/openapi" },
|
||||||
|
{ name: "Search", url: "/search?query=naruto" },
|
||||||
|
{ name: "Suggestions", url: "/suggestions?query=naruto" },
|
||||||
|
{ name: "Filter", url: "/filter?genre=Action&per_page=1" },
|
||||||
|
{ name: "Trending", url: "/trending?per_page=1" },
|
||||||
|
{ name: "Popular", url: "/popular?per_page=1" },
|
||||||
|
{ name: "Upcoming", url: "/upcoming?per_page=1" },
|
||||||
|
{ name: "Recent", url: "/recent?per_page=1" },
|
||||||
|
{ name: "Spotlight", url: "/spotlight" },
|
||||||
|
{ name: "Schedule", url: "/schedule" },
|
||||||
|
{ name: "Info", url: "/info/20" },
|
||||||
|
{ name: "Characters", url: "/anime/20/characters" },
|
||||||
|
{ name: "Relations", url: "/anime/20/relations" },
|
||||||
|
{ name: "Recommendations", url: "/anime/20/recommendations" },
|
||||||
|
{ name: "Episodes", url: "/episodes/20" },
|
||||||
|
{ name: "Watch", url: "/watch/kiwi/20/sub/animepahe-1" },
|
||||||
|
];
|
||||||
|
|
||||||
|
let passed = 0;
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runAll() {
|
||||||
|
console.log(`\n🧪 Running ${tests.length} tests...\n`);
|
||||||
|
|
||||||
|
for (const test of tests) {
|
||||||
|
await runTest(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`\n📊 Results: ${passed} passed, ${failed} failed, ${tests.length} total\n`);
|
||||||
|
process.exit(failed > 0 ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
runAll();
|
||||||