Repository: akionii/Miruro Branch: main Commit: 5986e25ef6d8 Files: 70 Total size: 286.1 KB Directory structure: gitextract_c4uzzfr4/ ├── .eslintrc.cjs ├── .github/ │ ├── FUNDING.yml │ ├── SECURITY.md │ └── dependabot.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── api/ │ └── exchange-token.ts ├── functions/ │ └── exchange-token.js ├── index.html ├── package.json ├── public/ │ └── manifest.json ├── renovate.json ├── robots.txt ├── server/ │ ├── README.md │ └── server.ts ├── src/ │ ├── App.tsx │ ├── client/ │ │ ├── ApolloClient.tsx │ │ ├── authService.ts │ │ ├── useAuth.tsx │ │ └── userInfoTypes.ts │ ├── components/ │ │ ├── Cards/ │ │ │ ├── CardGrid.tsx │ │ │ └── CardItem.tsx │ │ ├── Home/ │ │ │ ├── EpisodeCard.tsx │ │ │ ├── HomeCarousel.tsx │ │ │ └── HomeSideBar.tsx │ │ ├── Navigation/ │ │ │ ├── DropSearch.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Navbar.tsx │ │ │ └── SearchFilters.tsx │ │ ├── Profile/ │ │ │ ├── Settings.tsx │ │ │ ├── SettingsProvider.tsx │ │ │ └── WatchingAnilist.tsx │ │ ├── ShortcutsPopup.tsx │ │ ├── Skeletons/ │ │ │ └── Skeletons.tsx │ │ ├── ThemeContext.tsx │ │ ├── Watch/ │ │ │ ├── AnimeDataList.tsx │ │ │ ├── EpisodeList.tsx │ │ │ ├── Seasons.tsx │ │ │ ├── Video/ │ │ │ │ ├── EmbedPlayer.tsx │ │ │ │ ├── MediaSource.tsx │ │ │ │ ├── Player.tsx │ │ │ │ └── PlayerStyles.css │ │ │ └── WatchAnimeData.tsx │ │ └── shared/ │ │ └── StatusIndicator.tsx │ ├── hooks/ │ │ ├── animeInterface.ts │ │ ├── useApi.ts │ │ ├── useCountdown.ts │ │ ├── useFilters.ts │ │ ├── useScroll.ts │ │ └── useTIme.ts │ ├── index.ts │ ├── main.tsx │ ├── pages/ │ │ ├── 404.tsx │ │ ├── About.tsx │ │ ├── Callback.tsx │ │ ├── Home.tsx │ │ ├── PolicyTerms.tsx │ │ ├── Profile.tsx │ │ ├── Search.tsx │ │ └── Watch.tsx │ ├── styles/ │ │ ├── animations.css │ │ ├── globals.css │ │ └── themes.css │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.cjs ================================================ module.exports = { root: true, env: { browser: true, es2020: true }, extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', ], ignorePatterns: ['dist', '.eslintrc.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], }, }; ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms # github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry # polar: # Replace with a single Polar username buy_me_a_coffee: # Replace with a single Buy Me a Coffee username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] ================================================ FILE: .github/SECURITY.md ================================================ # Security Policy ## Supported Versions | Version | Supported | | ------- | --------- | | 0.2.0 | ❔ | | 0.1.0 | ✅ | | < 0.1.0 | ❌ | ## Reporting a Vulnerability If you discover a security vulnerability, please report it by opening an [issue](https://github.com/Miruro-no-kuon/Miruro-no-Kuon/issues). To help us better understand and address the issue, please follow the template provided when creating a new issue. ### Reporting Process 1. **Open a new issue**: Clearly describe the vulnerability, providing as much detail as possible. 2. **Assessment**: Our team will assess the reported vulnerability and respond when available. 3. **Fix and Release**: If the vulnerability is accepted, we will work on fixing it and release a patch within a reasonable timeframe. ### Expectations - We will strive to keep you informed about the progress of your reported vulnerability. - If the vulnerability is accepted, it will be prioritized based on severity. - If the vulnerability is declined, we will provide a reason for the decision. - We encourage responsible disclosure, and we appreciate your efforts in keeping our project secure. ## Versioning Scheme We follow [Semantic Versioning](https://semver.org/) for our releases. Security updates will be applied to the latest minor version of the current major version. - **Major Version**: Significant changes, possibly breaking backward compatibility. - **Minor Version**: New features, enhancements, and backward-compatible bug fixes. - **Patch Version**: Backward-compatible bug fixes only. ## Contact For any questions or additional information regarding security, please contact miruro@proton.me ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: 'npm' # See documentation for possible values directory: '/' # Location of package manifests schedule: interval: 'weekly' ================================================ FILE: .gitignore ================================================ # Dependency directories node_modules/ jspm_packages/ .pnpm-store/ # Vite and Build outputs dist/ dist-ssr/ build/ out/ .temp/ # Bun .bun/ # TypeScript cache *.tsbuildinfo # Compiled binary addons (node-gyp) build/Release/ # Editor directories and files .idea/ .vscode/ *.sublime-workspace *.sublime-project # Operating System generated files .DS_Store ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # Package manager lock files package-lock.json yarn.lock pnpm-lock.yaml bun.lockb # dotenv environment variables files .env .env.local .env.development.local .env.test.local .env.production.local # Caches and logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* .cache/ .eslintcache .stylelintcache # Temporary files *.tmp *~ *.bak *.sw? *.swo *.swn *.swp *.orig ================================================ FILE: .prettierrc ================================================ { "semi": true, "trailingComma": "all", "singleQuote": true, "printWidth": 80, "tabWidth": 2, "useTabs": false, "endOfLine": "lf", "plugins": ["prettier-plugin-tailwindcss"], "arrowParens": "always", "bracketSpacing": true, "jsxSingleQuote": true, "bracketSameLine": false, "htmlWhitespaceSensitivity": "css", "vueIndentScriptAndStyle": false, "embeddedLanguageFormatting": "auto", "quoteProps": "as-needed", "overrides": [ { "files": "*.{ts,tsx}", "options": { "parser": "typescript" } }, { "files": "*.html", "options": { "printWidth": 120 } } ] } ================================================ FILE: LICENSE ================================================ CUSTOM ATTRIBUTION-NONCOMMERCIAL (CUSTOM BY-NC) LICENSE © 2024 Miruro no Kuon. All Rights Reserved. This software, licensed under the Custom BY-NC License, grants users the freedom to share, copy, distribute, and transmit the code, as well as to adapt, modify, transform, and build upon it. However, this freedom comes with specific terms: By using this software, you are required to provide appropriate credit to the original author(s) by including a visible and clear attribution in any distribution or derivative work. Attribution is a fundamental condition for utilizing or redistributing the code. Furthermore, this license explicitly prohibits the commercial use of the code or any derivative work based on it. Commercial purposes include, but are not limited to, activities that involve the sale, licensing, or exploitation of the software for financial gain. If you intend to use the code for commercial use or any purpose not explicitly covered by this license, please seek explicit permission from the author(s) by contacting them directly. The author(s) reserves the right to grant or deny permission based on individual circumstances. It is essential to note that this license does not grant any rights beyond what is explicitly stated here. Any use of the code not explicitly allowed by this license is strictly prohibited. For inquiries, additional permissions, or clarification on specific terms, please contact the author(s). ================================================ FILE: README.md ================================================

