From 6e86adb0e623f6308631d3118143e220d38a0425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=81nis=20L?= Date: Sun, 19 Jul 2026 20:09:25 +0300 Subject: [PATCH] Initial commit --- Assets/Logo-2.png | Bin 0 -> 9540 bytes Assets/streamflix-1.png | Bin 0 -> 3833 bytes DOCUMENTATION.md | 337 ++++++ LICENSE | 674 +++++++++++ README.md | 271 +++++ build.js | 193 ++++ manifest.json | 56 + package-lock.json | 2235 ++++++++++++++++++++++++++++++++++++ package.json | 23 + providers/_template.js | 69 ++ providers/flixer.js | 5 + providers/miruro.js | 867 ++++++++++++++ server.js | 89 ++ src/_template/extractor.js | 32 + src/_template/http.js | 42 + src/_template/index.js | 29 + src/flixer/decoder.js | 281 +++++ src/flixer/extractor.js | 159 +++ src/flixer/http.js | 201 ++++ src/flixer/index.js | 17 + src/miruro/extractor.js | 379 ++++++ src/miruro/http.js | 153 +++ src/miruro/index.js | 29 + 23 files changed, 6141 insertions(+) create mode 100644 Assets/Logo-2.png create mode 100644 Assets/streamflix-1.png create mode 100644 DOCUMENTATION.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.js create mode 100644 manifest.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 providers/_template.js create mode 100644 providers/flixer.js create mode 100644 providers/miruro.js create mode 100644 server.js create mode 100644 src/_template/extractor.js create mode 100644 src/_template/http.js create mode 100644 src/_template/index.js create mode 100644 src/flixer/decoder.js create mode 100644 src/flixer/extractor.js create mode 100644 src/flixer/http.js create mode 100644 src/flixer/index.js create mode 100644 src/miruro/extractor.js create mode 100644 src/miruro/http.js create mode 100644 src/miruro/index.js diff --git a/Assets/Logo-2.png b/Assets/Logo-2.png new file mode 100644 index 0000000000000000000000000000000000000000..9ff5dd6e2fe3fb22e538c6cb3700508b9f1020ef GIT binary patch literal 9540 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 OWr9j8OI1jk1pFUdCE(xy literal 0 HcmV?d00001 diff --git a/Assets/streamflix-1.png b/Assets/streamflix-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5db25250f59d7931596fcd2671f3a749bff2822e GIT binary patch literal 3833 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 literal 0 HcmV?d00001 diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md new file mode 100644 index 0000000..aca2bc3 --- /dev/null +++ b/DOCUMENTATION.md @@ -0,0 +1,337 @@ +# Nuvio Provider Development Guide + +This comprehensive guide covers everything you need to know to build, debug, and publish streaming providers for the Nuvio app. + +## Table of Contents + +1. [Introduction](#1-introduction) +2. [Getting Started](#2-getting-started) + - [Setup](#setup) + - [Architecture](#architecture) +3. [The Provider Template](#3-the-provider-template) +4. [Development Workflows](#4-development-workflows) + - [Workflow A: Multi-File (Recommended)](#workflow-a-multi-file-recommended) + - [Workflow B: Single-File (Legacy/Simple)](#workflow-b-single-file-legacysimple) +5. [The Build System](#5-the-build-system) + - [Bundling Source Providers](#bundling-source-providers) + - [Transpiling Async/Await](#transpiling-asyncawait) + - [Watch Mode](#watch-mode) +6. [API Reference](#6-api-reference) +7. [Testing & Debugging](#7-testing--debugging) +8. [Publishing](#8-publishing) +9. [FAQ & Troubleshooting](#9-faq--troubleshooting) + +--- + +## 1. Introduction + +A **Nuvio Provider** is a JavaScript module responsible for finding video streams. When a user taps a movie in the app, the provider receives the media details (TMDB ID, title, etc.) and returns a list of playable URLs. + +Providers run locally on the user's device. The Nuvio app uses the **Hermes** JavaScript engine. + +**Crucial Limitation:** Hermes does not natively support `async/await` syntax inside dynamically loaded code (plugins). +**Our Solution:** We provide a build script that automatically transpiles your modern `async/await` code into generator functions that Hermes can execute safely. + +**Also Important (Runtime Differences):** Local Node.js tests can pass even when the provider fails in-app. +The Nuvio runtime is React Native + Hermes, so many Node-specific APIs/modules are not available (for example Node built-ins like `crypto`, and some crypto libraries that assume a Node/browser environment such as `node-forge`). +If your provider uses encryption/decryption or heavy parsing dependencies, always test it in the Nuvio app (Plugin Tester) even if it works locally. + +--- + +## 2. Getting Started + +### Setup + +1. **Clone the Repository** + ```bash + git clone https://github.com/tapframe/nuvio-providers.git + cd nuvio-providers + ``` + +2. **Install Dependencies** + This installs the build tools (`esbuild`) required for transpilation. + ```bash + npm install + ``` + +### Architecture + +- **`src/`**: The workspace for modern, multi-file providers. +- **`providers/`**: The distribution folder. The app reads files from here. **Do not edit these files manually if they were built from `src/`.** +- **`build.js`**: The utility script that builds and transpiles your code. + +--- + +## 3. The Provider Template + +We have included a starter template in `src/_template`. To create a new provider: + +1. **Copy the template** + ```bash + cp -r src/_template src/my-new-provider + ``` + +2. **Rename/Edit files** + - `src/my-new-provider/index.js`: The main entry point. + - `src/my-new-provider/http.js`: Helper for network requests. + - `src/my-new-provider/extractor.js`: Your scraping logic. + +--- + +## 4. Development Workflows + +### Workflow A: Multi-File (Recommended) + +Best for complex providers. You write modern code in `src/`, split across multiple files. + +1. **Develpop in `src/myprovider/`**. + You can use `import`/`export` and `async/await` freely. + ```javascript + // src/myprovider/index.js + import { getStream } from './extractor.js'; + + async function getStreams(tmdbId, ...) { + return await getStream(tmdbId); + } + ``` + +2. **Build** + ```bash + node build.js myprovider + ``` + This bundles everything into a single file at `providers/myprovider.js` and transpiles the async code. + +### Workflow B: Single-File (Legacy/Simple) + +If you have a simple script or are porting a provider from another project, you might have a single file in `providers/`. + +1. **Develop in `providers/myprovider.js`**. + If you use `async/await`, it will **crash** the app unless you process it. + +2. **Transpile** + Run the transpile command to convert the async code in-place: + ```bash + node build.js --transpile myprovider.js + ``` + This rewrites `providers/myprovider.js` with Hermes-compatible code. + +--- + +## 5. The Build System + +The `build.js` script is your primary tool. It handles two main jobs: +1. **Bundling**: Combines multiple files from `src/` into one. +2. **Transpiling**: Converts ES2017+ async/await into ES2016 Generators. + +### Bundling Source Providers + +Usage: `node build.js [provider_names...]` + +| Command | Description | +|---------|-------------| +| `node build.js` | Builds **ALL** providers found in `src/`. | +| `node build.js vidlink` | Builds only the `src/vidlink` provider. | +| `node build.js vidlink castle` | Builds multiple specific providers. | + +**Output**: Creates `providers/.js`. + +### Transpiling Async/Await (Single Files) + +If you have a standalone file in `providers/` that uses `async/await`, you must transpile it. + +Usage: `node build.js --transpile [filenames...]` + +| Command | Description | +|---------|-------------| +| `node build.js --transpile` | Scans `providers/` for single files using async and transpiles them all. | +| `node build.js --transpile old-scraper` | Transpiles `providers/old-scraper.js` in-place. | +| `node build.js --transpile file1 file2` | Transpiles multiple specific files. | + +**Note**: This overwrites the file with the transpiled version. The original source is lost unless you keep a backup or use Git. This is why **Workflow A (src folder)** is recommended, as it keeps your source code separate from the build artifact. + +### Watch Mode + +Automatically rebuilds source providers when you modify files in `src/`. + +```bash +npm run build:watch +``` + +### Minification + +By default, builds keep code readable for debugging. You can enable minification to reduce file size. + +Usage: `node build.js --minify [provider_names...]` + +| Command | Description | +|---------|-------------| +| `node build.js --minify` | Builds **ALL** providers with minification. | +| `node build.js --minify vidlink` | Builds only `vidlink` provider, minified. | +| `node build.js --minify vidlink castle` | Builds multiple providers, all minified. | + +#### Advantages of Minification + +- **Smaller File Size**: ~50% reduction for providers with heavy dependencies (e.g., using `node-forge`, `cheerio`). + - Example: A 1.0 MB unminified bundle becomes ~473 KB when minified. +- **Faster Load Time**: Smaller files load quicker in the Nuvio app and over network transfers. +- **Reduced Storage**: Less disk space consumed on user devices. +- **Production Ready**: Recommended for final releases. + +#### Disadvantages of Minification + +- **Hard to Debug**: Variable and function names are mangled (e.g., `getStreams()` → `u()`), making it difficult to troubleshoot errors from crash reports or logs. +- **Longer Build Time**: Minification adds a slight overhead to the build process. +- **Stack Traces Unreadable**: Error messages won't map back to original function names. + +#### Recommendation + +- **Development**: Use unminified builds (`node build.js`) for easier debugging. +- **Testing**: Test both minified and unminified versions before publishing. +- **Production/Release**: Use minified builds (`node build.js --minify`) for deployment to users. + +--- + +## 6. API Reference + +Your provider must export a `getStreams` function. + +```javascript +/* + * @param {string} tmdbId - The TMDB ID (e.g., "550") + * @param {string} mediaType - "movie" or "tv" + * @param {number} season - Season number (1-based), null for movies + * @param {number} episode - Episode number (1-based), null for movies + * @returns {Promise} - List of streams + */ +async function getStreams(tmdbId, mediaType, season, episode) { ... } +``` + +### Stream Object + +```javascript +{ + "name": "MyProvider", // Short identifier + "title": "1080p Stream", // Display name + "url": "https://server.com/...", // Playable URL + "quality": "1080p", // 4K, 1080p, 720p, CAM + "headers": { // (Optional) + "User-Agent": "Key for playback", + "Referer": "..." + } +} +``` + +--- + +## 7. Testing & Debugging + +While local Node.js scripts are useful for initial logic verification, providers must be tested within the Nuvio application to ensure compatibility with the Hermes engine and the app's runtime environment. + +### 7.1. Local Logic Verification (Node.js) + +Create a temporary test script (e.g., `test.js`) to verify your provider's scraping logic on your computer. + +```javascript +const { getStreams } = require('./providers/myprovider.js'); + +async function run() { + console.log("Fetching streams..."); + try { + const streams = await getStreams('550', 'movie'); // Fight Club + console.log(streams); + } catch (e) { + console.error(e); + } +} +run(); +``` + +Run it using: +```bash +node test.js +``` + +### 7.2. In-App Testing (Plugin Tester) + +The **Plugin Tester** is a dedicated developer tool within the Nuvio app that allows you to load, run, and debug providers directly on your device interactively. + +#### Prerequisites +1. **Get the App**: You need the **debug version** of Nuvio. + - **Download**: Get the latest `debug.apk` from the **Releases** tab on GitHub. + - **Build**: Or run `npx expo run:android` / `npx expo run:ios` locally. + > *Note: Production versions do not include the Plugin Tester.* + +2. Ensure your computer and mobile device are on the same Wi-Fi network. +3. Start the local development server in this repository: + ```bash + npm start + ``` + This serves your `providers/` directory and `manifest.json` over HTTP (e.g., `http://192.168.1.X:3000`). + +#### Accessing the Plugin Tester +1. Open the Nuvio application. +2. Navigate to **Settings**. +3. Scroll down to the **Developer Section** and select **Plugin Tester**. + +#### Testing Individual Providers +The "Individual Plugin" tab is designed for rapid iteration on a single provider script. + +> [!IMPORTANT] +> **Code Requirements:** You must use the **compiled/bundled** file (found in `providers/`) or a standalone **single-file** provider. +> The app **cannot** execute source files that use `import` to load other local files (e.g., from `src/`). If you are using the multi-file workflow, you must build your provider first (`node build.js myprovider`) and test the generated output file. + +1. **Load Source**: + - **From URL**: Enter the direct URL to your *compiled* provider file hosted by your local server (e.g., `http://192.168.1.5:3000/providers/myprovider.js`) and tap **Load**. + - **Direct Input**: Alternatively, paste your *compiled* provider code directly into the code editor. +2. **Parameters**: Set the test parameters (TMDB ID, Media Type, Season, Episode). +3. **Run Test**: Tap the **Run Test** button. +4. **View Results**: + - **Logs**: Check the "Logs" tab for `console.log` output and errors. + - **Results**: View the list of discovered streams in the "Results" tab. + - **Playback**: Tap the **Play** button on any stream result to verify that the URL is playable in the native player (KSPlayer on iOS, AndroidVideoPlayer on Android). + +#### Testing Repositories +The "Repo Tester" tab allows you to validate an entire plugin repository manifest. + +1. Enter your local manifest URL (e.g., `http://192.168.1.5:3000/manifest.json`). +2. Tap **Fetch Manifest** to load the list of available providers. +3. Tap **Test All** to run a connectivity test on all enabled providers in the manifest, or test specific providers individually. + +> [!NOTE] +> The Plugin Tester behaves exactly like the production app environment (Hermes), so if a provider works here, it will work for users. + +--- + +## 8. Publishing + +1. **Build your provider**: Ensure `providers/myprovider.js` is up to date. + ```bash + node build.js myprovider + ``` +2. **Update Manifest**: Add your provider entry to `manifest.json`. +3. **Commit & Push**: + ```bash + git add . + git commit -m "Add new provider" + git push + ``` + +Users can then use your raw GitHub repository URL to load the plugins in Nuvio. + +--- + +## 9. FAQ & Troubleshooting + +### Error: `SyntaxError: async functions are unsupported` +**Cause**: The app running on Hermes cannot execute `async function` directly in plugins. +**Fix**: You forgot to build/transpile. +- If using `src/`: Run `node build.js myprovider`. +- If using single file: Run `node build.js --transpile myprovider.js`. + +### Error: `fetch is not defined` (in local testing) +**Cause**: Node.js (before v18) doesn't have native `fetch`. +**Fix**: Use Node v18+, or our build environment handles this for the app. For local testing, ensure you are on a recent Node version. + +### The app crashes when loading my provider +**Cause**: Syntax error or unhandled exception at the root level. +**Fix**: Check your `index.js`. Ensure you are not doing heavy work (like networking) at the top level. All logic must be inside `getStreams`. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..12cc834 --- /dev/null +++ b/README.md @@ -0,0 +1,271 @@ +# Nuvio Providers + +A collection of streaming providers for the Nuvio app. Providers are JavaScript modules that fetch streams from various sources. + +📖 **[Read the Comprehensive Developer Guide](DOCUMENTATION.md)** + +## Quick Start + +### Using in Nuvio App + +1. Open **Nuvio** > **Settings** > **Plugins** +2. Add this repository URL: + ``` + https://raw.githubusercontent.com/yoruix/nuvio-providers/refs/heads/main/manifest.json + ``` +3. Refresh and enable the providers you want +4. **Developer Mode**: To test local changes, run `npm start` on your computer. + > ⚠️ **Important:** You must use the **development build** of Nuvio (`npx expo run:android` or `npx expo run:ios`). Some providers may work locally but fail in React Native. + - Go to **Settings** > **Developer** > **Plugin Tester** in the app. + - Enter your local server URL (e.g., `http://192.168.1.5:3000/manifest.json`). + - You can also test individual provider URLs here. + +--- + +## Project Structure + +``` +nuvio-providers/ +├── src/ # Source files (multi-file development) +│ ├── vixsrc/ +│ │ ├── index.js # Main entry point +│ │ ├── extractor.js # Stream extraction +│ │ ├── http.js # HTTP utilities +│ │ └── ... +│ └── uhdmovies/ +│ └── ... +│ +├── providers/ # Output directory (ready-to-use files) +│ ├── vixsrc.js # Bundled from src/vixsrc/ +│ ├── uhdmovies.js +│ └── ... +│ +├── manifest.json # Provider registry +├── build.js # Build script +└── package.json +``` + +--- + +## Development + +There are two ways to create providers: + +### Option 1: Single-File Provider + +For simple providers, you can create a single JavaScript file directly in the `providers/` directory. + +**Important:** The app's JavaScript engine (Hermes) has limitations with `async/await` in dynamic code. +- **Recommended**: Use Promise chains (`.then()`). +- **Alternative**: Use `async/await` and run the transpiler command (see below). + +**Example (Promise Chains):** +```javascript +// providers/myprovider.js + +function getStreams(tmdbId, mediaType, season, episode) { + console.log(`[MyProvider] Fetching ${mediaType} ${tmdbId}`); + + return fetch(`https://api.example.com/streams/${tmdbId}`) + .then(response => response.json()) + .then(data => { + return data.streams.map(s => ({ + name: "MyProvider", + title: s.title, + url: s.url, + quality: s.quality + })); + }) + .catch(error => { + console.error('[MyProvider] Error:', error.message); + return []; + }); +} + +module.exports = { getStreams }; +``` + +To register the provider, add it to `manifest.json`: +```json +{ + "id": "myprovider", + "name": "My Provider", + "filename": "providers/myprovider.js", + "supportedTypes": ["movie", "tv"], + "enabled": true +} +``` + +### Option 2: Multi-File Provider (Recommended) + +For complex providers, use the `src/` directory. This allows you to split code into multiple files. The build script automatically handles bundling and `async/await` transpilation. + +1. **Create source folder:** + ```bash + mkdir -p src/myprovider + ``` + +2. **Create entry point** (`src/myprovider/index.js`): + ```javascript + import { fetchPage } from './http.js'; + import { extractStreams } from './extractor.js'; + + // async/await is fully supported here + async function getStreams(tmdbId, mediaType, season, episode) { + const page = await fetchPage(tmdbId, mediaType, season, episode); + return extractStreams(page); + } + + module.exports = { getStreams }; + ``` + +3. **Build:** + ```bash + node build.js myprovider + ``` + +This generates `providers/myprovider.js`. + +--- + +## Building + +### Build Source Providers +Bundles files from `src//` into `providers/.js`. + +```bash +# Build specific provider +node build.js vixsrc + +# Build multiple +node build.js vixsrc uhdmovies + +# Build all source providers +node build.js +``` + +### Transpile Single-File Providers +If you wrote a single-file provider using `async/await`, you must transpile it for compatibility. + +```bash +# Transpile specific file +node build.js --transpile myprovider.js + +# Transpile all applicable files in providers/ +node build.js --transpile +``` + +### Watch Mode +Automatically rebuilds when files change. +```bash +npm run build:watch +``` + +--- + +## Testing + +Create a test script to identify issues before loading into the app. + +```javascript +// test-myprovider.js +const { getStreams } = require('./providers/myprovider.js'); + +async function test() { + console.log('Testing...'); + const streams = await getStreams('872585', 'movie'); // Oppenheimer ID + console.log('Streams found:', streams.length); +} + +test(); +``` + +Run with Node.js: +```bash +node test-myprovider.js +``` + +--- + +## Stream Object Format + +Providers must return an array of stream objects: + +```javascript +{ + name: "Provider Name", // Provider identifier + title: "1080p Stream", // Stream description + url: "https://...", // Direct stream URL (m3u8, mp4, mkv) + quality: "1080p", // Quality label + size: "2.5 GB", // Optional file size + headers: { // Optional headers for playback + "Referer": "https://source.com", + "User-Agent": "Mozilla/5.0..." + } +} +``` + +--- + +## Available Modules + +Providers have access to these modules via `require()`: + +| Module | Usage | +|--------|-------| +| `cheerio-without-node-native` | HTML parsing | +| `crypto-js` | Encryption/decryption | +| `axios` | HTTP requests | + +Native `fetch` and `console` are also available globally. + +--- + +## Manifest Options + +The `manifest.json` file controls provider settings. + +```json +{ + "id": "unique-id", + "name": "Display Name", + "description": "Short description", + "version": "1.0.0", + "author": "Your Name", + "supportedTypes": ["movie", "tv"], + "filename": "providers/file.js", + "enabled": true, + "logo": "https://url/to/logo.png", + "contentLanguage": ["en", "hi"], + "formats": ["mkv", "mp4"], + "limited": false, + "disabledPlatforms": ["ios"], + "supportsExternalPlayer": true +} +``` + +--- + +## Contributing + +1. **Fork the repository** +2. **Create a branch**: `git checkout -b add-myprovider` +3. **Develop and test** +4. **Build**: `node build.js myprovider` +5. **Commit**: `git commit -m "Add MyProvider"` +6. **Push and PR** + +--- + +## License + +This project is licensed under the **GNU General Public License v3.0**. + +--- + +## Disclaimer + +- **No content is hosted by this repository.** +- Providers fetch publicly available content from third-party websites. +- Users are responsible for compliance with local laws. +- For DMCA concerns, contact the actual content hosts. diff --git a/build.js b/build.js new file mode 100644 index 0000000..a155522 --- /dev/null +++ b/build.js @@ -0,0 +1,193 @@ +#!/usr/bin/env node + +/** + * Build script for nuvio-providers + * + * Bundles each provider from src// into a single file at providers/.js + * + * Usage: + * node build.js # Build all providers + * node build.js vixsrc # Build only vixsrc + * node build.js --minify # Build all with minification + * node build.js --watch # Watch mode (requires nodemon) + * node build.js --transpile # Transpile async/await in providers/ + */ + +const esbuild = require('esbuild'); +const fs = require('fs'); +const path = require('path'); + +const srcDir = path.join(__dirname, 'src'); +const outDir = path.join(__dirname, 'providers'); + +// Modules that the Nuvio app provides - don't bundle these +const EXTERNAL_MODULES = [ + 'cheerio-without-node-native', + 'react-native-cheerio', + 'cheerio', + 'crypto-js', + 'axios' +]; + +// Get provider names from command line or discover all +function getProvidersToBuild() { + const args = process.argv.slice(2).filter(arg => !arg.startsWith('-')); + + if (args.length > 0) { + return args; + } + + // Discover all provider folders in src/ + if (!fs.existsSync(srcDir)) { + console.error('❌ src/ directory not found. Create provider folders in src//'); + process.exit(1); + } + + return fs.readdirSync(srcDir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name); +} + +async function buildProvider(providerName, options = {}) { + const providerDir = path.join(srcDir, providerName); + const entryPoint = path.join(providerDir, 'index.js'); + const outFile = path.join(outDir, `${providerName}.js`); + + if (!fs.existsSync(entryPoint)) { + console.warn(`⚠️ Skipping ${providerName}: no src/${providerName}/index.js found`); + return false; + } + + try { + const result = await esbuild.build({ + entryPoints: [entryPoint], + bundle: true, + outfile: outFile, + format: 'cjs', // CommonJS for module.exports compatibility + platform: 'neutral', // Works in both browser and node-like environments + target: 'es2016', // Transpile async/await to generators for Hermes + minify: options.minify || false, // Minify if --minify flag is set + sourcemap: false, + external: EXTERNAL_MODULES, + banner: { + js: `/**\n * ${providerName} - Built from src/${providerName}/\n * Generated: ${new Date().toISOString()}\n */` + }, + logLevel: 'warning' + }); + + const stats = fs.statSync(outFile); + const sizeKB = (stats.size / 1024).toFixed(1); + const minifyIndicator = options.minify ? ' (minified)' : ''; + console.log(`✅ ${providerName}.js (${sizeKB} KB)${minifyIndicator}`); + return true; + } catch (err) { + console.error(`❌ Failed to build ${providerName}:`, err.message); + return false; + } +} + +// Transpile a single file in providers/ (for developers writing single-file providers with async) +async function transpileSingleFile(filename) { + const inputPath = path.join(outDir, filename); + + if (!fs.existsSync(inputPath)) { + console.warn(`⚠️ File not found: providers/${filename}`); + return false; + } + + // Read original file + const originalContent = fs.readFileSync(inputPath, 'utf-8'); + + // Check if it needs transpilation (has async/await) + if (!originalContent.includes('async ') && !originalContent.includes('await ')) { + console.log(`⏭️ ${filename} - no async/await, skipping`); + return true; + } + + try { + const result = await esbuild.transform(originalContent, { + loader: 'js', + target: 'es2016', // Transpile async/await to generators + format: 'cjs' + }); + + // Write transpiled content back + fs.writeFileSync(inputPath, result.code); + + const stats = fs.statSync(inputPath); + const sizeKB = (stats.size / 1024).toFixed(1); + console.log(`✅ ${filename} transpiled (${sizeKB} KB)`); + return true; + } catch (err) { + console.error(`❌ Failed to transpile ${filename}:`, err.message); + return false; + } +} + +async function main() { + const args = process.argv.slice(2); + const shouldMinify = args.includes('--minify'); + + // Handle --transpile flag for single-file providers + if (args.includes('--transpile')) { + const files = args.filter(a => a !== '--transpile' && !a.startsWith('-')); + + if (files.length === 0) { + // Transpile all .js files in providers/ that aren't from src/ + const srcProviders = fs.existsSync(srcDir) + ? fs.readdirSync(srcDir, { withFileTypes: true }) + .filter(d => d.isDirectory()) + .map(d => d.name + '.js') + : []; + + const allProviderFiles = fs.readdirSync(outDir) + .filter(f => f.endsWith('.js') && !srcProviders.includes(f)); + + console.log(`\n🔄 Transpiling ${allProviderFiles.length} single-file provider(s)...\n`); + + for (const file of allProviderFiles) { + await transpileSingleFile(file); + } + } else { + console.log(`\n🔄 Transpiling ${files.length} file(s)...\n`); + for (const file of files) { + const filename = file.endsWith('.js') ? file : file + '.js'; + await transpileSingleFile(filename); + } + } + return; + } + + const providers = getProvidersToBuild(); + + if (providers.length === 0) { + console.log('No providers found in src/ directory.'); + console.log('Create a provider: mkdir -p src/myprovider && touch src/myprovider/index.js'); + return; + } + + const minifyLabel = shouldMinify ? ' (minified)' : ''; + console.log(`\n📦 Building ${providers.length} provider(s)${minifyLabel}...\n`); + + // Ensure output directory exists + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir, { recursive: true }); + } + + let success = 0; + let failed = 0; + + for (const provider of providers) { + const result = await buildProvider(provider, { minify: shouldMinify }); + if (result) success++; + else failed++; + } + + console.log(`\n✨ Done! ${success} built, ${failed} skipped/failed\n`); +} + +main().catch(err => { + console.error('Build failed:', err); + process.exit(1); +}); + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..3c7ec28 --- /dev/null +++ b/manifest.json @@ -0,0 +1,56 @@ +{ + "name": "Nuvio Providers", + "version": "1.0.4", + "scrapers": [ + { + "id": "miruro", + "name": "Miruro", + "description": "Anime streams from Miruro with subbed and dubbed sources.", + "version": "1.0.0", + "author": "Jānis L", + "supportedTypes": [ + "movie", + "tv" + ], + "filename": "providers/miruro.js", + "enabled": true, + "hasSettings": false, + "formats": [ + "m3u8", + "mp4" + ], + "logo": "https://www.miruro.to/icon-light-1024x1024.png", + "contentLanguage": [ + "en", + "ja" + ], + "limited": false, + "disabledPlatforms": [], + "supportsExternalPlayer": true + }, + { + "id": "flixer", + "name": "Flixer", + "description": "Movies and TV streams from Flixer.", + "version": "1.0.2", + "author": "Jānis L", + "supportedTypes": [ + "movie", + "tv" + ], + "filename": "providers/flixer.js", + "enabled": true, + "hasSettings": false, + "formats": [ + "m3u8" + ], + "logo": "https://flixer.gd/assets/images/logo.png", + "contentLanguage": [ + "en" + ], + "limited": false, + "disabledPlatforms": [], + "supportsExternalPlayer": true + } + ] +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..fb6e59c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2235 @@ +{ + "name": "nuvio-providers", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "nuvio-providers", + "version": "1.0.0", + "license": "GPL-3.0", + "devDependencies": { + "cheerio": "^1.1.2", + "cheerio-without-node-native": "^0.20.2", + "esbuild": "^0.20.0", + "nodemon": "^3.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/abab": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", + "integrity": "sha512-I+Wi+qiE2kUXyrRhNsWv6XsjUTBJjSoVSctKNBfLG5zG/Xe7Rjbxf13+vqYHNTwHaFU+FtSlVxOCTiMEVtPv0A==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/acorn": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-2.7.0.tgz", + "integrity": "sha512-pXK8ez/pVjqFdAgBkF1YPVRacuLQ9EXBKaKWaeh58WNfMkCmZhOZzu+NtKSPD5PHmCCHheQ5cD29qM1K4QTxIg==", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", + "integrity": "sha512-j3/4pkfih8W4NK22gxVSXcEonTpAHOHh0hu5BoZrKcOsW/4oBPxTi4Yk3SAj+FhC1f3+bRTkXdm4019gw1vg9g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "acorn": "^2.1.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio-without-node-native": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/cheerio-without-node-native/-/cheerio-without-node-native-0.20.2.tgz", + "integrity": "sha512-TiXlwWtMZIYU9ujMWtvMiKIVw/aZcQ8G6kxvn53zr0fU12kXIXSs0ICH05LGzjAsmQxdoC/WBuN882MyTbLNrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2-without-node-native": "^3.9.0", + "lodash": "^4.1.0" + }, + "engines": { + "node": ">= 0.6" + }, + "optionalDependencies": { + "jsdom": "^7.0.2" + } + }, + "node_modules/cheerio-without-node-native/node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha512-dUQOBoqdR7QwV90WysXPLXG5LO7nhYBgiWVfxF80DKPF8zx1t/pUd2FYy73emg3zrjtM6dzmYgbHKfV2rxiHQA==", + "dev": true, + "license": "BSD-like", + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/cheerio-without-node-native/node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/cheerio-without-node-native/node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/cheerio-without-node-native/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/cheerio-without-node-native/node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw==", + "dev": true, + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/cheerio-without-node-native/node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/cssstyle": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", + "integrity": "sha512-FUpKc+1FNBsHUr9IsfSGCovr8VuGOiiuzlgCyppKBjJi2jYTOFLN3oiiNRMIvYqbFzF38mqKj4BgcevzU5/kIA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "cssom": "0.3.x" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter2": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-1.0.5.tgz", + "integrity": "sha512-EUFhWUYzqqBZlzBMI+dPU8rnKXfQZEUnitnccQuEIAnvWFHCpt3+4fts2+4dpxLtlsiseVXCMFg37KjYChSxpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/htmlparser2-without-node-native": { + "version": "3.9.2", + "resolved": "https://registry.npmjs.org/htmlparser2-without-node-native/-/htmlparser2-without-node-native-3.9.2.tgz", + "integrity": "sha512-+FplQXqmY5fRx6vCIp2P5urWaoBCpTNJMXnKP/6mNCcyb+AZWWJzA8D03peXfozlxDL+vpgLK5dJblqEgu8j6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "eventemitter2": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/htmlparser2-without-node-native/node_modules/dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "node_modules/htmlparser2-without-node-native/node_modules/dom-serializer/node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/htmlparser2-without-node-native/node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/htmlparser2-without-node-native/node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/htmlparser2-without-node-native/node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/htmlparser2-without-node-native/node_modules/domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/jsdom": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-7.2.2.tgz", + "integrity": "sha512-kYeYuos/pYp0V/V8VAoGnUc0va0UZjTjwCsldBFZNBrOi9Q5kUXrvsw6W5/lQllB7hKXBARC4HRk1Sfk4dPFtA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "abab": "^1.0.0", + "acorn": "^2.4.0", + "acorn-globals": "^1.0.4", + "cssom": ">= 0.3.0 < 0.4.0", + "cssstyle": ">= 0.2.29 < 0.3.0", + "escodegen": "^1.6.1", + "nwmatcher": ">= 1.3.7 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.55.0", + "sax": "^1.1.4", + "symbol-tree": ">= 3.1.0 < 4.0.0", + "tough-cookie": "^2.2.0", + "webidl-conversions": "^2.0.0", + "whatwg-url-compat": "~0.6.5", + "xml-name-validator": ">= 2.0.1 < 3.0.0" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", + "integrity": "sha512-w2jx/0tJzvgKwZa58sj2vAYq/S/K1QJfIB3cWYea/Iu1scFPDQQ3IQiVZTHWtRBwAjv2Yd7S/xeZf3XqLDb3bA==", + "dev": true, + "optional": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)", + "optional": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwmatcher": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", + "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true, + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "optional": true + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true, + "license": "Unlicense", + "optional": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.18.2.tgz", + "integrity": "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "license": "MIT", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/webidl-conversions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-2.0.1.tgz", + "integrity": "sha512-OZ7I/f0sM+T28T2/OXinNGfmvjm3KKptdyQy8NPRZyLfYBn+9vt72Bfr+uQaE9OvWyxJjQ5kHFygH2wOTUb76g==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url-compat": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/whatwg-url-compat/-/whatwg-url-compat-0.6.5.tgz", + "integrity": "sha512-vbg5+JVNwGtHRI3GheZGWrcUlxF9BXHbA80dLa+2XqJjlV/BK6upoi2j8dIRW9FGPUUyaMm7Hf1pTexHnsk85g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tr46": "~0.0.1" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xml-name-validator": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", + "integrity": "sha512-jRKe/iQYMyVJpzPH+3HL97Lgu5HrCfii+qSo+TfjKHtOnvbnvdVfMYrn9Q34YV81M2e5sviJlI6Ko9y+nByzvA==", + "dev": true, + "license": "WTFPL", + "optional": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..55b3798 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "nuvio-providers", + "version": "1.0.0", + "description": "Providers for Nuvio Streaming", + "scripts": { + "build": "node build.js", + "build:watch": "nodemon --watch src -e js,json --exec 'node build.js'", + "build:provider": "node build.js", + "start": "node server.js" + }, + "devDependencies": { + "cheerio": "^1.1.2", + "cheerio-without-node-native": "^0.20.2", + "esbuild": "^0.20.0", + "nodemon": "^3.0.0" + }, + "keywords": [ + "nuvio", + "streaming", + "providers" + ], + "license": "GPL-3.0" +} \ No newline at end of file diff --git a/providers/_template.js b/providers/_template.js new file mode 100644 index 0000000..44dbc47 --- /dev/null +++ b/providers/_template.js @@ -0,0 +1,69 @@ +/** + * _template - Built from src/_template/ + * Generated: 2026-07-19T17:05:24.210Z + */ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; +}; +var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __async = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; + +// src/_template/extractor.js +var import_cheerio_without_node_native = __toESM(require("cheerio-without-node-native")); +function extractStreams(tmdbId, mediaType, season, episode) { + return __async(this, null, function* () { + return []; + }); +} + +// src/_template/index.js +function getStreams(tmdbId, mediaType, season, episode) { + return __async(this, null, function* () { + try { + console.log(`[Template] Request: ${mediaType} ${tmdbId}`); + const streams = yield extractStreams(tmdbId, mediaType, season, episode); + return streams; + } catch (error) { + console.error(`[Template] Error: ${error.message}`); + return []; + } + }); +} +module.exports = { getStreams }; diff --git a/providers/flixer.js b/providers/flixer.js new file mode 100644 index 0000000..3277581 --- /dev/null +++ b/providers/flixer.js @@ -0,0 +1,5 @@ +/** + * flixer - Built from src/flixer/ + * Generated: 2026-07-19T17:05:50.362Z + */ +var se=Object.defineProperty,ie=Object.defineProperties;var ce=Object.getOwnPropertyDescriptors;var G=Object.getOwnPropertySymbols;var ae=Object.prototype.hasOwnProperty,le=Object.prototype.propertyIsEnumerable;var K=(e,r,t)=>r in e?se(e,r,{enumerable:!0,configurable:!0,writable:!0,value:t}):e[r]=t,B=(e,r)=>{for(var t in r||(r={}))ae.call(r,t)&&K(e,t,r[t]);if(G)for(var t of G(r))le.call(r,t)&&K(e,t,r[t]);return e},S=(e,r)=>ie(e,ce(r));var m=(e,r,t)=>new Promise((n,o)=>{var c=a=>{try{i(t.next(a))}catch(h){o(h)}},s=a=>{try{i(t.throw(a))}catch(h){o(h)}},i=a=>a.done?n(a.value):Promise.resolve(a.value).then(c,s);i((t=t.apply(e,r)).next())});var $="https://flixer.gd",k="https://plsdontscrapemelove.flixer.gd",R={Accept:"text/plain",Origin:$,Referer:`${$}/`,"User-Agent":"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"},b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",fe=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298];function v(e){let r=[];for(let t=0;t=55296&&n<=56319&&t+1=56320&&o<=57343&&(n=65536+(n-55296<<10)+o-56320,t++)}n<128?r.push(n):n<2048?r.push(192|n>>6,128|n&63):n<65536?r.push(224|n>>12,128|n>>6&63,128|n&63):r.push(240|n>>18,128|n>>12&63,128|n>>6&63,128|n&63)}return new Uint8Array(r)}function p(e,r){return e>>>r|e<<32-r}function E(e){let r=e.length*8,t=Math.ceil((e.length+9)/64)*64,n=new Uint8Array(t);n.set(e),n[e.length]=128;let o=Math.floor(r/4294967296),c=r>>>0,s=t-8;n[s]=o>>>24&255,n[s+1]=o>>>16&255,n[s+2]=o>>>8&255,n[s+3]=o&255,n[s+4]=c>>>24&255,n[s+5]=c>>>16&255,n[s+6]=c>>>8&255,n[s+7]=c&255;let i=[1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225],a=new Uint32Array(64);for(let l=0;l>>0}for(let x=16;x<64;x++){let y=p(a[x-15],7)^p(a[x-15],18)^a[x-15]>>>3,L=p(a[x-2],17)^p(a[x-2],19)^a[x-2]>>>10;a[x]=a[x-16]+y+a[x-7]+L>>>0}let[A,w,C,d,f,u,g,U]=i;for(let x=0;x<64;x++){let y=p(f,6)^p(f,11)^p(f,25),L=f&u^~f&g,j=U+y+L+fe[x]+a[x]>>>0,ne=p(A,2)^p(A,13)^p(A,22),re=A&w^A&C^w&C,oe=ne+re>>>0;U=g,g=u,u=f,f=d+j>>>0,d=C,C=w,w=A,A=j+oe>>>0}i[0]=i[0]+A>>>0,i[1]=i[1]+w>>>0,i[2]=i[2]+C>>>0,i[3]=i[3]+d>>>0,i[4]=i[4]+f>>>0,i[5]=i[5]+u>>>0,i[6]=i[6]+g>>>0,i[7]=i[7]+U>>>0}let h=new Uint8Array(32);for(let l=0;l>>24&255,h[l*4+1]=i[l]>>>16&255,h[l*4+2]=i[l]>>>8&255,h[l*4+3]=i[l]&255;return h}function P(e,r){let t=e instanceof Uint8Array?e:v(String(e));t.length>64&&(t=E(t));let n=r instanceof Uint8Array?r:v(String(r)),o=new Uint8Array(64+n.length),c=new Uint8Array(96);for(let s=0;s<64;s++){let i=s>>18&63],r+=b[c>>>12&63],r+=n?b[c>>>6&63]:"=",r+=o?b[c&63]:"="}return r}function ue(){let e=new Uint8Array(16);for(let r=0;r>>10,56320|o&1023))}return r}function D(e,r){let t=0;for(let n=0;n<8;n++)r&1&&(t^=e),e=(e<<1^(e&128?283:0))&255,r>>>=1;return t}function me(e){if(!e)return 0;let r=1,t=e,n=254;for(;n;)n&1&&(r=D(r,t)),t=D(t,t),n>>>=1;return r}function M(e,r){return(e<>>8-r)&255}var F=(()=>{let e=new Uint8Array(256);for(let r=0;r<256;r++){let t=me(r);e[r]=t^M(t,1)^M(t,2)^M(t,3)^M(t,4)^99}return e})();function Ce(e){if(e.length!==32)throw new Error("Invalid AES-256 key");let r=new Uint32Array(60);for(let n=0;n<8;n++){let o=n*4;r[n]=(e[o]<<24|e[o+1]<<16|e[o+2]<<8|e[o+3])>>>0}let t=1;for(let n=8;n>>24)>>>0,o=(F[o>>>24]<<24|F[o>>>16&255]<<16|F[o>>>8&255]<<8|F[o&255])>>>0,o^=t<<24,t=(t<<1^(t&128?283:0))&255):n%8===4&&(o=(F[o>>>24]<<24|F[o>>>16&255]<<16|F[o>>>8&255]<<8|F[o&255])>>>0),r[n]=(r[n-8]^o)>>>0}return r}function W(e,r,t){for(let n=0;n<4;n++){let o=r[t*4+n];e[n*4]^=o>>>24,e[n*4+1]^=o>>>16&255,e[n*4+2]^=o>>>8&255,e[n*4+3]^=o&255}}function O(e,r){let t=new Uint8Array(e),n=new Uint8Array(16);W(t,r,0);for(let o=1;o<=14;o++){for(let c=0;c<16;c++)t[c]=F[t[c]];for(let c=0;c<4;c++)for(let s=0;s<4;s++)n[c+s*4]=t[c+(s+c&3)*4];if(t.set(n),o!==14)for(let c=0;c<4;c++){let s=c*4,i=t[s],a=t[s+1],h=t[s+2],l=t[s+3],A=i^a^h^l;t[s]^=A^D(i^a,2),t[s+1]^=A^D(a^h,2),t[s+2]^=A^D(h^l,2),t[s+3]^=A^D(l^i,2)}W(t,r,o)}return t}function X(e){let r=new Uint32Array(4);for(let t=0;t<4;t++){let n=t*4;r[t]=(e[n]<<24|e[n+1]<<16|e[n+2]<<8|e[n+3])>>>0}return r}function we(e){let r=new Uint8Array(16);for(let t=0;t<4;t++)r[t*4]=e[t]>>>24,r[t*4+1]=e[t]>>>16&255,r[t*4+2]=e[t]>>>8&255,r[t*4+3]=e[t]&255;return r}function V(e,r){let t=new Uint32Array(4),n=new Uint32Array(r);for(let o=0;o<128;o++){if(e[o>>>5]>>>31-(o&31)&1)for(let s=0;s<4;s++)t[s]^=n[s];let c=n[3]&1;n[3]=(n[3]>>>1|(n[2]&1)<<31)>>>0,n[2]=(n[2]>>>1|(n[1]&1)<<31)>>>0,n[1]=(n[1]>>>1|(n[0]&1)<<31)>>>0,n[0]>>>=1,c&&(n[0]=(n[0]^3774873600)>>>0)}return t}function pe(e,r){let t=new Uint32Array(4);for(let i=0;i>>0),c.setUint32(8,Math.floor(o/4294967296));let s=X(n);for(let i=0;i<4;i++)t[i]^=s[i];return V(t,r)}function Fe(e){for(let r=15;r>=12&&(e[r]=e[r]+1&255,!e[r]);r--);}function ye(e,r){if(e.length<28)throw new Error("Invalid encrypted payload");let t=e.subarray(0,12),n=e.subarray(12,e.length-16),o=e.subarray(e.length-16),c=Ce(r),s=new Uint8Array(16),i=X(O(s,c)),a=new Uint8Array(16);a.set(t),a[15]=1;let h=we(pe(n,i)),l=O(a,c),A=0;for(let d=0;d<16;d++)A|=h[d]^l[d]^o[d];if(A)throw new Error("Invalid encrypted payload tag");let w=new Uint8Array(n.length),C=new Uint8Array(a);for(let d=0;dr.includes(t)).concat(r.filter(t=>!Z.includes(t)))}function De(e,r){if(e&&Array.isArray(e.sources)){let n=e.sources.find(o=>o&&String(o.server||"").toLowerCase()===r)||e.sources.find(o=>o&&o.url);return n&&typeof n.url=="string"?n.url.trim():""}if(e&&e.sources&&typeof e.sources=="object"){let t=e.sources.file||e.sources.url;return typeof t=="string"?t.trim():""}return""}function $e(e){let r=String(e).match(/(?:^|[^0-9])(2160|1440|1080|720|480|360)p?(?:[^0-9]|$)/i);return r?`${r[1]}p`:"Auto"}function be(e,r,t,n){return m(this,null,function*(){let o=yield H(t,r);return yield _(e,{headers:S(B({},o),{"X-Only-Sources":"1","X-Server":n})})})}function te(e,r,t,n){return m(this,null,function*(){let o=String(e||"").trim();if(r!=="movie"&&r!=="tv")return[];if(r==="tv"&&(!Number.isInteger(Number(t))||!Number.isInteger(Number(n))||Number(t)<1||Number(n)<1))return[];let c=yield Ue(o,r);if(!c)return[];J();let s=Q(),i=Se(c,r,Number(t),Number(n)),a=`${k}${i}`,h=yield H(s,i),l=yield _(a,{headers:S(B({},h),{bW90aGFmYWth:"1"})}),A=ve(yield ee(l,s)),w=yield Promise.all(A.map(f=>m(this,null,function*(){try{return{server:f,encrypted:yield be(a,i,s,f)}}catch(u){return console.warn(`[Flixer] ${f} request failed: ${u&&u.message?u.message:String(u)}`),{server:f,encrypted:""}}}))),C=[],d={};for(let f of w)if(f.encrypted)try{let u=De(yield ee(f.encrypted,s),f.server);if(!u||d[u])continue;d[u]=!0;let g=Ee[f.server]||f.server.charAt(0).toUpperCase()+f.server.slice(1),U=$e(u);C.push({name:`Flixer \xB7 ${g}`,title:`${g} \xB7 ${U}`,url:u,quality:U,headers:{Referer:`${$}/`,Origin:$,"User-Agent":R["User-Agent"]}})}catch(u){let g=u&&u.message?u.message:String(u);/^E5[678]$/.test(g)||console.warn(`[Flixer] ${f.server} decode failed: ${g}`)}return C})}function Re(e,r,t,n){return m(this,null,function*(){try{console.log(`[Flixer] Request: ${r} ${e}`);let o=yield te(e,r,t,n);return console.log(`[Flixer] Found ${o.length} stream(s)`),o}catch(o){return console.error(`[Flixer] Error: ${o.message}`),[]}})}module.exports={getStreams:Re}; diff --git a/providers/miruro.js b/providers/miruro.js new file mode 100644 index 0000000..8744f4c --- /dev/null +++ b/providers/miruro.js @@ -0,0 +1,867 @@ +/** + * miruro - Built from src/miruro/ + * Generated: 2026-07-19T17:01:21.823Z + */ +var __defProp = Object.defineProperty; +var __defProps = Object.defineProperties; +var __getOwnPropDescs = Object.getOwnPropertyDescriptors; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; +}; +var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); +var __async = (__this, __arguments, generator) => { + return new Promise((resolve, reject) => { + var fulfilled = (value) => { + try { + step(generator.next(value)); + } catch (e) { + reject(e); + } + }; + var rejected = (value) => { + try { + step(generator.throw(value)); + } catch (e) { + reject(e); + } + }; + var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); + step((generator = generator.apply(__this, __arguments)).next()); + }); +}; + +// node_modules/fflate/esm/browser.js +var u8 = Uint8Array; +var u16 = Uint16Array; +var i32 = Int32Array; +var fleb = new u8([ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 2, + 2, + 2, + 2, + 3, + 3, + 3, + 3, + 4, + 4, + 4, + 4, + 5, + 5, + 5, + 5, + 0, + /* unused */ + 0, + 0, + /* impossible */ + 0 +]); +var fdeb = new u8([ + 0, + 0, + 0, + 0, + 1, + 1, + 2, + 2, + 3, + 3, + 4, + 4, + 5, + 5, + 6, + 6, + 7, + 7, + 8, + 8, + 9, + 9, + 10, + 10, + 11, + 11, + 12, + 12, + 13, + 13, + /* unused */ + 0, + 0 +]); +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +var freb = function(eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; + } + var r = new i32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = j - b[i] << 5 | i; + } + } + return { b, r }; +}; +var _a = freb(fleb, 2); +var fl = _a.b; +var revfl = _a.r; +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0); +var fd = _b.b; +var revfd = _b.r; +var rev = new u16(32768); +for (i = 0; i < 32768; ++i) { + x = (i & 43690) >> 1 | (i & 21845) << 1; + x = (x & 52428) >> 2 | (x & 13107) << 2; + x = (x & 61680) >> 4 | (x & 3855) << 4; + rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1; +} +var x; +var i; +var hMap = function(cd, mb, r) { + var s = cd.length; + var i = 0; + var l = new u16(mb); + for (; i < s; ++i) { + if (cd[i]) + ++l[cd[i] - 1]; + } + var le = new u16(mb); + for (i = 1; i < mb; ++i) { + le[i] = le[i - 1] + l[i - 1] << 1; + } + var co; + if (r) { + co = new u16(1 << mb); + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + if (cd[i]) { + var sv = i << 4 | cd[i]; + var r_1 = mb - cd[i]; + var v = le[cd[i] - 1]++ << r_1; + for (var m = v | (1 << r_1) - 1; v <= m; ++v) { + co[rev[v] >> rvb] = sv; + } + } + } + } else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i]; + } + } + } + return co; +}; +var flt = new u8(288); +for (i = 0; i < 144; ++i) + flt[i] = 8; +var i; +for (i = 144; i < 256; ++i) + flt[i] = 9; +var i; +for (i = 256; i < 280; ++i) + flt[i] = 7; +var i; +for (i = 280; i < 288; ++i) + flt[i] = 8; +var i; +var fdt = new u8(32); +for (i = 0; i < 32; ++i) + fdt[i] = 5; +var i; +var flrm = /* @__PURE__ */ hMap(flt, 9, 1); +var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1); +var max = function(a) { + var m = a[0]; + for (var i = 1; i < a.length; ++i) { + if (a[i] > m) + m = a[i]; + } + return m; +}; +var bits = function(d, p, m) { + var o = p / 8 | 0; + return (d[o] | d[o + 1] << 8) >> (p & 7) & m; +}; +var bits16 = function(d, p) { + var o = p / 8 | 0; + return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7); +}; +var shft = function(p) { + return (p + 7) / 8 | 0; +}; +var slc = function(v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + return new u8(v.subarray(s, e)); +}; +var ec = [ + "unexpected EOF", + "invalid block type", + "invalid length/literal", + "invalid distance", + "stream finished", + "no stream handler", + , + // determined by compression function + "no callback", + "invalid UTF-8 data", + "extra field too long", + "date not in range 1980-2099", + "filename too long", + "stream finishing", + "invalid zip data" + // determined by unknown compression method +]; +var err = function(ind, msg, nt) { + var e = new Error(msg || ec[ind]); + e.code = ind; + if (Error.captureStackTrace) + Error.captureStackTrace(e, err); + if (!nt) + throw e; + return e; +}; +var inflt = function(dat, st, buf, dict) { + var sl = dat.length, dl = dict ? dict.length : 0; + if (!sl || st.f && !st.l) + return buf || new u8(0); + var noBuf = !buf; + var resize = noBuf || st.i != 2; + var noSt = st.i; + if (noBuf) + buf = new u8(sl * 3); + var cbuf = function(l2) { + var bl = buf.length; + if (l2 > bl) { + var nbuf = new u8(Math.max(bl * 2, l2)); + nbuf.set(buf); + buf = nbuf; + } + }; + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + var tbts = sl * 8; + do { + if (!lm) { + final = bits(dat, pos, 1); + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l; + if (t > sl) { + if (noSt) + err(0); + break; + } + if (resize) + cbuf(bt + l); + buf.set(dat.subarray(s, t), bt); + st.b = bt += l, st.p = pos = t * 8, st.f = final; + continue; + } else if (type == 1) + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + else if (type == 2) { + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + var ldt = new u8(tl); + var clt = new u8(19); + for (var i = 0; i < hcLen; ++i) { + clt[clim[i]] = bits(dat, pos + i * 3, 7); + } + pos += hcLen * 3; + var clb = max(clt), clbmsk = (1 << clb) - 1; + var clm = hMap(clt, clb, 1); + for (var i = 0; i < tl; ) { + var r = clm[bits(dat, pos, clbmsk)]; + pos += r & 15; + var s = r >> 4; + if (s < 16) { + ldt[i++] = s; + } else { + var c = 0, n = 0; + if (s == 16) + n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; + else if (s == 17) + n = 3 + bits(dat, pos, 7), pos += 3; + else if (s == 18) + n = 11 + bits(dat, pos, 127), pos += 7; + while (n--) + ldt[i++] = c; + } + } + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + lbt = max(lt); + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); + } else + err(1); + if (pos > tbts) { + if (noSt) + err(0); + break; + } + } + if (resize) + cbuf(bt + 131072); + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (; ; lpos = pos) { + var c = lm[bits16(dat, pos) & lms], sym = c >> 4; + pos += c & 15; + if (pos > tbts) { + if (noSt) + err(0); + break; + } + if (!c) + err(2); + if (sym < 256) + buf[bt++] = sym; + else if (sym == 256) { + lpos = pos, lm = null; + break; + } else { + var add = sym - 254; + if (sym > 264) { + var i = sym - 257, b = fleb[i]; + add = bits(dat, pos, (1 << b) - 1) + fl[i]; + pos += b; + } + var d = dm[bits16(dat, pos) & dms], dsym = d >> 4; + if (!d) + err(3); + pos += d & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & (1 << b) - 1, pos += b; + } + if (pos > tbts) { + if (noSt) + err(0); + break; + } + if (resize) + cbuf(bt + 131072); + var end = bt + add; + if (bt < dt) { + var shift = dl - dt, dend = Math.min(dt, end); + if (shift + bt < 0) + err(3); + for (; bt < dend; ++bt) + buf[bt] = dict[shift + bt]; + } + for (; bt < end; ++bt) + buf[bt] = buf[bt - dt]; + } + } + st.l = lm, st.p = lpos, st.b = bt, st.f = final; + if (lm) + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } while (!final); + return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt); +}; +var et = /* @__PURE__ */ new u8(0); +var gzs = function(d) { + if (d[0] != 31 || d[1] != 139 || d[2] != 8) + err(6, "invalid gzip data"); + var flg = d[3]; + var st = 10; + if (flg & 4) + st += (d[10] | d[11] << 8) + 2; + for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) + ; + return st + (flg & 2); +}; +var gzl = function(d) { + var l = d.length; + return (d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16 | d[l - 1] << 24) >>> 0; +}; +function gunzipSync(data, opts) { + var st = gzs(data); + if (st + 8 > data.length) + err(6, "invalid gzip data"); + return inflt(data.subarray(st, -8), { i: 2 }, opts && opts.out || new u8(gzl(data)), opts && opts.dictionary); +} +var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder(); +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; +} catch (e) { +} +var dutf8 = function(d) { + for (var r = "", i = 0; ; ) { + var c = d[i++]; + var eb = (c > 127) + (c > 223) + (c > 239); + if (i + eb > d.length) + return { s: r, r: slc(d, i - 1) }; + if (!eb) + r += String.fromCharCode(c); + else if (eb == 3) { + c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | d[i++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023); + } else if (eb & 1) + r += String.fromCharCode((c & 31) << 6 | d[i++] & 63); + else + r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63); + } +}; +function strFromU8(dat, latin1) { + if (latin1) { + var r = ""; + for (var i = 0; i < dat.length; i += 16384) + r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); + return r; + } else if (td) { + return td.decode(dat); + } else { + var _a2 = dutf8(dat), s = _a2.s, r = _a2.r; + if (r.length) + err(8); + return s; + } +} + +// src/miruro/http.js +var BASE_URL = "https://www.miruro.to"; +var HEADERS = { + "Accept": "*/*", + "Referer": `${BASE_URL}/`, + "User-Agent": "Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 Chrome/140.0.0.0 Mobile Safari/537.36", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin" +}; +var PIPE_OBFUSCATION_KEY = hexToBytes("71951034f8fbcf53d89db52ceb3dc22c"); +var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +function hexToBytes(value) { + const bytes = new Uint8Array(value.length / 2); + for (let index = 0; index < bytes.length; index++) { + bytes[index] = parseInt(value.slice(index * 2, index * 2 + 2), 16); + } + return bytes; +} +function encodeBase64Url(value) { + let result = ""; + for (let index = 0; index < value.length; index += 3) { + const first = value.charCodeAt(index); + const hasSecond = index + 1 < value.length; + const hasThird = index + 2 < value.length; + const second = hasSecond ? value.charCodeAt(index + 1) : 0; + const third = hasThird ? value.charCodeAt(index + 2) : 0; + const chunk = first << 16 | second << 8 | third; + result += BASE64_ALPHABET[chunk >> 18 & 63]; + result += BASE64_ALPHABET[chunk >> 12 & 63]; + result += hasSecond ? BASE64_ALPHABET[chunk >> 6 & 63] : "="; + result += hasThird ? BASE64_ALPHABET[chunk & 63] : "="; + } + return result.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, ""); +} +function decodeBase64Url(value) { + const normalized = value.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""); + const outputLength = Math.floor(normalized.length * 6 / 8); + const output = new Uint8Array(outputLength); + let buffer = 0; + let bits2 = 0; + let outputIndex = 0; + for (let index = 0; index < normalized.length; index++) { + const code = BASE64_ALPHABET.indexOf(normalized[index]); + if (code < 0) { + continue; + } + buffer = buffer << 6 | code; + bits2 += 6; + if (bits2 >= 8) { + bits2 -= 8; + output[outputIndex++] = buffer >> bits2 & 255; + } + } + return outputIndex === output.length ? output : output.slice(0, outputIndex); +} +function decodeSecurePayload(raw, obfuscationVersion) { + if (!obfuscationVersion) { + return JSON.parse(raw); + } + const bytes = decodeBase64Url(raw); + if (obfuscationVersion === "2") { + for (let index = 0; index < bytes.length; index++) { + bytes[index] ^= PIPE_OBFUSCATION_KEY[index % PIPE_OBFUSCATION_KEY.length]; + } + } + return JSON.parse(strFromU8(gunzipSync(bytes))); +} +function mergeHeaders(headers) { + return __spreadValues(__spreadValues({}, HEADERS), headers || {}); +} +function fetchText(_0) { + return __async(this, arguments, function* (url, options = {}) { + const response = yield fetch(url, __spreadProps(__spreadValues({}, options), { + headers: mergeHeaders(options.headers) + })); + if (!response.ok) { + throw new Error(`HTTP ${response.status} for ${url}`); + } + return yield response.text(); + }); +} +function fetchJson(_0) { + return __async(this, arguments, function* (url, options = {}) { + const raw = yield fetchText(url, options); + return JSON.parse(raw); + }); +} +function secureGet(_0) { + return __async(this, arguments, function* (path, query = {}) { + const envelope = encodeBase64Url(JSON.stringify({ + path, + method: "GET", + query, + body: null + })); + const url = `${BASE_URL}/api/secure/pipe?e=${envelope}`; + let response; + for (let attempt = 0; attempt < 2; attempt++) { + response = yield fetch(url, { headers: HEADERS }); + if (response.ok || response.status !== 403 && response.status !== 429 && response.status < 500) { + break; + } + } + if (!response || !response.ok) { + throw new Error(`Miruro API returned HTTP ${response ? response.status : "unknown"}`); + } + const raw = yield response.text(); + const version = response.headers && response.headers.get ? response.headers.get("x-obfuscated") : null; + return decodeSecurePayload(raw.trim(), version); + }); +} + +// src/miruro/extractor.js +var ANIMAP_URL = "https://animap.id/api/map"; +var ANILIST_URL = "https://graphql.anilist.co"; +var PROVIDERS = [ + { id: "bonk", label: "Bonk", subCategory: "ssub" }, + { id: "kiwi", label: "Kiwi", subCategory: "sub" }, + { id: "hop", label: "Hop", subCategory: "ssub" }, + { id: "ally", label: "Ally", subCategory: "sub" }, + { id: "pewe", label: "Pewe", subCategory: "sub" }, + { id: "bee", label: "Bee", subCategory: "ssub" }, + { id: "moo", label: "Moo", subCategory: "sub" } +]; +function asNumber(value) { + const number = Number(value); + return Number.isFinite(number) ? number : null; +} +function parseIdentifier(value) { + const id = String(value || "").trim(); + if (/^tt\d+$/i.test(id)) { + return { service: "imdb", value: id.toLowerCase() }; + } + if (/^\d+$/.test(id)) { + return { service: "tmdb", value: id }; + } + return null; +} +function recordMatchesIdentifier(record, identifier, mediaType) { + const expectedType = mediaType === "movie" ? "movie" : "tv"; + if (identifier.service === "imdb") { + const imdbMatches = (record.imdb_id || []).some((id) => String(id).toLowerCase() === identifier.value); + const typeMatches = (record.tmdb_id || []).length === 0 || (record.tmdb_id || []).some((mapping) => mapping.type === expectedType); + return imdbMatches && typeMatches; + } + return (record.tmdb_id || []).some((mapping) => String(mapping.id) === identifier.value && mapping.type === expectedType); +} +function getAniListIds(records, identifier, mediaType) { + const ids = []; + for (const record of Array.isArray(records) ? records : []) { + if (!recordMatchesIdentifier(record, identifier, mediaType)) { + continue; + } + for (const id of record.anilist_id || []) { + const numericId = asNumber(id); + if (numericId && !ids.includes(numericId)) { + ids.push(numericId); + } + } + } + return ids; +} +function getLocalEpisode(mappings, mediaType, season, episode) { + if (!mappings) { + return null; + } + if (mediaType === "movie") { + return 1; + } + const requestedSeason = asNumber(season) || 1; + const requestedEpisode = asNumber(episode); + if (!requestedEpisode || requestedEpisode < 1) { + return null; + } + const mappedSeason = mappings.tmdbSeason || mappings.defaultTvdbSeason; + if (mappedSeason === "absolute") { + return requestedSeason === 1 ? requestedEpisode : null; + } + if (mappedSeason != null && String(mappedSeason) !== String(requestedSeason)) { + return null; + } + const offset = asNumber(mappings.tmdbOffset) || asNumber(mappings.episodeOffset) || 0; + const episodeCount = asNumber(mappings.episodes); + if (requestedEpisode <= offset || episodeCount && requestedEpisode > offset + episodeCount) { + return null; + } + return requestedEpisode - offset; +} +function getAniListSequel(anilistId) { + return __async(this, null, function* () { + const response = yield fetchJson(ANILIST_URL, { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/json" + }, + body: JSON.stringify({ + query: ` + query ($id: Int!) { + Media(id: $id, type: ANIME) { + relations { + edges { + relationType + node { + id + type + format + } + } + } + } + } + `, + variables: { id: anilistId } + }) + }); + const edges = response && response.data && response.data.Media && response.data.Media.relations ? response.data.Media.relations.edges : []; + const sequel = (edges || []).find((edge) => edge.relationType === "SEQUEL" && edge.node && edge.node.type === "ANIME" && ["TV", "TV_SHORT", "ONA"].includes(edge.node.format)); + return sequel ? asNumber(sequel.node.id) : null; + }); +} +function getSeasonAniListId(anilistId, season) { + return __async(this, null, function* () { + const requestedSeason = asNumber(season) || 1; + let currentId = anilistId; + for (let currentSeason = 1; currentSeason < requestedSeason; currentSeason++) { + const sequelId = yield getAniListSequel(currentId); + if (!sequelId || sequelId === currentId) { + return null; + } + currentId = sequelId; + } + return currentId; + }); +} +function getSequelEpisode(payload, mediaType, season, episode) { + return getLocalEpisode(payload.mappings, mediaType, season, episode) || getLocalEpisode(payload.mappings, mediaType, 1, episode); +} +function resolveEpisodePayload(mediaId, mediaType, season, episode) { + return __async(this, null, function* () { + const identifier = parseIdentifier(mediaId); + if (!identifier) { + return null; + } + let mappingRecords; + try { + mappingRecords = yield fetchJson( + `${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`, + { headers: { "Accept": "application/json" } } + ); + } catch (error) { + console.warn(`[Miruro] No anime mapping for ${mediaId}: ${error.message}`); + return null; + } + const aniListIds = getAniListIds(mappingRecords, identifier, mediaType); + if (aniListIds.length === 0) { + return null; + } + if (aniListIds.length === 1) { + const anilistId = aniListIds[0]; + if (mediaType === "tv" && (asNumber(season) || 1) > 1) { + try { + const seasonAniListId = yield getSeasonAniListId(anilistId, season); + if (seasonAniListId && seasonAniListId !== anilistId) { + const payload2 = yield secureGet("episodes", { anilistId: seasonAniListId }); + const localEpisode2 = getSequelEpisode(payload2, mediaType, season, episode); + if (localEpisode2) { + return { anilistId: seasonAniListId, localEpisode: localEpisode2, payload: payload2 }; + } + } + } catch (error) { + console.warn(`[Miruro] Season ${season} mapping fallback failed: ${error.message}`); + } + } + const payload = yield secureGet("episodes", { anilistId }); + const localEpisode = getLocalEpisode(payload.mappings, mediaType, season, episode); + return localEpisode ? { anilistId, localEpisode, payload } : null; + } + for (const anilistId of aniListIds) { + try { + const info = yield secureGet(`info/${anilistId}`); + const localEpisode = getLocalEpisode(info.mappings, mediaType, season, episode); + if (localEpisode) { + const payload = yield secureGet("episodes", { anilistId }); + return { anilistId, localEpisode, payload }; + } + } catch (error) { + console.warn(`[Miruro] Skipping AniList ${anilistId}: ${error.message}`); + } + } + return null; + }); +} +function findEpisode(providerPayload, variant, episodeNumber) { + const episodes = providerPayload && providerPayload.episodes ? providerPayload.episodes[variant] : null; + if (!Array.isArray(episodes)) { + return null; + } + return episodes.find((item) => Number(item.number) === Number(episodeNumber)) || null; +} +function getOrigin(url) { + const match = typeof url === "string" ? url.match(/^(https?:\/\/[^/]+)/i) : null; + return match ? match[1] : BASE_URL; +} +function getQuality(stream) { + const value = String(stream.quality || stream.server || "HD"); + const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i); + if (resolution) { + return `${resolution[1]}p`; + } + return /4k/i.test(value) ? "4K" : "HD"; +} +function isPlayableStream(stream) { + const type = String(stream && stream.type || "").toLowerCase(); + return !!(stream && stream.url) && (type === "hls" || type === "m3u8" || type === "mp4" || type === "application/x-mpegurl"); +} +function normalizeStreams(sourcePayload, provider, variant) { + const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0; + const language = variant === "dub" ? "DUB" : "SUB"; + return (sourcePayload.streams || []).filter(isPlayableStream).map((stream) => { + const referer = stream.referer || BASE_URL; + const server = stream.server || getQuality(stream); + const subtitleLabel = subtitleCount > 0 && variant !== "dub" ? " \xB7 CC" : ""; + return { + // Nuvio's source picker prominently displays `name`, so include the + // Miruro server and audio variant here instead of only in `title`. + name: `Miruro \xB7 ${provider.label} \xB7 ${language}`, + title: `[${language}] ${provider.label} \xB7 ${server}${subtitleLabel}`, + url: stream.url, + quality: getQuality(stream), + headers: { + "Referer": referer, + "Origin": getOrigin(referer), + "User-Agent": HEADERS["User-Agent"] + } + }; + }); +} +function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) { + return __async(this, null, function* () { + const requests = []; + const subEpisode = findEpisode(providerPayload, "sub", episodeNumber); + const dubEpisode = findEpisode(providerPayload, "dub", episodeNumber); + if (subEpisode) { + requests.push({ episode: subEpisode, variant: "sub", category: provider.subCategory }); + } + if (dubEpisode) { + requests.push({ episode: dubEpisode, variant: "dub", category: "dub" }); + } + const results = yield Promise.all(requests.map((request) => __async(this, null, function* () { + try { + const sources = yield secureGet("sources", { + episodeId: request.episode.id, + provider: provider.id, + category: request.category, + anilistId + }); + return normalizeStreams(sources, provider, request.variant); + } catch (error) { + console.warn(`[Miruro] ${provider.label} ${request.variant} failed: ${error.message}`); + return []; + } + }))); + return results.flat(); + }); +} +function extractStreams(tmdbId, mediaType, season, episode) { + return __async(this, null, function* () { + if (!tmdbId || mediaType !== "movie" && mediaType !== "tv") { + return []; + } + const resolved = yield resolveEpisodePayload(tmdbId, mediaType, season, episode); + if (!resolved || !resolved.payload || !resolved.payload.providers) { + return []; + } + const providerResults = yield Promise.all(PROVIDERS.map((provider) => { + const payload = resolved.payload.providers[provider.id]; + return payload ? getProviderStreams(provider, payload, resolved.anilistId, resolved.localEpisode) : []; + })); + const streams = providerResults.flat(); + const seen = /* @__PURE__ */ new Set(); + return streams.filter((stream) => { + if (seen.has(stream.url)) { + return false; + } + seen.add(stream.url); + return true; + }); + }); +} + +// src/miruro/index.js +function getStreams(tmdbId, mediaType, season, episode) { + return __async(this, null, function* () { + try { + const episodeLabel = mediaType === "tv" ? ` S${season}E${episode}` : ""; + console.log(`[Miruro] Request: ${mediaType} ${tmdbId}${episodeLabel}`); + const streams = yield extractStreams(tmdbId, mediaType, season, episode); + console.log(`[Miruro] Found ${streams.length} stream(s)`); + return streams; + } catch (error) { + console.error(`[Miruro] Error: ${error.message}`); + return []; + } + }); +} +module.exports = { getStreams }; diff --git a/server.js b/server.js new file mode 100644 index 0000000..95e15b9 --- /dev/null +++ b/server.js @@ -0,0 +1,89 @@ +const http = require('http'); +const fs = require('fs'); +const path = require('path'); +const os = require('os'); + +const PORT = 3000; + +function getLocalIp() { + const interfaces = os.networkInterfaces(); + for (const name of Object.keys(interfaces)) { + for (const iface of interfaces[name]) { + if (iface.family === 'IPv4' && !iface.internal) { + return iface.address; + } + } + } + return 'localhost'; +} + +const mimeTypes = { + '.json': 'application/json', + '.js': 'application/javascript', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.ico': 'image/x-icon', + '.html': 'text/html', +}; + +const server = http.createServer((req, res) => { + console.log(`${req.method} ${req.url}`); + + // Handle CORS + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + + if (req.method === 'OPTIONS') { + res.writeHead(200); + res.end(); + return; + } + + // Prepare file path + let filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url); + + // Security check: prevent directory traversal + if (!filePath.startsWith(__dirname)) { + res.writeHead(403); + res.end('Forbidden'); + return; + } + + const videoExtensions = ['.mp4', '.mkv', '.avi', '.mov']; + + const extname = path.extname(filePath); + let contentType = mimeTypes[extname] || 'application/octet-stream'; + if (videoExtensions.includes(extname)) { + contentType = "video/mp4"; // Defaulting to mp4 for video files for simplicity + } + + + fs.readFile(filePath, (err, content) => { + if (err) { + if (err.code === 'ENOENT') { + // If asking for root and index.html doesn't exist, allow checking specific files + if (req.url === '/') { + res.writeHead(200, { 'Content-Type': 'text/plain' }); + res.end('Nuvio Providers Server Running. Access /manifest.json to see the manifest.'); + return; + } + res.writeHead(404); + res.end(`File not found: ${req.url}`); + } else { + res.writeHead(500); + res.end(`Server Error: ${err.code}`); + } + } else { + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content, 'utf-8'); + } + }); +}); + +server.listen(PORT, () => { + const ip = getLocalIp(); + console.log(`\n🚀 Server running at: http://${ip}:${PORT}/`); + console.log(`📝 Manifest URL: http://${ip}:${PORT}/manifest.json`); + console.log('Press Ctrl+C to stop\n'); +}); diff --git a/src/_template/extractor.js b/src/_template/extractor.js new file mode 100644 index 0000000..b534229 --- /dev/null +++ b/src/_template/extractor.js @@ -0,0 +1,32 @@ +/** + * Extractor Logic + * This file handles parsing HTML/JSON to find video streams. + */ + +import { fetchText, HEADERS } from './http.js'; +import cheerio from 'cheerio-without-node-native'; + +export async function extractStreams(tmdbId, mediaType, season, episode) { + // 1. Construct the search or video URL + // const url = `https://example.com/watch/${tmdbId}`; + + // 2. Fetch content + // const html = await fetchText(url); + + // 3. Parse with Cheerio + // const $ = cheerio.load(html); + // const videoUrl = $('video source').attr('src'); + + // 4. Return streams + // if (videoUrl) { + // return [{ + // name: "MyTemplateProvider", + // title: "1080p Stream", + // url: videoUrl, + // quality: "1080p", + // headers: HEADERS + // }]; + // } + + return []; +} diff --git a/src/_template/http.js b/src/_template/http.js new file mode 100644 index 0000000..38f1aff --- /dev/null +++ b/src/_template/http.js @@ -0,0 +1,42 @@ +/** + * HTTP Utilities + * Use this file for network requests and headers. + */ + +export const HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36", + // Add other common headers like 'Referer' if needed +}; + +/** + * Fetch text content from a URL + * @param {string} url + * @param {object} options + */ +export async function fetchText(url, options = {}) { + console.log(`[Template] Fetching: ${url}`); + + const response = await fetch(url, { + headers: { + ...HEADERS, + ...options.headers + }, + ...options + }); + + if (!response.ok) { + throw new Error(`HTTP error ${response.status} for ${url}`); + } + + return await response.text(); +} + +/** + * Fetch JSON content from a URL + * @param {string} url + * @param {object} options + */ +export async function fetchJson(url, options = {}) { + const raw = await fetchText(url, options); + return JSON.parse(raw); +} diff --git a/src/_template/index.js b/src/_template/index.js new file mode 100644 index 0000000..ade3f6b --- /dev/null +++ b/src/_template/index.js @@ -0,0 +1,29 @@ +/** + * Template Provider + * Main entry point. + */ + +import { extractStreams } from './extractor.js'; + +/** + * Main function called by Nuvio + * @param {string} tmdbId - TMDB ID of the media + * @param {string} mediaType - 'movie' or 'tv' + * @param {number} season - Season number (for TV) + * @param {number} episode - Episode number (for TV) + */ +async function getStreams(tmdbId, mediaType, season, episode) { + try { + console.log(`[Template] Request: ${mediaType} ${tmdbId}`); + + // Call your extraction logic + const streams = await extractStreams(tmdbId, mediaType, season, episode); + + return streams; + } catch (error) { + console.error(`[Template] Error: ${error.message}`); + return []; + } +} + +module.exports = { getStreams }; diff --git a/src/flixer/decoder.js b/src/flixer/decoder.js new file mode 100644 index 0000000..57da8b0 --- /dev/null +++ b/src/flixer/decoder.js @@ -0,0 +1,281 @@ +/** Pure-JavaScript replacement for Flixer's img_data WebAssembly module. */ + +import { encodeUtf8, hmacSha256, sha256 } from './http.js'; + +const CANVAS_FINGERPRINT = 'iVBORw0KGgoAAAANSUhEUgAAAMgAAAAyCAYAAAAZUZThAAAQAE'; + +function concatBytes(...values) { + let length = 0; + for (const value of values) length += value.length; + const result = new Uint8Array(length); + let offset = 0; + for (const value of values) { + result.set(value, offset); + offset += value.length; + } + return result; +} + +function toHex(bytes) { + let result = ''; + for (const value of bytes) result += value.toString(16).padStart(2, '0'); + return result; +} + +function decodeBase64(value) { + const binary = atob(value); + const bytes = new Uint8Array(binary.length); + for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index); + return bytes; +} + +function decodeUtf8(bytes) { + let result = ''; + for (let index = 0; index < bytes.length;) { + const first = bytes[index++]; + let codePoint; + if (first < 0x80) codePoint = first; + else if ((first & 0xE0) === 0xC0) codePoint = ((first & 0x1F) << 6) | (bytes[index++] & 0x3F); + else if ((first & 0xF0) === 0xE0) { + codePoint = ((first & 0x0F) << 12) | ((bytes[index++] & 0x3F) << 6) | (bytes[index++] & 0x3F); + } else { + codePoint = ((first & 7) << 18) | ((bytes[index++] & 0x3F) << 12) | + ((bytes[index++] & 0x3F) << 6) | (bytes[index++] & 0x3F); + } + if (codePoint <= 0xFFFF) result += String.fromCharCode(codePoint); + else { + codePoint -= 0x10000; + result += String.fromCharCode(0xD800 | (codePoint >>> 10), 0xDC00 | (codePoint & 0x3FF)); + } + } + return result; +} + +function multiplyByte(first, second) { + let result = 0; + for (let index = 0; index < 8; index++) { + if (second & 1) result ^= first; + first = ((first << 1) ^ ((first & 0x80) ? 0x11B : 0)) & 255; + second >>>= 1; + } + return result; +} + +function inverseByte(value) { + if (!value) return 0; + let result = 1; + let base = value; + let power = 254; + while (power) { + if (power & 1) result = multiplyByte(result, base); + base = multiplyByte(base, base); + power >>>= 1; + } + return result; +} + +function rotateByte(value, amount) { + return ((value << amount) | (value >>> (8 - amount))) & 255; +} + +const AES_SBOX = (() => { + const result = new Uint8Array(256); + for (let index = 0; index < 256; index++) { + const value = inverseByte(index); + result[index] = value ^ rotateByte(value, 1) ^ rotateByte(value, 2) ^ + rotateByte(value, 3) ^ rotateByte(value, 4) ^ 0x63; + } + return result; +})(); + +function expandAes256Key(key) { + if (key.length !== 32) throw new Error('Invalid AES-256 key'); + const words = new Uint32Array(60); + for (let index = 0; index < 8; index++) { + const offset = index * 4; + words[index] = ((key[offset] << 24) | (key[offset + 1] << 16) | + (key[offset + 2] << 8) | key[offset + 3]) >>> 0; + } + + let roundConstant = 1; + for (let index = 8; index < words.length; index++) { + let temporary = words[index - 1]; + if (index % 8 === 0) { + temporary = ((temporary << 8) | (temporary >>> 24)) >>> 0; + temporary = ((AES_SBOX[temporary >>> 24] << 24) | (AES_SBOX[(temporary >>> 16) & 255] << 16) | + (AES_SBOX[(temporary >>> 8) & 255] << 8) | AES_SBOX[temporary & 255]) >>> 0; + temporary ^= roundConstant << 24; + roundConstant = ((roundConstant << 1) ^ ((roundConstant & 0x80) ? 0x11B : 0)) & 255; + } else if (index % 8 === 4) { + temporary = ((AES_SBOX[temporary >>> 24] << 24) | (AES_SBOX[(temporary >>> 16) & 255] << 16) | + (AES_SBOX[(temporary >>> 8) & 255] << 8) | AES_SBOX[temporary & 255]) >>> 0; + } + words[index] = (words[index - 8] ^ temporary) >>> 0; + } + return words; +} + +function addRoundKey(state, words, round) { + for (let column = 0; column < 4; column++) { + const word = words[round * 4 + column]; + state[column * 4] ^= word >>> 24; + state[column * 4 + 1] ^= (word >>> 16) & 255; + state[column * 4 + 2] ^= (word >>> 8) & 255; + state[column * 4 + 3] ^= word & 255; + } +} + +function encryptAesBlock(input, words) { + const state = new Uint8Array(input); + const shifted = new Uint8Array(16); + addRoundKey(state, words, 0); + + for (let round = 1; round <= 14; round++) { + for (let index = 0; index < 16; index++) state[index] = AES_SBOX[state[index]]; + for (let row = 0; row < 4; row++) { + for (let column = 0; column < 4; column++) { + shifted[row + column * 4] = state[row + ((column + row) & 3) * 4]; + } + } + state.set(shifted); + + if (round !== 14) { + for (let column = 0; column < 4; column++) { + const offset = column * 4; + const first = state[offset]; + const second = state[offset + 1]; + const third = state[offset + 2]; + const fourth = state[offset + 3]; + const combined = first ^ second ^ third ^ fourth; + state[offset] ^= combined ^ multiplyByte(first ^ second, 2); + state[offset + 1] ^= combined ^ multiplyByte(second ^ third, 2); + state[offset + 2] ^= combined ^ multiplyByte(third ^ fourth, 2); + state[offset + 3] ^= combined ^ multiplyByte(fourth ^ first, 2); + } + } + addRoundKey(state, words, round); + } + return state; +} + +function bytesToWords(bytes) { + const result = new Uint32Array(4); + for (let index = 0; index < 4; index++) { + const offset = index * 4; + result[index] = ((bytes[offset] << 24) | (bytes[offset + 1] << 16) | + (bytes[offset + 2] << 8) | bytes[offset + 3]) >>> 0; + } + return result; +} + +function wordsToBytes(words) { + const result = new Uint8Array(16); + for (let index = 0; index < 4; index++) { + result[index * 4] = words[index] >>> 24; + result[index * 4 + 1] = (words[index] >>> 16) & 255; + result[index * 4 + 2] = (words[index] >>> 8) & 255; + result[index * 4 + 3] = words[index] & 255; + } + return result; +} + +function multiplyGhash(first, second) { + const result = new Uint32Array(4); + const value = new Uint32Array(second); + for (let bit = 0; bit < 128; bit++) { + if ((first[bit >>> 5] >>> (31 - (bit & 31))) & 1) { + for (let index = 0; index < 4; index++) result[index] ^= value[index]; + } + const lowBit = value[3] & 1; + value[3] = ((value[3] >>> 1) | ((value[2] & 1) << 31)) >>> 0; + value[2] = ((value[2] >>> 1) | ((value[1] & 1) << 31)) >>> 0; + value[1] = ((value[1] >>> 1) | ((value[0] & 1) << 31)) >>> 0; + value[0] >>>= 1; + if (lowBit) value[0] = (value[0] ^ 0xE1000000) >>> 0; + } + return result; +} + +function calculateGhash(ciphertext, hashKey) { + let state = new Uint32Array(4); + for (let offset = 0; offset < ciphertext.length; offset += 16) { + const block = new Uint8Array(16); + block.set(ciphertext.subarray(offset, Math.min(offset + 16, ciphertext.length))); + const words = bytesToWords(block); + for (let index = 0; index < 4; index++) state[index] ^= words[index]; + state = multiplyGhash(state, hashKey); + } + + const lengthBlock = new Uint8Array(16); + const bitLength = ciphertext.length * 8; + const view = new DataView(lengthBlock.buffer); + view.setUint32(12, bitLength >>> 0); + view.setUint32(8, Math.floor(bitLength / 0x100000000)); + const lengths = bytesToWords(lengthBlock); + for (let index = 0; index < 4; index++) state[index] ^= lengths[index]; + return multiplyGhash(state, hashKey); +} + +function incrementCounter(counter) { + for (let index = 15; index >= 12; index--) { + counter[index] = (counter[index] + 1) & 255; + if (counter[index]) break; + } +} + +function decryptAesGcm(payload, key) { + if (payload.length < 28) throw new Error('Invalid encrypted payload'); + const nonce = payload.subarray(0, 12); + const ciphertext = payload.subarray(12, payload.length - 16); + const expectedTag = payload.subarray(payload.length - 16); + const words = expandAes256Key(key); + const zero = new Uint8Array(16); + const hashKey = bytesToWords(encryptAesBlock(zero, words)); + const initialCounter = new Uint8Array(16); + initialCounter.set(nonce); + initialCounter[15] = 1; + + const authentication = wordsToBytes(calculateGhash(ciphertext, hashKey)); + const encryptedCounter = encryptAesBlock(initialCounter, words); + let mismatch = 0; + for (let index = 0; index < 16; index++) mismatch |= (authentication[index] ^ encryptedCounter[index]) ^ expectedTag[index]; + if (mismatch) throw new Error('Invalid encrypted payload tag'); + + const output = new Uint8Array(ciphertext.length); + const counter = new Uint8Array(initialCounter); + for (let offset = 0; offset < ciphertext.length; offset += 16) { + incrementCounter(counter); + const stream = encryptAesBlock(counter, words); + const length = Math.min(16, ciphertext.length - offset); + for (let index = 0; index < length; index++) output[offset + index] = ciphertext[offset + index] ^ stream[index]; + } + return output; +} + +function derivePayloadKey(apiKey, now) { + const hour = Math.floor(now / 3600000); + const dynamic = new Uint8Array(16); + for (let index = 0; index < dynamic.length; index++) { + dynamic[index] = (index + 1) ^ (Math.floor(hour / (2 ** (index & 7))) & 255); + } + + let derived = concatBytes(encodeUtf8(apiKey), dynamic); + for (let iteration = 0; iteration < 600; iteration++) derived = sha256(derived); + const timeBucket = Math.floor(now / 300000); + return hmacSha256(derived, `${timeBucket}${apiKey}`); +} + +export function initDecoder() { + return true; +} + +export function get_img_key() { + const timestamp = Math.floor(Date.now() / 1000); + const fingerprint = `1365x768:24:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36:Linux x86_64:en-US:-180:${timestamp}:${CANVAS_FINGERPRINT}`; + return toHex(sha256(encodeUtf8(toHex(sha256(encodeUtf8(fingerprint)))))); +} + +export function process_img_data(value, apiKey) { + const key = derivePayloadKey(apiKey, Date.now()); + return decodeUtf8(decryptAesGcm(decodeBase64(value), key)); +} diff --git a/src/flixer/extractor.js b/src/flixer/extractor.js new file mode 100644 index 0000000..69c80ce --- /dev/null +++ b/src/flixer/extractor.js @@ -0,0 +1,159 @@ +/** Resolve a TMDB title through Flixer's signed source API. */ + +import { get_img_key, initDecoder, process_img_data } from './decoder.js'; +import { API_URL, createSecureHeaders, fetchText, HEADERS, SITE_URL } from './http.js'; + +const NATO_ORDER = [ + 'alpha', 'bravo', 'charlie', 'delta', 'echo', 'foxtrot', 'golf', 'hotel', 'india', + 'juliet', 'kilo', 'lima', 'mike', 'november', 'oscar', 'papa', 'quebec', 'romeo', + 'sierra', 'tango', 'uniform', 'victor', 'whiskey', 'xray', 'yankee', 'zulu' +]; + +const SERVER_LABELS = { + bravo: 'Balder', delta: 'Dionysus', echo: 'Eros', golf: 'Gaia', hotel: 'Hades', + india: 'Isis', juliet: 'Juno', kilo: 'Kronos', lima: 'Loki' +}; + +const TMDB_ID_CACHE = {}; + +async function resolveTmdbId(id, mediaType) { + if (/^\d+$/.test(id)) return id; + if (!/^tt\d+$/i.test(id)) return null; + + const cacheKey = `${mediaType}:${id.toLowerCase()}`; + if (TMDB_ID_CACHE[cacheKey]) return TMDB_ID_CACHE[cacheKey]; + + const catalogType = mediaType === 'movie' ? 'movie' : 'series'; + try { + const raw = await fetchText(`https://v3-cinemeta.strem.io/meta/${catalogType}/${encodeURIComponent(id)}.json`, { + headers: { 'Accept': 'application/json' } + }); + const payload = JSON.parse(raw); + const resolved = payload && payload.meta && payload.meta.moviedb_id; + if (!resolved || !/^\d+$/.test(String(resolved))) return null; + + TMDB_ID_CACHE[cacheKey] = String(resolved); + console.log(`[Flixer] Resolved ${id} to TMDB ${resolved}`); + return TMDB_ID_CACHE[cacheKey]; + } catch (error) { + console.warn(`[Flixer] Could not resolve IMDb ID ${id}: ${error && error.message ? error.message : String(error)}`); + return null; + } +} + +function buildApiPath(tmdbId, mediaType, season, episode) { + if (mediaType === 'movie') return `/api/tmdb/movie/${encodeURIComponent(tmdbId)}/images`; + return `/api/tmdb/tv/${encodeURIComponent(tmdbId)}/season/${season}/episode/${episode}/images`; +} + +async function decryptPayload(encrypted, apiKey) { + const clearText = await process_img_data(encrypted, apiKey); + return JSON.parse(clearText); +} + +function getServers(payload) { + const values = []; + if (payload && Array.isArray(payload.sources)) { + for (const source of payload.sources) { + const server = source && String(source.server || '').toLowerCase(); + if (server && !values.includes(server)) values.push(server); + } + } + if (payload && payload.servers && typeof payload.servers === 'object') { + for (const server of Object.keys(payload.servers)) { + const normalized = server.toLowerCase(); + if (!values.includes(normalized)) values.push(normalized); + } + } + return NATO_ORDER.filter(server => values.includes(server)).concat(values.filter(server => !NATO_ORDER.includes(server))); +} + +function findUrl(payload, server) { + if (payload && Array.isArray(payload.sources)) { + const exact = payload.sources.find(source => source && String(source.server || '').toLowerCase() === server); + const candidate = exact || payload.sources.find(source => source && source.url); + return candidate && typeof candidate.url === 'string' ? candidate.url.trim() : ''; + } + if (payload && payload.sources && typeof payload.sources === 'object') { + const value = payload.sources.file || payload.sources.url; + return typeof value === 'string' ? value.trim() : ''; + } + return ''; +} + +function getQuality(url) { + const match = String(url).match(/(?:^|[^0-9])(2160|1440|1080|720|480|360)p?(?:[^0-9]|$)/i); + return match ? `${match[1]}p` : 'Auto'; +} + +async function fetchServerPayload(apiUrl, apiPath, apiKey, server) { + const secureHeaders = await createSecureHeaders(apiKey, apiPath); + return await fetchText(apiUrl, { + headers: { + ...secureHeaders, + 'X-Only-Sources': '1', + 'X-Server': server + } + }); +} + +export async function extractStreams(tmdbId, mediaType, season, episode) { + const id = String(tmdbId || '').trim(); + if (mediaType !== 'movie' && mediaType !== 'tv') return []; + if (mediaType === 'tv' && (!Number.isInteger(Number(season)) || !Number.isInteger(Number(episode)) || Number(season) < 1 || Number(episode) < 1)) return []; + + const resolvedId = await resolveTmdbId(id, mediaType); + if (!resolvedId) return []; + + initDecoder(); + const apiKey = get_img_key(); + const apiPath = buildApiPath(resolvedId, mediaType, Number(season), Number(episode)); + const apiUrl = `${API_URL}${apiPath}`; + const secureHeaders = await createSecureHeaders(apiKey, apiPath); + const serverPayload = await fetchText(apiUrl, { + headers: { ...secureHeaders, bW90aGFmYWth: '1' } + }); + const servers = getServers(await decryptPayload(serverPayload, apiKey)); + // Stagger requests as the website does, but allow slow/dead mirrors to run + // concurrently so they do not add their timeout to every other mirror. + const encryptedResponses = await Promise.all(servers.map(async server => { + try { + return { server, encrypted: await fetchServerPayload(apiUrl, apiPath, apiKey, server) }; + } catch (error) { + console.warn(`[Flixer] ${server} request failed: ${error && error.message ? error.message : String(error)}`); + return { server, encrypted: '' }; + } + })); + + const streams = []; + const seenUrls = {}; + for (const response of encryptedResponses) { + if (!response.encrypted) continue; + + try { + const url = findUrl(await decryptPayload(response.encrypted, apiKey), response.server); + if (!url || seenUrls[url]) continue; + seenUrls[url] = true; + const label = SERVER_LABELS[response.server] || response.server.charAt(0).toUpperCase() + response.server.slice(1); + const quality = getQuality(url); + streams.push({ + name: `Flixer · ${label}`, + title: `${label} · ${quality}`, + url, + quality, + headers: { + 'Referer': `${SITE_URL}/`, + 'Origin': SITE_URL, + 'User-Agent': HEADERS['User-Agent'] + } + }); + } catch (error) { + const message = error && error.message ? error.message : String(error); + if (!/^E5[678]$/.test(message)) { + console.warn(`[Flixer] ${response.server} decode failed: ${message}`); + } + } + } + + return streams; +} diff --git a/src/flixer/http.js b/src/flixer/http.js new file mode 100644 index 0000000..b6d01f4 --- /dev/null +++ b/src/flixer/http.js @@ -0,0 +1,201 @@ +/** HTTP and request-signing helpers for Flixer's source API. */ + +export const SITE_URL = 'https://flixer.gd'; +export const API_URL = 'https://plsdontscrapemelove.flixer.gd'; + +export const HEADERS = { + 'Accept': 'text/plain', + 'Origin': SITE_URL, + 'Referer': `${SITE_URL}/`, + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36' +}; + +const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +const SHA256_CONSTANTS = [ + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 +]; + +export function encodeUtf8(value) { + const bytes = []; + for (let index = 0; index < value.length; index++) { + let codePoint = value.charCodeAt(index); + if (codePoint >= 0xD800 && codePoint <= 0xDBFF && index + 1 < value.length) { + const trailing = value.charCodeAt(index + 1); + if (trailing >= 0xDC00 && trailing <= 0xDFFF) { + codePoint = 0x10000 + ((codePoint - 0xD800) << 10) + trailing - 0xDC00; + index++; + } + } + + if (codePoint < 0x80) bytes.push(codePoint); + else if (codePoint < 0x800) bytes.push(0xC0 | (codePoint >> 6), 0x80 | (codePoint & 63)); + else if (codePoint < 0x10000) bytes.push(0xE0 | (codePoint >> 12), 0x80 | ((codePoint >> 6) & 63), 0x80 | (codePoint & 63)); + else bytes.push(0xF0 | (codePoint >> 18), 0x80 | ((codePoint >> 12) & 63), 0x80 | ((codePoint >> 6) & 63), 0x80 | (codePoint & 63)); + } + return new Uint8Array(bytes); +} + +function rotateRight(value, amount) { + return (value >>> amount) | (value << (32 - amount)); +} + +export function sha256(input) { + const bitLength = input.length * 8; + const paddedLength = Math.ceil((input.length + 9) / 64) * 64; + const bytes = new Uint8Array(paddedLength); + bytes.set(input); + bytes[input.length] = 0x80; + + const high = Math.floor(bitLength / 0x100000000); + const low = bitLength >>> 0; + const end = paddedLength - 8; + bytes[end] = (high >>> 24) & 255; + bytes[end + 1] = (high >>> 16) & 255; + bytes[end + 2] = (high >>> 8) & 255; + bytes[end + 3] = high & 255; + bytes[end + 4] = (low >>> 24) & 255; + bytes[end + 5] = (low >>> 16) & 255; + bytes[end + 6] = (low >>> 8) & 255; + bytes[end + 7] = low & 255; + + const hash = [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19]; + const words = new Uint32Array(64); + + for (let offset = 0; offset < bytes.length; offset += 64) { + for (let index = 0; index < 16; index++) { + const position = offset + index * 4; + words[index] = ((bytes[position] << 24) | (bytes[position + 1] << 16) | + (bytes[position + 2] << 8) | bytes[position + 3]) >>> 0; + } + for (let index = 16; index < 64; index++) { + const first = rotateRight(words[index - 15], 7) ^ rotateRight(words[index - 15], 18) ^ (words[index - 15] >>> 3); + const second = rotateRight(words[index - 2], 17) ^ rotateRight(words[index - 2], 19) ^ (words[index - 2] >>> 10); + words[index] = (words[index - 16] + first + words[index - 7] + second) >>> 0; + } + + let [a, b, c, d, e, f, g, h] = hash; + for (let index = 0; index < 64; index++) { + const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25); + const choice = (e & f) ^ (~e & g); + const temporary1 = (h + sum1 + choice + SHA256_CONSTANTS[index] + words[index]) >>> 0; + const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22); + const majority = (a & b) ^ (a & c) ^ (b & c); + const temporary2 = (sum0 + majority) >>> 0; + h = g; g = f; f = e; e = (d + temporary1) >>> 0; + d = c; c = b; b = a; a = (temporary1 + temporary2) >>> 0; + } + + hash[0] = (hash[0] + a) >>> 0; + hash[1] = (hash[1] + b) >>> 0; + hash[2] = (hash[2] + c) >>> 0; + hash[3] = (hash[3] + d) >>> 0; + hash[4] = (hash[4] + e) >>> 0; + hash[5] = (hash[5] + f) >>> 0; + hash[6] = (hash[6] + g) >>> 0; + hash[7] = (hash[7] + h) >>> 0; + } + + const output = new Uint8Array(32); + for (let index = 0; index < hash.length; index++) { + output[index * 4] = (hash[index] >>> 24) & 255; + output[index * 4 + 1] = (hash[index] >>> 16) & 255; + output[index * 4 + 2] = (hash[index] >>> 8) & 255; + output[index * 4 + 3] = hash[index] & 255; + } + return output; +} + +export function hmacSha256(key, message) { + let keyBytes = key instanceof Uint8Array ? key : encodeUtf8(String(key)); + if (keyBytes.length > 64) keyBytes = sha256(keyBytes); + + const messageBytes = message instanceof Uint8Array ? message : encodeUtf8(String(message)); + const inner = new Uint8Array(64 + messageBytes.length); + const outer = new Uint8Array(96); + for (let index = 0; index < 64; index++) { + const value = index < keyBytes.length ? keyBytes[index] : 0; + inner[index] = value ^ 0x36; + outer[index] = value ^ 0x5C; + } + inner.set(messageBytes, 64); + outer.set(sha256(inner), 64); + return sha256(outer); +} + +function encodeBase64(bytes) { + let output = ''; + for (let index = 0; index < bytes.length; index += 3) { + const secondExists = index + 1 < bytes.length; + const thirdExists = index + 2 < bytes.length; + const value = (bytes[index] << 16) | ((secondExists ? bytes[index + 1] : 0) << 8) | + (thirdExists ? bytes[index + 2] : 0); + output += BASE64_ALPHABET[(value >>> 18) & 63]; + output += BASE64_ALPHABET[(value >>> 12) & 63]; + output += secondExists ? BASE64_ALPHABET[(value >>> 6) & 63] : '='; + output += thirdExists ? BASE64_ALPHABET[value & 63] : '='; + } + return output; +} + +function createNonce() { + const bytes = new Uint8Array(16); + for (let index = 0; index < bytes.length; index++) bytes[index] = Math.floor(Math.random() * 256); + return encodeBase64(bytes).replace(/[/+=]/g, '').slice(0, 22); +} + +let serverTimeOffset = 0; +let serverTimeFetchedAt = 0; + +async function getServerTimestamp() { + const now = Date.now(); + if (serverTimeFetchedAt && now - serverTimeFetchedAt < 300000) { + return Math.floor((now + serverTimeOffset) / 1000); + } + + try { + const startedAt = Date.now(); + const response = await fetch(`${API_URL}/api/time?t=${startedAt}`, { + headers: { ...HEADERS, 'Cache-Control': 'no-cache' } + }); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const payload = await response.json(); + if (!payload || typeof payload.timestamp !== 'number') throw new Error('invalid response'); + const finishedAt = Date.now(); + serverTimeOffset = payload.timestamp * 1000 + (finishedAt - startedAt) / 2 - finishedAt; + serverTimeFetchedAt = finishedAt; + return Math.floor((finishedAt + serverTimeOffset) / 1000); + } catch (_) { + return Math.floor(Date.now() / 1000); + } +} + +export async function createSecureHeaders(apiKey, path) { + const timestamp = await getServerTimestamp(); + const nonce = createNonce(); + const message = `${apiKey}:${timestamp}:${nonce}:${path}`; + + return { + 'X-Api-Key': apiKey, + 'X-Request-Timestamp': String(timestamp), + 'X-Request-Nonce': nonce, + 'X-Request-Signature': encodeBase64(hmacSha256(apiKey, message)), + 'X-Client-Fingerprint': 'zerlgt', + 'X-Fingerprint-Lite': 'e9136c41504646444' + }; +} + +export async function fetchText(url, options = {}) { + const response = await fetch(url, { + ...options, + headers: { ...HEADERS, ...(options.headers || {}) } + }); + if (!response.ok) throw new Error(`HTTP ${response.status} for ${url}`); + return await response.text(); +} diff --git a/src/flixer/index.js b/src/flixer/index.js new file mode 100644 index 0000000..0c9340f --- /dev/null +++ b/src/flixer/index.js @@ -0,0 +1,17 @@ +/** Flixer provider entry point. */ + +import { extractStreams } from './extractor.js'; + +async function getStreams(tmdbId, mediaType, season, episode) { + try { + console.log(`[Flixer] Request: ${mediaType} ${tmdbId}`); + const streams = await extractStreams(tmdbId, mediaType, season, episode); + console.log(`[Flixer] Found ${streams.length} stream(s)`); + return streams; + } catch (error) { + console.error(`[Flixer] Error: ${error.message}`); + return []; + } +} + +module.exports = { getStreams }; diff --git a/src/miruro/extractor.js b/src/miruro/extractor.js new file mode 100644 index 0000000..17683b2 --- /dev/null +++ b/src/miruro/extractor.js @@ -0,0 +1,379 @@ +/** + * Resolve TMDB media to AniList, then retrieve Miruro episode sources. + */ + +import { BASE_URL, fetchJson, HEADERS, secureGet } from './http.js'; + +const ANIMAP_URL = 'https://animap.id/api/map'; +const ANILIST_URL = 'https://graphql.anilist.co'; + +// Miruro's visible native-player providers and their preferred subtitle mode. +const PROVIDERS = [ + { id: 'bonk', label: 'Bonk', subCategory: 'ssub' }, + { id: 'kiwi', label: 'Kiwi', subCategory: 'sub' }, + { id: 'hop', label: 'Hop', subCategory: 'ssub' }, + { id: 'ally', label: 'Ally', subCategory: 'sub' }, + { id: 'pewe', label: 'Pewe', subCategory: 'sub' }, + { id: 'bee', label: 'Bee', subCategory: 'ssub' }, + { id: 'moo', label: 'Moo', subCategory: 'sub' } +]; + +function asNumber(value) { + const number = Number(value); + return Number.isFinite(number) ? number : null; +} + +function parseIdentifier(value) { + const id = String(value || '').trim(); + + if (/^tt\d+$/i.test(id)) { + return { service: 'imdb', value: id.toLowerCase() }; + } + + if (/^\d+$/.test(id)) { + return { service: 'tmdb', value: id }; + } + + return null; +} + +function recordMatchesIdentifier(record, identifier, mediaType) { + const expectedType = mediaType === 'movie' ? 'movie' : 'tv'; + + if (identifier.service === 'imdb') { + const imdbMatches = (record.imdb_id || []).some((id) => ( + String(id).toLowerCase() === identifier.value + )); + const typeMatches = (record.tmdb_id || []).length === 0 || (record.tmdb_id || []).some((mapping) => ( + mapping.type === expectedType + )); + + return imdbMatches && typeMatches; + } + + return (record.tmdb_id || []).some((mapping) => ( + String(mapping.id) === identifier.value && mapping.type === expectedType + )); +} + +function getAniListIds(records, identifier, mediaType) { + const ids = []; + + for (const record of Array.isArray(records) ? records : []) { + if (!recordMatchesIdentifier(record, identifier, mediaType)) { + continue; + } + + for (const id of record.anilist_id || []) { + const numericId = asNumber(id); + + if (numericId && !ids.includes(numericId)) { + ids.push(numericId); + } + } + } + + return ids; +} + +function getLocalEpisode(mappings, mediaType, season, episode) { + if (!mappings) { + return null; + } + + if (mediaType === 'movie') { + return 1; + } + + const requestedSeason = asNumber(season) || 1; + const requestedEpisode = asNumber(episode); + + if (!requestedEpisode || requestedEpisode < 1) { + return null; + } + + const mappedSeason = mappings.tmdbSeason || mappings.defaultTvdbSeason; + + if (mappedSeason === 'absolute') { + // Absolute mappings cannot reliably translate later TMDB seasons without + // season lengths. Season one still maps directly. + return requestedSeason === 1 ? requestedEpisode : null; + } + + if (mappedSeason != null && String(mappedSeason) !== String(requestedSeason)) { + return null; + } + + const offset = asNumber(mappings.tmdbOffset) || asNumber(mappings.episodeOffset) || 0; + const episodeCount = asNumber(mappings.episodes); + + if (requestedEpisode <= offset || (episodeCount && requestedEpisode > offset + episodeCount)) { + return null; + } + + return requestedEpisode - offset; +} + +async function getAniListSequel(anilistId) { + const response = await fetchJson(ANILIST_URL, { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + query: ` + query ($id: Int!) { + Media(id: $id, type: ANIME) { + relations { + edges { + relationType + node { + id + type + format + } + } + } + } + } + `, + variables: { id: anilistId } + }) + }); + const edges = response && response.data && response.data.Media && response.data.Media.relations + ? response.data.Media.relations.edges + : []; + const sequel = (edges || []).find((edge) => ( + edge.relationType === 'SEQUEL' && + edge.node && + edge.node.type === 'ANIME' && + ['TV', 'TV_SHORT', 'ONA'].includes(edge.node.format) + )); + + return sequel ? asNumber(sequel.node.id) : null; +} + +async function getSeasonAniListId(anilistId, season) { + const requestedSeason = asNumber(season) || 1; + let currentId = anilistId; + + for (let currentSeason = 1; currentSeason < requestedSeason; currentSeason++) { + const sequelId = await getAniListSequel(currentId); + + if (!sequelId || sequelId === currentId) { + return null; + } + + currentId = sequelId; + } + + return currentId; +} + +function getSequelEpisode(payload, mediaType, season, episode) { + // A sequel has its own AniList episode list, so its first episode is local + // episode 1 even when the caller identifies it as TMDB/IMDb season 2+. Keep + // an explicit TMDB season mapping when Miruro provides one, then fall back + // to numbering relative to the selected AniList entry. + return getLocalEpisode(payload.mappings, mediaType, season, episode) || + getLocalEpisode(payload.mappings, mediaType, 1, episode); +} + +async function resolveEpisodePayload(mediaId, mediaType, season, episode) { + const identifier = parseIdentifier(mediaId); + + if (!identifier) { + return null; + } + + let mappingRecords; + + try { + mappingRecords = await fetchJson( + `${ANIMAP_URL}/${identifier.service}/${encodeURIComponent(identifier.value)}`, + { headers: { 'Accept': 'application/json' } } + ); + } catch (error) { + console.warn(`[Miruro] No anime mapping for ${mediaId}: ${error.message}`); + return null; + } + + const aniListIds = getAniListIds(mappingRecords, identifier, mediaType); + + if (aniListIds.length === 0) { + return null; + } + + // A single mapping can be validated using the episode response itself. + if (aniListIds.length === 1) { + const anilistId = aniListIds[0]; + + if (mediaType === 'tv' && (asNumber(season) || 1) > 1) { + try { + const seasonAniListId = await getSeasonAniListId(anilistId, season); + + if (seasonAniListId && seasonAniListId !== anilistId) { + const payload = await secureGet('episodes', { anilistId: seasonAniListId }); + const localEpisode = getSequelEpisode(payload, mediaType, season, episode); + + if (localEpisode) { + return { anilistId: seasonAniListId, localEpisode, payload }; + } + } + } catch (error) { + // AniList is a fallback for mappings that only identify season + // one. If it is unavailable, retain Miruro's normal mapping. + console.warn(`[Miruro] Season ${season} mapping fallback failed: ${error.message}`); + } + } + + const payload = await secureGet('episodes', { anilistId }); + const localEpisode = getLocalEpisode(payload.mappings, mediaType, season, episode); + + return localEpisode ? { anilistId, localEpisode, payload } : null; + } + + // Franchises can map multiple AniList cours to one TMDB show. Miruro's info + // metadata identifies both the TMDB season and the cour's episode offset. + for (const anilistId of aniListIds) { + try { + const info = await secureGet(`info/${anilistId}`); + const localEpisode = getLocalEpisode(info.mappings, mediaType, season, episode); + + if (localEpisode) { + const payload = await secureGet('episodes', { anilistId }); + return { anilistId, localEpisode, payload }; + } + } catch (error) { + console.warn(`[Miruro] Skipping AniList ${anilistId}: ${error.message}`); + } + } + + return null; +} + +function findEpisode(providerPayload, variant, episodeNumber) { + const episodes = providerPayload && providerPayload.episodes + ? providerPayload.episodes[variant] + : null; + + if (!Array.isArray(episodes)) { + return null; + } + + return episodes.find((item) => Number(item.number) === Number(episodeNumber)) || null; +} + +function getOrigin(url) { + const match = typeof url === 'string' ? url.match(/^(https?:\/\/[^/]+)/i) : null; + return match ? match[1] : BASE_URL; +} + +function getQuality(stream) { + const value = String(stream.quality || stream.server || 'HD'); + const resolution = value.match(/(2160|1440|1080|720|480|360)p?/i); + + if (resolution) { + return `${resolution[1]}p`; + } + + return /4k/i.test(value) ? '4K' : 'HD'; +} + +function isPlayableStream(stream) { + const type = String(stream && stream.type || '').toLowerCase(); + return !!(stream && stream.url) && ( + type === 'hls' || + type === 'm3u8' || + type === 'mp4' || + type === 'application/x-mpegurl' + ); +} + +function normalizeStreams(sourcePayload, provider, variant) { + const subtitleCount = Array.isArray(sourcePayload.subtitles) ? sourcePayload.subtitles.length : 0; + const language = variant === 'dub' ? 'DUB' : 'SUB'; + + return (sourcePayload.streams || []).filter(isPlayableStream).map((stream) => { + const referer = stream.referer || BASE_URL; + const server = stream.server || getQuality(stream); + const subtitleLabel = subtitleCount > 0 && variant !== 'dub' ? ' · CC' : ''; + + return { + // Nuvio's source picker prominently displays `name`, so include the + // Miruro server and audio variant here instead of only in `title`. + name: `Miruro · ${provider.label} · ${language}`, + title: `[${language}] ${provider.label} · ${server}${subtitleLabel}`, + url: stream.url, + quality: getQuality(stream), + headers: { + 'Referer': referer, + 'Origin': getOrigin(referer), + 'User-Agent': HEADERS['User-Agent'] + } + }; + }); +} + +async function getProviderStreams(provider, providerPayload, anilistId, episodeNumber) { + const requests = []; + const subEpisode = findEpisode(providerPayload, 'sub', episodeNumber); + const dubEpisode = findEpisode(providerPayload, 'dub', episodeNumber); + + if (subEpisode) { + requests.push({ episode: subEpisode, variant: 'sub', category: provider.subCategory }); + } + + if (dubEpisode) { + requests.push({ episode: dubEpisode, variant: 'dub', category: 'dub' }); + } + + const results = await Promise.all(requests.map(async (request) => { + try { + const sources = await secureGet('sources', { + episodeId: request.episode.id, + provider: provider.id, + category: request.category, + anilistId + }); + + return normalizeStreams(sources, provider, request.variant); + } catch (error) { + console.warn(`[Miruro] ${provider.label} ${request.variant} failed: ${error.message}`); + return []; + } + })); + + return results.flat(); +} + +export async function extractStreams(tmdbId, mediaType, season, episode) { + if (!tmdbId || (mediaType !== 'movie' && mediaType !== 'tv')) { + return []; + } + + const resolved = await resolveEpisodePayload(tmdbId, mediaType, season, episode); + + if (!resolved || !resolved.payload || !resolved.payload.providers) { + return []; + } + + const providerResults = await Promise.all(PROVIDERS.map((provider) => { + const payload = resolved.payload.providers[provider.id]; + return payload + ? getProviderStreams(provider, payload, resolved.anilistId, resolved.localEpisode) + : []; + })); + const streams = providerResults.flat(); + const seen = new Set(); + + return streams.filter((stream) => { + if (seen.has(stream.url)) { + return false; + } + + seen.add(stream.url); + return true; + }); +} diff --git a/src/miruro/http.js b/src/miruro/http.js new file mode 100644 index 0000000..1617eba --- /dev/null +++ b/src/miruro/http.js @@ -0,0 +1,153 @@ +/** + * HTTP helpers for Miruro's public read API. + */ + +import { gunzipSync, strFromU8 } from 'fflate'; + +export const BASE_URL = 'https://www.miruro.to'; + +export const HEADERS = { + 'Accept': '*/*', + 'Referer': `${BASE_URL}/`, + 'User-Agent': 'Mozilla/5.0 (Linux; Android 15) AppleWebKit/537.36 Chrome/140.0.0.0 Mobile Safari/537.36', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin' +}; + +const PIPE_OBFUSCATION_KEY = hexToBytes('71951034f8fbcf53d89db52ceb3dc22c'); +const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + +function hexToBytes(value) { + const bytes = new Uint8Array(value.length / 2); + + for (let index = 0; index < bytes.length; index++) { + bytes[index] = parseInt(value.slice(index * 2, index * 2 + 2), 16); + } + + return bytes; +} + +function encodeBase64Url(value) { + let result = ''; + + for (let index = 0; index < value.length; index += 3) { + const first = value.charCodeAt(index); + const hasSecond = index + 1 < value.length; + const hasThird = index + 2 < value.length; + const second = hasSecond ? value.charCodeAt(index + 1) : 0; + const third = hasThird ? value.charCodeAt(index + 2) : 0; + const chunk = (first << 16) | (second << 8) | third; + + result += BASE64_ALPHABET[(chunk >> 18) & 63]; + result += BASE64_ALPHABET[(chunk >> 12) & 63]; + result += hasSecond ? BASE64_ALPHABET[(chunk >> 6) & 63] : '='; + result += hasThird ? BASE64_ALPHABET[chunk & 63] : '='; + } + + return result.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); +} + +function decodeBase64Url(value) { + const normalized = value.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, ''); + const outputLength = Math.floor(normalized.length * 6 / 8); + const output = new Uint8Array(outputLength); + let buffer = 0; + let bits = 0; + let outputIndex = 0; + + for (let index = 0; index < normalized.length; index++) { + const code = BASE64_ALPHABET.indexOf(normalized[index]); + + if (code < 0) { + continue; + } + + buffer = (buffer << 6) | code; + bits += 6; + + if (bits >= 8) { + bits -= 8; + output[outputIndex++] = (buffer >> bits) & 255; + } + } + + return outputIndex === output.length ? output : output.slice(0, outputIndex); +} + +function decodeSecurePayload(raw, obfuscationVersion) { + if (!obfuscationVersion) { + return JSON.parse(raw); + } + + const bytes = decodeBase64Url(raw); + + if (obfuscationVersion === '2') { + for (let index = 0; index < bytes.length; index++) { + bytes[index] ^= PIPE_OBFUSCATION_KEY[index % PIPE_OBFUSCATION_KEY.length]; + } + } + + return JSON.parse(strFromU8(gunzipSync(bytes))); +} + +function mergeHeaders(headers) { + return { + ...HEADERS, + ...(headers || {}) + }; +} + +/** Fetch text and throw a useful error for non-success responses. */ +export async function fetchText(url, options = {}) { + const response = await fetch(url, { + ...options, + headers: mergeHeaders(options.headers) + }); + + if (!response.ok) { + throw new Error(`HTTP ${response.status} for ${url}`); + } + + return await response.text(); +} + +/** Fetch a regular JSON endpoint. */ +export async function fetchJson(url, options = {}) { + const raw = await fetchText(url, options); + return JSON.parse(raw); +} + +/** + * Perform the compact, read-only request used by Miruro's secure API. Miruro + * returns base64url-encoded gzip JSON, optionally XOR-obfuscated. + */ +export async function secureGet(path, query = {}) { + const envelope = encodeBase64Url(JSON.stringify({ + path, + method: 'GET', + query, + body: null + })); + const url = `${BASE_URL}/api/secure/pipe?e=${envelope}`; + let response; + + for (let attempt = 0; attempt < 2; attempt++) { + response = await fetch(url, { headers: HEADERS }); + + if (response.ok || (response.status !== 403 && response.status !== 429 && response.status < 500)) { + break; + } + } + + if (!response || !response.ok) { + throw new Error(`Miruro API returned HTTP ${response ? response.status : 'unknown'}`); + } + + const raw = await response.text(); + const version = response.headers && response.headers.get + ? response.headers.get('x-obfuscated') + : null; + + return decodeSecurePayload(raw.trim(), version); +} diff --git a/src/miruro/index.js b/src/miruro/index.js new file mode 100644 index 0000000..5fe26cb --- /dev/null +++ b/src/miruro/index.js @@ -0,0 +1,29 @@ +/** + * Miruro Provider + * Main entry point. + */ + +import { extractStreams } from './extractor.js'; + +/** + * Main function called by Nuvio + * @param {string} tmdbId - TMDB numeric ID or IMDb tt-prefixed ID + * @param {string} mediaType - 'movie' or 'tv' + * @param {number} season - Season number (for TV) + * @param {number} episode - Episode number (for TV) + */ +async function getStreams(tmdbId, mediaType, season, episode) { + try { + const episodeLabel = mediaType === 'tv' ? ` S${season}E${episode}` : ''; + console.log(`[Miruro] Request: ${mediaType} ${tmdbId}${episodeLabel}`); + + const streams = await extractStreams(tmdbId, mediaType, season, episode); + console.log(`[Miruro] Found ${streams.length} stream(s)`); + return streams; + } catch (error) { + console.error(`[Miruro] Error: ${error.message}`); + return []; + } +} + +module.exports = { getStreams };