From 1e11642aad72a40d8a1a989fd94941a9312734cc Mon Sep 17 00:00:00 2001 From: Gavin Fox Date: Mon, 27 Jul 2026 04:28:33 -0700 Subject: [PATCH] create SDK --- .gitignore | 3 + .npmignore | 4 + LICENSE | 407 ++++++++++++++++++++ README.md | 40 ++ package.json | 28 ++ src/config.js | 519 ++++++++++++++++++++++++++ src/downloads/downloads.js | 8 + src/downloads/sources/trendimovies.js | 63 ++++ src/health.js | 38 ++ src/sdk.js | 65 ++++ src/sources/123anime.js | 57 +++ src/sources/1embed.js | 92 +++++ src/sources/anihq.js | 112 ++++++ src/sources/anineko.js | 81 ++++ src/sources/anipm.js | 70 ++++ src/sources/flaxmovies.js | 14 + src/sources/fsharetv.js | 44 +++ src/sources/fsonic.js | 47 +++ src/sources/fsonline.js | 46 +++ src/sources/goated.js | 262 +++++++++++++ src/sources/hexa.js | 82 ++++ src/sources/kisskh.js | 60 +++ src/sources/lookmovie.js | 78 ++++ src/sources/mapple.js | 115 ++++++ src/sources/meowtv.js | 111 ++++++ src/sources/nebula.js | 62 +++ src/sources/nekowatch.js | 57 +++ src/sources/opstream.js | 123 ++++++ src/sources/peachify.js | 78 ++++ src/sources/pengu.js | 45 +++ src/sources/purstream.js | 104 ++++++ src/sources/vidapi.js | 115 ++++++ src/sources/vidcore.js | 43 +++ src/sources/videasy.js | 98 +++++ src/sources/vidfast.js | 53 +++ src/sources/vidlink.js | 28 ++ src/sources/vidnest.js | 77 ++++ src/sources/vidrift.js | 95 +++++ src/sources/vidrock.js | 145 +++++++ src/sources/vidsync.js | 38 ++ src/sources/vidup.js | 79 ++++ src/sources/vidzee.js | 120 ++++++ src/sources/vixsrc.js | 98 +++++ src/sources/xpass.js | 40 ++ src/subtitles.js | 127 +++++++ src/utils/helpers.js | 194 ++++++++++ test/index.js | 54 +++ 47 files changed, 4319 insertions(+) create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 package.json create mode 100644 src/config.js create mode 100644 src/downloads/downloads.js create mode 100644 src/downloads/sources/trendimovies.js create mode 100644 src/health.js create mode 100644 src/sdk.js create mode 100644 src/sources/123anime.js create mode 100644 src/sources/1embed.js create mode 100644 src/sources/anihq.js create mode 100644 src/sources/anineko.js create mode 100644 src/sources/anipm.js create mode 100644 src/sources/flaxmovies.js create mode 100644 src/sources/fsharetv.js create mode 100644 src/sources/fsonic.js create mode 100644 src/sources/fsonline.js create mode 100644 src/sources/goated.js create mode 100644 src/sources/hexa.js create mode 100644 src/sources/kisskh.js create mode 100644 src/sources/lookmovie.js create mode 100644 src/sources/mapple.js create mode 100644 src/sources/meowtv.js create mode 100644 src/sources/nebula.js create mode 100644 src/sources/nekowatch.js create mode 100644 src/sources/opstream.js create mode 100644 src/sources/peachify.js create mode 100644 src/sources/pengu.js create mode 100644 src/sources/purstream.js create mode 100644 src/sources/vidapi.js create mode 100644 src/sources/vidcore.js create mode 100644 src/sources/videasy.js create mode 100644 src/sources/vidfast.js create mode 100644 src/sources/vidlink.js create mode 100644 src/sources/vidnest.js create mode 100644 src/sources/vidrift.js create mode 100644 src/sources/vidrock.js create mode 100644 src/sources/vidsync.js create mode 100644 src/sources/vidup.js create mode 100644 src/sources/vidzee.js create mode 100644 src/sources/vixsrc.js create mode 100644 src/sources/xpass.js create mode 100644 src/subtitles.js create mode 100644 src/utils/helpers.js create mode 100644 test/index.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..055395e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.env +package-lock.json \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..06db450 --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +# npmignore for vyla-sdk in js +.env +test/ +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12d4ca7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,407 @@ +Attribution-NonCommercial 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-NonCommercial 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-NonCommercial 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. NonCommercial means not primarily intended for or directed towards + commercial advantage or monetary compensation. For purposes of + this Public License, the exchange of the Licensed Material for + other material subject to Copyright and Similar Rights by digital + file-sharing or similar means is NonCommercial provided there is + no payment of monetary compensation in connection with the + exchange. + + j. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + k. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + l. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part, for NonCommercial purposes only; and + + b. produce, reproduce, and Share Adapted Material for + NonCommercial purposes only. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties, including when + the Licensed Material is used other than for NonCommercial + purposes. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database for NonCommercial purposes + only; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3edf372 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# vyla/sdk +VylaSDK is a javascript base version of [Vyla](https://vyla.mintlify.app/)'s stream & scrape api, allowing developers to access content providers directly without using a REST api. This gives you the power & updates of Vyla without the headaches of api keys & server reliability. + +## Installing +To start, install VylaSDK through npm or pnpm: +```bash +# npm +npm install @vyla-entertainment/sdk + +# pnpm +pnpm install @vyla-entertainment/sdk +``` + +## Usage +Start by creating a VylaSDK element with your TMDB api key. +```js +import VylaSDK from "@vyla-entertainment/sdk"; + +const sdk = new VylaSDK({ + tmdbApiKey: ... +}); +``` + +You can now use this to access Vyla's content. +```js +// Get sources +// Args: excludeDisabled +sdk.getSources(false); + +// Get subtitles +// Args: tmdb id, season?, episode? +await sdk.getSubtitles('0000000', null, null); -> {label, file, type, source}[] + +// Probe & test a source +// Args: source key +await sdk.probeSource('nebula'); -> {ok, ms} + +// Probe & test all sources +await sdk.probeAllSources(); -> {'key': {ok, ms}, ...} +``` \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..e8c7db3 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vyla-entertainment/sdk", + "version": "1.0.0", + "scripts": { + "test": "node test/index.js" + }, + "author": "vyla-entertainment", + "contributors": [ + { + "name": "GavinGoGaming", + "email": "gavin@klash.dev" + }, + { + "name": "EndOverdosing" + } + ], + "license": "CC-BY-NC-4.0", + "type": "module", + "description": "Vyla's multi-provider stream scraper SDK", + "main": "src/sdk.js", + "directories": { + "test": "test" + }, + "keywords": [], + "devDependencies": { + "dotenv": "^17.4.2" + } +} diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..d7f7e30 --- /dev/null +++ b/src/config.js @@ -0,0 +1,519 @@ +export const SOURCES = [ + { + key: '1embed', + label: '1Embed', + sourceFile: '1embed', + proxyParam: '1e', + timeout: 20000, + jitter: 500, + retries: 2, + multiUrl: true, + }, + + { + key: '123anime', + label: '123Anime', + sourceFile: '123anime', + proxyParam: 'a1', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: false, + skipProxy: true, + cdnHeaders: [{ + pattern: /hlsx\d+cdn\.|burntburst\d+\.store|echovideo\.ru/i, + headers: { + Referer: 'https://play2.echovideo.ru/', + Origin: 'https://play2.echovideo.ru', + }, + },], + }, + + { + key: 'anihq-sub', + label: 'AniHQ (Sub)', + sourceFile: 'anihq', + proxyParam: 'ahsub', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true + }, + + { + key: 'anihq-dub', + label: 'AniHQ (Dub)', + sourceFile: 'anihq', + proxyParam: 'ahdub', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true + }, + + { + key: 'anineko-sub', + sourceFile: 'anineko', + label: 'AniNeko (Sub)', + proxyParam: 'anksub', + timeout: 25000, + jitter: 500, + retries: 2 + }, + + { + key: 'anineko-dub', + sourceFile: 'anineko', + label: 'AniNeko (Dub)', + proxyParam: 'ankdub', + timeout: 25000, + jitter: 500, + retries: 2 + }, + + { + key: 'anipm-sub', + sourceFile: 'anipm', + label: 'AniPM (Sub)', + proxyParam: 'apsub', + timeout: 25000, + jitter: 500, + retries: 2, + skipProxy: true, + skipVerify: true, + multiUrl: true, + verifyHeaders: { + Referer: 'https://ani.pm/', + Origin: 'https://ani.pm', + }, + }, + + { + key: 'anipm-dub', + sourceFile: 'anipm', + label: 'AniPM (Dub)', + proxyParam: 'apdub', + timeout: 25000, + jitter: 500, + retries: 2, + skipProxy: true, + skipVerify: true, + multiUrl: true, + verifyHeaders: { + Referer: 'https://ani.pm/', + Origin: 'https://ani.pm', + }, + }, + + { + key: 'flaxmovies', + label: 'FlaxMovies', + sourceFile: 'flaxmovies', + proxyParam: 'fx', + timeout: 20000, + jitter: 500, + retries: 2, + multiUrl: true, + disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's + cdnHeaders: [{ + pattern: /flix2watch\.pro/i, + headers: { + Referer: 'https://flaxmovies.xyz/', + Origin: 'https://flaxmovies.xyz', + }, + },], + }, + + { + key: 'fsharetv', + label: 'FShareTV', + sourceFile: 'fsharetv', + proxyParam: 'fs', + timeout: 25000, + jitter: 600, + retries: 2, + multiUrl: false, + verifyHeaders: { + Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8', + 'Accept-Language': 'en-US,en;q=0.9', + Referer: 'https://fsharetv.cc', + }, + }, + + { + key: 'goated', + label: 'Goated', + sourceFile: 'goated', + proxyParam: 'gt', + timeout: 25000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + }, + + { + key: 'fsonic', + label: 'Fsonic', + sourceFile: 'fsonic', + proxyParam: 'fn', + timeout: 35000, + jitter: 600, + retries: 1, + multiUrl: true, + }, + + { + key: 'fsonline', + label: 'FSOnline', + sourceFile: 'fsonline', + proxyParam: 'fo', + timeout: 20000, + retries: 1, + jitter: 0, + multiUrl: true, + skipProxy: true, + }, + + { + key: 'hexa', + label: 'Hexa', + sourceFile: 'hexa', + proxyParam: 'hx', + timeout: 20000, + jitter: 500, + retries: 2, + }, + + { + key: 'kisskh', + label: 'KissKH', + sourceFile: 'kisskh', + proxyParam: 'kk', + timeout: 30000, + jitter: 500, + retries: 1, + }, + + { + key: 'lookmovie', + label: 'LookMovie', + sourceFile: 'lookmovie', + proxyParam: 'lm', + timeout: 20000, + jitter: 500, + retries: 2, + multiUrl: true, + verifyHeaders: { + 'Accept-Language': 'en-US,en;q=0.9', + }, + }, + + { + key: 'mapple', + label: 'Mapple', + sourceFile: 'mapple', + proxyParam: 'mp', + timeout: 15000, + retries: 1, + jitter: 0 + }, + + { + key: 'meowtv', + label: 'MeowTV', + sourceFile: 'meowtv', + proxyParam: 'mt', + timeout: 20000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true + }, + + { + key: 'nebula', + label: 'Nebula', + sourceFile: 'nebula', + proxyParam: 'nb', + timeout: 25000, + jitter: 500, + retries: 1, + multiUrl: true, + skipProxy: true, + }, + + { + key: 'nekowatch-sub', + sourceFile: 'nekowatch', + label: 'NekoWatch (Sub)', + proxyParam: 'nwsub', + timeout: 30000, + jitter: 500, + retries: 2 + }, + + { + key: 'nekowatch-dub', + sourceFile: 'nekowatch', + label: 'NekoWatch (Dub)', + proxyParam: 'nwdub', + timeout: 30000, + jitter: 500, + retries: 2 + }, + + { + key: 'opstream', + label: 'OpStream', + sourceFile: 'opstream', + proxyParam: 'op', + timeout: 35000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true, + verifyHeaders: { + Referer: 'https://opstream.fun/', + Origin: 'https://opstream.fun', + }, + }, + + { + key: 'peachify', + label: 'Peachify', + sourceFile: 'peachify', + proxyParam: 'pf', + timeout: 30000, + jitter: 500, + retries: 1, + multiUrl: true + }, + + { + key: 'pengu', + label: 'Pengu', + sourceFile: 'pengu', + proxyParam: 'pg', + timeout: 25000, + jitter: 500, + retries: 1, + multiUrl: true, + skipProxy: true, + disabled: true, // Temporarily disabled because they now require google auth + }, + + { + key: 'purstream', + sourceFile: 'purstream', + label: 'Purstream', + proxyParam: 'ps', + timeout: 20000, + jitter: 500, + retries: 2, + }, + + { + key: 'vidapi', + label: 'VidAPI', + sourceFile: 'vidapi', + proxyParam: 'va', + timeout: 25000, + jitter: 500, + retries: 1, + multiUrl: true + }, + + { + key: 'vidcore', + sourceFile: 'vidcore', + label: 'Vidcore', + proxyParam: 'vc', + timeout: 30000, + jitter: 500, + retries: 2, + sourcesTimeout: 10000, + }, + + { + key: 'videasy', + label: 'Videasy', + sourceFile: 'videasy', + proxyParam: 'vy', + timeout: 40000, + sourcesTimeout: 10000, + jitter: 900, + retries: 3, + multiUrl: true, + verifyHeaders: { + Accept: 'application/json, /; q=0.01', + Referer: 'https://player.videasy.net/', + Origin: 'https://player.videasy.net', + }, + }, + + { + key: 'vidfast', + label: 'Vidfast', + sourceFile: 'vidfast', + proxyParam: 'vf', + timeout: 35000, + jitter: 500, + retries: 1, + }, + + { + key: 'vidlink', + label: 'Vidlink', + sourceFile: 'vidlink', + proxyParam: 'vl', + timeout: 20000, + jitter: 500, + retries: 2, + skipProxy: true, + multiUrl: true, + disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's + }, + + { + key: 'vidnest', + label: 'VidNest', + sourceFile: 'vidnest', + proxyParam: 'vdn', + timeout: 20000, + retries: 1, + jitter: 0, + multiUrl: true, + }, + + { + key: 'vidnest-sub', + label: 'VidNest (Sub)', + sourceFile: 'vidnest', + proxyParam: 'vdn', + timeout: 20000, + retries: 1, + jitter: 0, + multiUrl: true, + skipProxy: true, + }, + + { + key: 'vidnest-dub', + label: 'VidNest (Dub)', + sourceFile: 'vidnest', + proxyParam: 'vdn', + timeout: 20000, + retries: 1, + jitter: 0, + multiUrl: true, + skipProxy: true, + }, + + { + key: 'vidrift', + label: 'VidRift', + sourceFile: 'vidrift', + proxyParam: 'vr', + timeout: 20000, + jitter: 500, + retries: 2, + multiUrl: true, + skipProxy: true + }, + + { + key: 'vidrock', + label: 'VidRock', + sourceFile: 'vidrock', + proxyParam: 'vr', + timeout: 20000, + jitter: 800, + retries: 3, + multiUrl: true, + cdnHeaders: [{ + pattern: /./, + headers: { + Accept: '/', + 'Accept-Language': 'en-US,en;q=0.9', + Referer: 'https://vidrock.ru/', + Origin: 'https://vidrock.ru', + }, + },], + }, + + { + key: 'vidsync', + label: 'VidSync', + sourceFile: 'vidsync', + proxyParam: 'vs', + timeout: 20000, + retries: 2, + multiUrl: true, + disabled: true, // Temporarily disabled due to enc dec issues + }, + + { + key: 'vidup', + label: 'VidUp', + sourceFile: 'vidup', + proxyParam: 'vu', + timeout: 35000, + jitter: 500, + retries: 1, + multiUrl: true + }, + + { + key: 'vidzee', + label: 'VidZee', + sourceFile: 'vidzee', + proxyParam: 'vz', + timeout: 20000, + sourcesTimeout: 10000, + jitter: 400, + retries: 3, + disabled: true, // Temporarily disabled because their site is down, all streams are returning 403's + verifyHeaders: { + Accept: '/', + 'Accept-Language': 'en-US,en;q=0.9', + Referer: 'https://player.vidzee.wtf', + Origin: 'https://player.vidzee.wtf', + }, + }, + + { + key: 'vixsrc', + label: 'VixSrc', + sourceFile: 'vixsrc', + proxyParam: 'vx', + timeout: 35000, + jitter: 0, + retries: 2, + multiUrl: false, + skipProxy: true, + verifyHeaders: { + Accept: 'application/json, text/javascript, /; q=0.01', + 'Accept-Language': 'en-US,en;q=0.9', + Referer: 'https://vixsrc.to/', + Origin: 'https://vixsrc.to', + }, + }, + + { + key: 'xpass', + sourceFile: 'xpass', + label: 'XPass', + proxyParam: 'xp', + timeout: 20000, + jitter: 500, + retries: 2, + skipProxy: true, + }, +]; + +export const HEALTH_PROBE_ID = '155'; +export const SOURCE_MAP = Object.fromEntries(SOURCES.map(s => [s.key, s])); +export default SOURCES; \ No newline at end of file diff --git a/src/downloads/downloads.js b/src/downloads/downloads.js new file mode 100644 index 0000000..da0d7de --- /dev/null +++ b/src/downloads/downloads.js @@ -0,0 +1,8 @@ +import { getDownloads, getDownloadsTv } from "./sources/trendimovies"; + +export async function fetchDownloads(id, s = null, e = null) { + const res = s != null && e != null + ? await getDownloadsTv(id, s, e) + : await getDownloads(id); + return res; +} \ No newline at end of file diff --git a/src/downloads/sources/trendimovies.js b/src/downloads/sources/trendimovies.js new file mode 100644 index 0000000..c796f5f --- /dev/null +++ b/src/downloads/sources/trendimovies.js @@ -0,0 +1,63 @@ +const BASE = "https://trendimovies.com"; + +const HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.9", + "Referer": "https://trendimovies.com/", +}; + +async function fetchPage(path) { + const res = await fetch(`${BASE}${path}`, { headers: HEADERS }); + if (!res.ok) throw new Error(`trendimovies ${res.status}`); + return res.text(); +} + +function parseLinks(html) { + const m = html.match(/DownloadSection[^>]*props="([^"]+)"/s) || + html.match(/props="([^"]+)"[^>]*ssr[^>]*client="load"[^>]*opts="[^"]*DownloadSection/s); + if (!m) { + const alt = html.match(/"DownloadSection"/); + if (!alt) return []; + const propsMatch = html.match(/props="({[^"]*DownloadSection[^"]*})"/s); + if (!propsMatch) return []; + return extractLinks(propsMatch[1].replace(/"/g, '"').replace(/'/g, "'")); + } + return extractLinks(m[1].replace(/"/g, '"').replace(/'/g, "'")); +} + +function extractLinks(propsJson) { + try { + const parsed = JSON.parse(propsJson); + const rawLinks = parsed?.links?.[1]; + if (!Array.isArray(rawLinks)) return []; + return rawLinks + .map(item => item?.[1]) + .filter(l => l && typeof l === "object") + .map(l => ({ + url: unwrap(l.url), + quality: unwrap(l.quality) || "HD", + size: unwrap(l.file_size) || null, + type: "mkv", + active: unwrap(l.is_active) !== false, + })) + .filter(l => l.url && l.active); + } catch { + return []; + } +} + +function unwrap(v) { + if (Array.isArray(v) && v.length === 2 && (v[0] === 0 || v[0] === 1)) return v[1]; + return v; +} + +export async function getDownloads(tmdbId) { + const html = await fetchPage(`/movie/${tmdbId}`); + return parseLinks(html); +} + +export async function getDownloadsTv(tmdbId, season, episode) { + const html = await fetchPage(`/tv/${tmdbId}/season/${season}/episode/${episode}`); + return parseLinks(html); +} \ No newline at end of file diff --git a/src/health.js b/src/health.js new file mode 100644 index 0000000..c9a28e8 --- /dev/null +++ b/src/health.js @@ -0,0 +1,38 @@ +import { HEALTH_PROBE_ID } from "./config.js"; + +const withTimeout = (promise, ms) => Promise.race([promise, new Promise(r => setTimeout(() => r(null), ms))]); +async function withRetry(fn, attempts = 2, delay = 300) { + for (let i = 0; i < attempts; i++) { + try { + const result = await fn(); + if (result != null) return result; + } catch (err) { + if (i === attempts - 1) throw err; + await new Promise(r => setTimeout(r, delay + Math.random() * delay * 0.5)); + } + } + return null; +} + +export async function probeSource(tmdbApiKey, cfg, mod) { + const audio = /dub$/.test(cfg.key) ? 'dub' : 'sub'; + const streamArgs = { + id: HEALTH_PROBE_ID, + s: null, + e: null, + clientIP: null, + absoluteBase: null, + audio, + tmdbApiKey, + config: cfg, + }; + + const t = Date.now(); + const result = await withTimeout( + withRetry(() => mod.getStream(streamArgs), cfg.retries, 300), + cfg.timeout + ); + + const firstUrl = result?.url ?? result?.allUrls?.[0]?.url; + return { ok: !!(typeof firstUrl === 'string' && firstUrl.startsWith('http')), ms: Date.now() - t }; +} \ No newline at end of file diff --git a/src/sdk.js b/src/sdk.js new file mode 100644 index 0000000..099e569 --- /dev/null +++ b/src/sdk.js @@ -0,0 +1,65 @@ +import SOURCES, { SOURCE_MAP } from "./config.js"; +import { fetchDownloads } from "./downloads/downloads.js"; +import { probeSource } from "./health.js"; +import { fetchSubtitles, getSubPathsMovie, getSubPathsTv } from "./subtitles.js"; +import { createStreamArgs } from "./utils/helpers.js"; + +const ALL_SOURCE_MODULES = Object.fromEntries( + await Promise.all(SOURCES.map(async cfg => [cfg.key, await import(`./sources/${cfg.sourceFile}.js`)])) +); + +const SOURCE_MODULES = Object.fromEntries( + Object.entries(ALL_SOURCE_MODULES).filter(([key]) => !SOURCE_MAP[key]?.disabled) +); + +export default class VylaSDK { + constructor({ tmdbApiKey } = {}) { + this.tmdbApiKey = tmdbApiKey; + } + getSources(excludeDisabled = false) { + if (excludeDisabled) { + return SOURCES.filter(cfg => !cfg.disabled); + } + return SOURCES; + } + async probeAllSources() { + const active = SOURCES.filter(cfg => !cfg.disabled); + + const results = await Promise.allSettled( + active.map(async cfg => await probeSource(cfg, SOURCE_MODULES[cfg.key])) + ); + + const sources = Object.fromEntries( + active.map((cfg, i) => [ + cfg.key, + results[i].status === 'fulfilled' ? results[i].value : { ok: false, ms: null }, + ]) + ); + + return sources; + } + async probeSource(key) { + const cfg = SOURCES.find(cfg => cfg.key === key); + if (!cfg) throw new Error(`Source with key "${key}" not found`); + const mod = SOURCE_MODULES[key]; + if (!mod) throw new Error(`Source module for key "${key}" not found`); + return await probeSource(this.tmdbApiKey, cfg, mod); + } + async getStream(key, id, s = null, e = null, clientIP = null) { + const cfg = SOURCES.find(cfg => cfg.key === key); + if (!cfg) throw new Error(`Source with key "${key}" not found`); + const mod = SOURCE_MODULES[key]; + if (!mod) throw new Error(`Source module for key "${key}" not found`); + const streamArgs = await createStreamArgs(cfg, this.tmdbApiKey, id, s, e, clientIP); + return await mod.getStream(streamArgs); + } + async getSubtitles(id, s = null, e = null) { + const paths = s != null && e != null + ? await getSubPathsTv(id, s, e) + : await getSubPathsMovie(id); + return await fetchSubtitles(paths); + } + async getDownloads(id, s = null, e = null) { + return await fetchDownloads(id, s, e); + } +} \ No newline at end of file diff --git a/src/sources/123anime.js b/src/sources/123anime.js new file mode 100644 index 0000000..a0fa6dd --- /dev/null +++ b/src/sources/123anime.js @@ -0,0 +1,57 @@ +import { getTmdbInfo, fetchJson, USER_AGENT } from '../utils/helpers.js'; + +async function resolveSlug(id, s, e, info) { + const titles = info?.titles || []; + const keywords = []; + for (const title of titles) { + const t = title.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').replace(/\s+/g, ' ').trim(); + if (!keywords.includes(t)) keywords.push(t); + const words = title.split(/[\s:]+/).filter(w => w.length > 2); + if (words.length >= 2) { + const short = words.slice(0, 3).join(' ').toLowerCase(); + if (!keywords.includes(short)) keywords.push(short); + } + } + const slugCandidates = []; + for (const keyword of keywords) { + try { + const data = await fetchJson(`https://123animehub.cc/ajax/film/search?keyword=${encodeURIComponent(keyword)}&_=${Date.now()}`, { headers: { 'X-Requested-With': 'XMLHttpRequest', 'Referer': 'https://123animehub.cc', 'Accept': 'application/json', 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(8000) }); + let html = data.html || '', idx = 0; + while ((idx = html.indexOf('href="/anime/', idx)) !== -1) { + const start = idx + 13; + const end = html.indexOf('"', start); + const slug = html.slice(start, end); + if (!slugCandidates.includes(slug)) slugCandidates.push(slug); + idx = end; + } + } catch { } + if (slugCandidates.length) break; + } + for (const slug of slugCandidates) { + try { + const data = await fetchJson(`https://123animehub.cc/ajax/episode/info?epr=${encodeURIComponent(s ? `${slug}/${s}/${e}` : `${slug}/1`)}&ts=1&_=${Date.now()}`, { headers: { 'Referer': `https://123animehub.cc/anime/${slug}`, 'X-Requested-With': 'XMLHttpRequest', 'Accept': 'application/json, text/javascript, */*; q=0.01', 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(10000) }); + if (data?.target) return data.target; + } catch { } + } + return null; +} + +export async function getStream({ id, s, e }) { + if (!s) return null; + const info = await getTmdbInfo(id, 'tv', s); + if (!info.isAnime) return null; + const target = await resolveSlug(id, s, e, info); + if (!target) return null; + const embedMatch = target.match(/\/embed-[^/]+\/([A-Za-z0-9+/=]+)$/); + if (!embedMatch) return null; + const origin = new URL(target).origin; + try { + const embedRes = await fetch(target, { headers: { 'User-Agent': USER_AGENT }, signal: AbortSignal.timeout(10000) }); + if (!embedRes.ok) return null; + const setCookie = embedRes.headers.get('set-cookie'); + const data = await fetchJson(`${origin}/hs/getSources?id=${embedMatch[1]}`, { headers: { 'Referer': target, 'Accept': '*/*', 'User-Agent': USER_AGENT, ...(setCookie ? { 'Cookie': setCookie.split(';')[0].trim() } : {}) }, signal: AbortSignal.timeout(10000) }); + const src = typeof data.sources === 'string' && data.sources.length ? data.sources : (Array.isArray(data.sources) && data.sources.length ? (data.sources[0].file || data.sources[0].src || data.sources[0].url) : null); + if (src) return { url: src, headers: { 'Referer': 'https://play2.echovideo.ru/', 'Origin': 'https://play2.echovideo.ru' } }; + } catch { } + return null; +} \ No newline at end of file diff --git a/src/sources/1embed.js b/src/sources/1embed.js new file mode 100644 index 0000000..a467229 --- /dev/null +++ b/src/sources/1embed.js @@ -0,0 +1,92 @@ +import { USER_AGENT } from '../utils/helpers.js'; + +const BASE_URL = "https://1embed.cc"; + +const PROVIDERS = [ + { id: "sdev", name: "1Embed - SDev" }, + { id: "xpa", name: "1Embed - XPass" }, + { id: "vnes", name: "1Embed - VidNest" }, + { id: "pro", name: "1Embed - Pro" }, + { id: "pur", name: "1Embed - Purstream" }, + { id: "czo", name: "1Embed - Cinezo" }, + { id: "fas", name: "1Embed - VidFast" } +]; + +let cachedToken = null; +let tokenExpiresAt = 0; + +async function getToken() { + const now = Math.floor(Date.now() / 1000); + if (cachedToken && tokenExpiresAt > now + 10) return cachedToken; + try { + const res = await fetch(`${BASE_URL}/api/token`, { headers: { "Referer": `${BASE_URL}/`, "User-Agent": USER_AGENT, "Accept": "application/json" }, signal: AbortSignal.timeout(10000) }); + if (!res.ok) return null; + const data = await res.json(); + cachedToken = data.token; + tokenExpiresAt = data.expiresAt; + return cachedToken; + } catch { return null; } +} + +async function fetchProviderStream(provider, type, id, s, e, token) { + try { + const referer = type === "movie" ? `${BASE_URL}/embed/movie/${id}` : `${BASE_URL}/embed/tv/${id}/${s}/${e}`; + const payload = { type, id: String(id), provider: provider.id }; + if (type === "tv") { payload.season = Number(s); payload.episode = Number(e); } + const res = await fetch(`${BASE_URL}/api/sources`, { + method: "POST", + headers: { "User-Agent": USER_AGENT, "Referer": referer, "Origin": BASE_URL, "Accept": "application/json", "Content-Type": "application/json", "x-bcine-key": token, "X-Requested-With": "XMLHttpRequest" }, + body: JSON.stringify(payload), + signal: AbortSignal.timeout(15000) + }); + if (!res.ok) return null; + const data = await res.json(); + + let rawSources = []; + if (Array.isArray(data.servers)) rawSources.push(...data.servers); + if (data.providerResults?.[provider.id]?.sources) rawSources.push(...data.providerResults[provider.id].sources); + if (data.providerResults?.[provider.id] && Array.isArray(data.providerResults[provider.id])) rawSources.push(...data.providerResults[provider.id]); + + if (!rawSources.length) return null; + const results = []; + for (const src of rawSources) { + if (typeof src !== 'object' || !src) continue; + let streamUrl = src.url || src.file || src.link; + if (typeof streamUrl !== 'string') continue; + if (streamUrl.startsWith('/')) streamUrl = `${BASE_URL}${streamUrl}`; + const isWorker = streamUrl.includes('omena-puu') || streamUrl.includes('nocach') || streamUrl.includes('?p='); + const isHls = src.type === 'hls' || streamUrl.includes('m3u8') || isWorker; + if (isHls && !streamUrl.includes('m3u8')) streamUrl += streamUrl.includes('?') ? '&format=.m3u8' : '?format=.m3u8'; + const srcHeaders = (src.headers && typeof src.headers === 'object') ? src.headers : {}; + results.push({ + url: streamUrl, + server: provider.name, + quality: src.quality || src.label || src.title || "Auto", + type: isHls ? "hls" : "mp4", + headers: { "User-Agent": USER_AGENT, "Referer": referer, "Origin": BASE_URL, ...srcHeaders }, + }); + } + return results; + } catch { + return null; + } +} + +export async function getStream(args) { + const { id, s, e, server } = args; + const type = s != null && e != null ? "tv" : "movie"; + const token = await getToken(); + if (!token) return null; + let targets = PROVIDERS; + if (server && server !== 'all') { + const cleanName = server.replace('1Embed - ', '').toLowerCase(); + targets = PROVIDERS.filter(p => p.id === cleanName || p.name.toLowerCase().includes(cleanName)); + if (!targets.length) targets = PROVIDERS; + } + const settled = await Promise.allSettled(targets.map(p => fetchProviderStream(p, type, id, s, e, token))); + const allUrls = []; + for (const r of settled) if (r.status === 'fulfilled' && r.value) allUrls.push(...r.value); + return allUrls.length ? { allUrls } : null; +} + +export async function getSources() { return PROVIDERS.map(p => p.name); } \ No newline at end of file diff --git a/src/sources/anihq.js b/src/sources/anihq.js new file mode 100644 index 0000000..45a69f3 --- /dev/null +++ b/src/sources/anihq.js @@ -0,0 +1,112 @@ +import { getTmdbInfo, USER_AGENT } from '../utils/helpers.js'; + +async function extractVoe(voeUrl) { + try { + const res = await fetch(voeUrl, { + headers: { + 'User-Agent': USER_AGENT, + 'Referer': 'https://anihq.cc/' + } + }); + + const mirrorUrl = res.url; + const html = await res.text(); + + const scriptMatch = html.match(/