MIRURO

Logo

fork stars

## What is Miruro? Welcome to **Miruro**, your premier destination for all things anime! Explore a comprehensive collection of high-definition anime with a seamless and user-friendly interface powered by **[Consumet](https://github.com/consumet)**. Built using **React** and **Vite**, Miruro offers a cutting-edge, minimalist design that ensures both fast loading times and smooth navigation. Whether you're looking for the latest anime series or classic favorites, Miruro has you covered with an ad-free streaming experience that supports both English subtitles and dubbed versions. Additionally, you can download individual episodes without the hassle of creating an account, making your viewing experience as convenient as possible.
Features [View More] ### General - Sub/Dub Anime support - User-friendly & Mobile responsive - Anilist Sync - Light/Dark theme - Continue Watching Section ### Watch Page - **Player** - Autoplay next episode - Skip op/ed button
## Installation and Local Development ### 1. Clone this repository using ```bash git clone https://github.com/Miruro-no-kuon/Miruro.git ``` ```bash cd Miruro ``` ### 2. Installation ### Basic Pre-Requisites > [!TIP] > This platform is built on [Node.js](https://nodejs.org/) and utilizes [Bun](https://bun.sh/) to ensure the quickest response times achievable. While `npm` can also be used, the commands for npm would mirror those of Bun, simply substituting the specific commands accordingly. > Bun is now available on **Windows**, **Linux**, and **macOS**. Below are the installation commands for each operating system. ### Install Bun - Linux & macOS ```bash curl -fsSL https://bun.sh/install | bash ``` - Windows ```powershell powershell -c "irm bun.sh/install.ps1 | iex" ``` ### Verify installations - Check that both Node.js and Bun are correctly installed by running. ```bash node -v bun -v ``` ### Install Dependencies - You can use Bun to install dependencies quickly. If you prefer, `npm` can also be used with equivalent commands. ```bash bun install ``` ### Copy `.env.example` into `.env.local` in the root folder - `.env.local` & `.env` are both viable options, you can also set `.env.test.local`, `.env.development.local` or `.env.production.local` ```bash cp .env.example .env.local ``` ### 3. Run on development &/or production (npm also works) - Run on development mode ```bash bun run dev ``` - Run on production mode ```bash bun start ``` ## Self-Hosting Notice > [!CAUTION] > Self-hosting this application is **strictly limited to personal use only**. Commercial utilization is **prohibited**, and the inclusion of advertisements on your self-hosted website may lead to serious consequences, including **potential site takedown measures**. Ensure compliance to avoid any legal or operational issues. ## License This project is governed by a Custom BY-NC License. What does this entail? Simply put, you are permitted to utilize, distribute, and modify the code for non-commercial purposes. However, it is imperative that due credit is accorded to our platform. Any commercial utilization of this code is strictly prohibited. For comprehensive details, please refer to the [LICENSE](LICENSE) file. Should you have inquiries or require special permissions, do not hesitate to contact us. ## Star History [![Stargazers over time](https://starchart.cc/Miruro-no-kuon/Miruro.svg?variant=adaptive)](https://starchart.cc/Miruro-no-kuon/Miruro) ================================================ FILE: api/exchange-token.ts ================================================ import axios from 'axios'; import type { VercelRequest, VercelResponse } from '@vercel/node'; export default async function exchangeAccessToken(req: VercelRequest, res: VercelResponse) { if (req.method !== 'POST') { res.status(405).send('Method Not Allowed'); return; } const { code } = req.body; if (!code) { return res.status(400).send('Authorization code is required'); } const payload = { client_id: process.env.VITE_CLIENT_ID, client_secret: process.env.VITE_CLIENT_SECRET, code, grant_type: 'authorization_code', redirect_uri: process.env.VITE_REDIRECT_URI, }; const url = 'https://anilist.co/api/v2/oauth/token'; try { const response = await axios.post(url, payload, { headers: { 'Content-Type': 'application/json', 'Accept-Encoding': 'identity', }, }); if (response.data.access_token) { res.json({ accessToken: response.data.access_token }); } else { throw new Error('Access token not found in the response'); } } catch (error: unknown) { // First, check if it's an instance of Error if (error instanceof Error) { // Now you can safely read the message property const message = error.message; // If it's an axios error, it may have a response object const details = axios.isAxiosError(error) && error.response ? error.response.data : message; res.status(500).json({ error: 'Failed to exchange token', details, }); } else { // If it's not an Error object, handle it as a generic error res.status(500).json({ error: 'Failed to exchange token', details: 'An unknown error occurred', }); } } } ================================================ FILE: functions/exchange-token.js ================================================ export async function onRequest(context) { const url = new URL(context.request.url); const path = url.pathname; if (path === '/exchange-token') { return handleTokenExchange(context); } else { return new Response('Not found', { status: 404 }); } } async function handleTokenExchange(context) { const request = context.request; if (request.method !== 'POST') { return new Response('Method Not Allowed', { status: 405 }); } try { const data = await request.json(); const code = data.code; if (!code) { return new Response('Authorization code is required', { status: 400 }); } const payload = { client_id: context.env.VITE_CLIENT_ID, client_secret: context.env.VITE_CLIENT_SECRET, code, grant_type: 'authorization_code', redirect_uri: context.env.VITE_REDIRECT_URI, }; const apiResponse = await fetch('https://anilist.co/api/v2/oauth/token', { method: 'POST', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json', 'Accept-Encoding': 'identity', }, }); const responseBody = await apiResponse.text(); if (!apiResponse.ok) { console.error('API response error:', responseBody); throw new Error(`API responded with status: ${apiResponse.status}`); } const responseData = JSON.parse(responseBody); if (responseData.access_token) { return new Response( JSON.stringify({ accessToken: responseData.access_token }), { headers: { 'Content-Type': 'application.json' }, }, ); } else { console.error( 'Access token not found in the API response:', responseBody, ); throw new Error('Access token not found in the response'); } } catch (error) { console.error(`Error when handling token exchange: ${error}`); return new Response( JSON.stringify({ error: 'Failed to exchange token', details: error.message, }), { status: 500, headers: { 'Content-Type': 'application.json' }, }, ); } } ================================================ FILE: index.html ================================================ Miruro | Watch Anime Online, Free Anime Streaming
================================================ FILE: package.json ================================================ { "name": "miruro_no_kuon", "private": true, "version": "0.5.2", "type": "module", "scripts": { "dev": "vite --host", "build": "vite build", "preview": "vite preview", "start": "vite build && bun run ./server/server.ts", "lint": "eslint . --ext js,jsx,ts,tsx --report-unused-disable-directives --max-warnings 0", "format": "prettier --write ." }, "dependencies": { "@apollo/client": "^3.10.1", "@fortawesome/fontawesome-svg-core": "^6.5.1", "@fortawesome/free-solid-svg-icons": "^6.5.1", "@fortawesome/react-fontawesome": "^0.2.0", "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/lodash": "^4.17.0", "@types/uuid": "^9.0.8", "@vercel/analytics": "^1.2.2", "@vidstack/react": "next", "axios": "^1.6.8", "body-parser": "^1.20.2", "eslint": "8.x", "express": "^4.19.2", "graphql": "^16.8.1", "lodash": "^4.17.21", "lru-cache": "latest", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.14", "react": "^18.2.0", "react-dom": "^18.2.0", "react-ga4": "^2.1.0", "react-icons": "^5.0.1", "react-router-dom": "latest", "react-select": "^5.8.0", "styled-components": "^6.1.0", "swiper": "^11.0.7", "typescript": "^5.0.0", "uuid": "^9.0.1", "wrangler": "^3.52.0" }, "devDependencies": { "@types/bun": "latest", "@types/react": "^18.2.55", "@types/react-dom": "^18.2.19", "@types/react-slick": "^0.23.13", "@typescript-eslint/eslint-plugin": "^7.4.0", "@typescript-eslint/parser": "^7.4.0", "@vercel/node": "^3.0.27", "@vitejs/plugin-react": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "vite": "5.x" }, "module": "index.ts", "peerDependencies": { "typescript": "^5.0.0" } } ================================================ FILE: public/manifest.json ================================================ { "name": "Miruro", "short_name": "Miruro", "description": "Watch HD Anime for Free", "lang": "en", "background_color": "#080808", "display": "standalone", "orientation": "standalone", "scope": "/", "start_url": "/", "screenshots": [ { "src": "/preview.png", "sizes": "960x540", "type": "image/png", "form_factor": "wide", "label": "Wonder Widgets" }, { "src": "/icon-256x256.png", "sizes": "256x256", "type": "image/png", "form_factor": "narrow", "label": "Icon Widget" } ], "icons": [ { "src": "/favicon-16x16.png", "sizes": "16x16", "type": "image/png" }, { "src": "/favicon-32x32.png", "sizes": "32x32", "type": "image/png" }, { "src": "/favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" }, { "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }, { "src": "/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable any" }, { "src": "/icon-256x256.png", "sizes": "256x256", "type": "image/png", "purpose": "maskable any" }, { "src": "/icon-384x384.png", "sizes": "384x384", "type": "image/png", "purpose": "maskable any" }, { "src": "/icon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable any" }, { "src": "/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ] } ================================================ FILE: renovate.json ================================================ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended"] } ================================================ FILE: robots.txt ================================================ User-agent: * Disallow: / ================================================ FILE: server/README.md ================================================ # Server README This README provides an overview of the `server.ts` file, which is an Express server designed to serve static files, handle error logging, and provide instructions for running it using the Bun JavaScript runtime. ## `server.ts` Overview ℹ️ The `server.ts` file includes the following features: - Express server setup - Static file serving to serve files from the `dist` directory 📂 - Error logging for server-side errors 📝 ## Installation and Running 🛠️ To run the server, follow these steps: 1. Clone this repository to your local machine 📦 2. Install project dependencies: ```bash bun install ``` 3. Start the server: ```bash bun run server.ts ``` - The server will start running on by default. You can modify the `PORT` .env variable to change the port in `server.ts` as needed. ================================================ FILE: server/server.ts ================================================ import express from 'express'; import axios from 'axios'; import path from 'path'; import os from 'os'; import bodyParser from 'body-parser'; const app = express(); // Environment Configuration const PORT = process.env.VITE_PORT || 5173; const { VITE_CLIENT_ID: CLIENT_ID, VITE_CLIENT_SECRET: CLIENT_SECRET, VITE_REDIRECT_URI: REDIRECT_URI, } = process.env; // Directory paths for static assets const DIST_DIR = path.join(__dirname, '../dist'); const INDEX_FILE = path.join(DIST_DIR, 'index.html'); // Middleware for static assets and JSON parsing app.use(express.static(DIST_DIR)); app.use(express.json()); app.use(bodyParser.json()); // API Endpoint for exchanging authorization token const apiEndpoint = '/api/exchange-token'; app.post(apiEndpoint, async (req, res) => { const { code } = req.body; if (!code) { console.error('Authorization code is missing'); return res.status(400).send('Authorization code is required'); } const payload = { client_id: CLIENT_ID, client_secret: CLIENT_SECRET, code, grant_type: 'authorization_code', redirect_uri: REDIRECT_URI, }; const url = 'https://anilist.co/api/v2/oauth/token'; // Logging the request details console.log('Sending request to AniList API'); console.log('URL:', url); console.log('Payload:', payload); try { const response = await axios.post(url, payload, { headers: { 'Content-Type': 'application/json', 'Accept-Encoding': 'identity', }, }); // Logging the response details console.log('Received response from AniList API'); console.log('Response Status:', response.status); console.log('Response Data:', response.data); if (response.data.access_token) { res.json({ accessToken: response.data.access_token }); } else { throw new Error('Access token not found in the response'); } } catch (error) { console.error('Error during token exchange:', error.message); if (error.response) { console.error('Error Status:', error.response.status); console.error('Error Details:', error.response.data); } res.status(500).json({ error: 'Failed to exchange token', details: error.response?.data || error.message, }); } }); // Serve the main index.html for any non-API requests app.get('*', (req, res) => { res.sendFile(INDEX_FILE, (err) => { if (err) { console.error('Error serving index.html:', err); res.status(500).send('An error occurred while serving the application'); } }); }); // Utility to get the first non-internal IPv4 address function getLocalIpAddress() { const networkInterfaces = os.networkInterfaces(); for (const networkInterface of Object.values(networkInterfaces)) { const found = networkInterface?.find( (net) => net.family === 'IPv4' && !net.internal, ); if (found) return found.address; } return 'localhost'; } // Starting the server app.listen(PORT, () => { const ipAddress = getLocalIpAddress(); console.log( `Server is running at:\n- Localhost: http://localhost:${PORT}\n- Local IP: http://${ipAddress}:${PORT}`, ); }); ================================================ FILE: src/App.tsx ================================================ import { BrowserRouter as Router, Routes, Route, useLocation, } from 'react-router-dom'; import { useEffect } from 'react'; import { Profile, Navbar, ThemeProvider, Footer, Home, Watch, Search, Page404, About, PolicyTerms, ShortcutsPopup, ScrollToTop, usePreserveScrollOnReload, Callback, ApolloClientProvider, Settings, SettingsProvider, } from './index'; import { register } from 'swiper/element/bundle'; import { Analytics } from '@vercel/analytics/react'; import { AuthProvider } from './client/useAuth'; import ReactGA from 'react-ga4'; register(); function App() { usePreserveScrollOnReload(); const measurementId = import.meta.env.VITE_GA_MEASUREMENT_ID; useEffect(() => { if (measurementId) { ReactGA.initialize(measurementId); } }, [measurementId]); return (
} /> } /> } /> } /> } /> } /> } /> } /> } /> } /> } />