mirror of
https://github.com/Inside4ndroid/TMDB-Embed-API.git
synced 2026-10-22 17:45:11 +00:00
Major Update : Added 2Embed, Update api routes, update json response format.
This commit is contained in:
@@ -1,4 +1,20 @@
|
|||||||
# Unoffical vidsrc (embed.su) API! 👋
|
# Unoffical vidsrc API! 👋
|
||||||
|
## Current Embed Providers:
|
||||||
|
|
||||||
|
1. embedsu
|
||||||
|
2. 2embed
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Hello! I'm Inside4ndroid, a passionate Software Developer.
|
||||||
|
|
||||||
|
- 🐛 Bugs will be found and squashed as soon as possible please report any isues.
|
||||||
|
- 🌱 Currently in a state that works and is recieving updates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
## Sponsorship / Donations
|
## Sponsorship / Donations
|
||||||
|
|
||||||
Your sponsorship is vital in helping me achieve this mission. With your support, I can:
|
Your sponsorship is vital in helping me achieve this mission. With your support, I can:
|
||||||
@@ -12,9 +28,11 @@ Every contribution, no matter the size, makes a significant impact.
|
|||||||
|
|
||||||
Thank you for considering supporting my work!
|
Thank you for considering supporting my work!
|
||||||
|
|
||||||
### NOTE:
|
<br>
|
||||||
|
|
||||||
This is an api for id's provided at [TMDB](https://www.themoviedb.org/)
|
### USAGE:
|
||||||
|
|
||||||
|
This is an api for id's provided at [TMDB](https://www.themoviedb.org/).
|
||||||
|
|
||||||
you must use the ID from the tmdb url like this :
|
you must use the ID from the tmdb url like this :
|
||||||
|
|
||||||
@@ -45,6 +63,8 @@ The providers are currently :
|
|||||||
|
|
||||||
```
|
```
|
||||||
embedsu
|
embedsu
|
||||||
|
2embed
|
||||||
|
combined (use this as provider to return results for all providers)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Example Movie
|
## Example Movie
|
||||||
@@ -57,6 +77,115 @@ https://your-domain/embedsu/916224
|
|||||||
https://your-domain/embedsu/1429?s=1&e=1
|
https://your-domain/embedsu/1429?s=1&e=1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Example JSON Response
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Click me</summary>
|
||||||
|
|
||||||
|
```Json
|
||||||
|
{
|
||||||
|
"embedsu": {
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"provider": "EmbedSu",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"file": "https://embed.su/api/proxy/viper/mistydawn62.pro/file2/dCjXQHTrXL5VWmkFvGEEIrCyskIr6MZERXLYef0sUKuY12Ey+dX5hLYObC78qdmoop2iV8+MYm5wcOiFtAuZbcsp46BsOa1q5in7CNuEv70H5ZNZ34kEPH5I9FpN3v6qN7WhRBXq0ThSHefHVb94rMFoirhHjC4o2LRkwXoAiTA=/MTA4MA==/aW5kZXgubTN1OA==.m3u8",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "1080p",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "https://embed.su/api/proxy/viper/mistydawn62.pro/file2/dCjXQHTrXL5VWmkFvGEEIrCyskIr6MZERXLYef0sUKuY12Ey+dX5hLYObC78qdmoop2iV8+MYm5wcOiFtAuZbcsp46BsOa1q5in7CNuEv70H5ZNZ34kEPH5I9FpN3v6qN7WhRBXq0ThSHefHVb94rMFoirhHjC4o2LRkwXoAiTA=/NzIw/aW5kZXgubTN1OA==.m3u8",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "720p",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "https://embed.su/api/proxy/viper/mistydawn62.pro/file2/dCjXQHTrXL5VWmkFvGEEIrCyskIr6MZERXLYef0sUKuY12Ey+dX5hLYObC78qdmoop2iV8+MYm5wcOiFtAuZbcsp46BsOa1q5in7CNuEv70H5ZNZ34kEPH5I9FpN3v6qN7WhRBXq0ThSHefHVb94rMFoirhHjC4o2LRkwXoAiTA=/MzYw/aW5kZXgubTN1OA==.m3u8",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "360p",
|
||||||
|
"lang": "en"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subtitles": [
|
||||||
|
{
|
||||||
|
"url": "https://cca.megafiles.store/1c/9e/1c9eb9aeb90915a2786400a748c8b6a4/eng-2.vtt",
|
||||||
|
"lang": "English"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://cca.megafiles.store/1c/9e/1c9eb9aeb90915a2786400a748c8b6a4/eng-3.vtt",
|
||||||
|
"lang": "English"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://cca.megafiles.store/1c/9e/1c9eb9aeb90915a2786400a748c8b6a4/fre-4.vtt",
|
||||||
|
"lang": "French"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://cca.megafiles.store/1c/9e/1c9eb9aeb90915a2786400a748c8b6a4/spa-5.vtt",
|
||||||
|
"lang": "Spanish"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": {
|
||||||
|
"Referer": "https://embed.su",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||||
|
"Origin": "https://embed.su"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"twoembed": {
|
||||||
|
"sources": {
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"provider": "2Embed/Swish",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"file": "https://j7fqjm25xt.premilkyway.com/hls2/01/05031/6z843r5do14n_,l,n,h,.urlset/index-f3-v1-a1.m3u8?t=T98eEbm347MS_kxTv3tPxGucVi-ZnV6omagdGNf-ZiA&s=1743805861&e=129600&f=25158304&srv=3FjgDmcnea8S35m&i=0.4&sp=500&p1=3FjgDmcnea8S35m&p2=3FjgDmcnea8S35m&asn=5378",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "1080p",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "https://j7fqjm25xt.premilkyway.com/hls2/01/05031/6z843r5do14n_,l,n,h,.urlset/index-f2-v1-a1.m3u8?t=T98eEbm347MS_kxTv3tPxGucVi-ZnV6omagdGNf-ZiA&s=1743805861&e=129600&f=25158304&srv=3FjgDmcnea8S35m&i=0.4&sp=500&p1=3FjgDmcnea8S35m&p2=3FjgDmcnea8S35m&asn=5378",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "720p",
|
||||||
|
"lang": "en"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "https://j7fqjm25xt.premilkyway.com/hls2/01/05031/6z843r5do14n_,l,n,h,.urlset/index-f1-v1-a1.m3u8?t=T98eEbm347MS_kxTv3tPxGucVi-ZnV6omagdGNf-ZiA&s=1743805861&e=129600&f=25158304&srv=3FjgDmcnea8S35m&i=0.4&sp=500&p1=3FjgDmcnea8S35m&p2=3FjgDmcnea8S35m&asn=5378",
|
||||||
|
"type": "hls",
|
||||||
|
"quality": "480p",
|
||||||
|
"lang": "en"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subtitles": [
|
||||||
|
{
|
||||||
|
"url": "https://3FjgDmcnea8S35m.premilkyway.com/vtt/01/05031/6z843r5do14n_fre.vtt",
|
||||||
|
"lang": "French"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://3FjgDmcnea8S35m.premilkyway.com/vtt/01/05031/6z843r5do14n_spa.vtt",
|
||||||
|
"lang": "Argentina"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://3FjgDmcnea8S35m.premilkyway.com/vtt/01/05031/6z843r5do14n_eng.vtt",
|
||||||
|
"lang": "English"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"headers": {
|
||||||
|
"Referer": "https://uqloads.xyz",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||||
|
"Origin": "https://uqloads.xyz"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
You can self host by doing the following :
|
You can self host by doing the following :
|
||||||
@@ -67,3 +196,11 @@ You can self host by doing the following :
|
|||||||
4. `npm run main`
|
4. `npm run main`
|
||||||
|
|
||||||
"This no longer works on vercel due to the source blocking vercel requests but will work on your own server self hosted."
|
"This no longer works on vercel due to the source blocking vercel requests but will work on your own server self hosted."
|
||||||
|
|
||||||
|
## WARNING
|
||||||
|
|
||||||
|
Using this without a proxy may lead to your server ip being banned there are 3 ways to get around this :
|
||||||
|
|
||||||
|
1. Use a proxy : [Find One Here](https://github.com/search?q=m3u8+proxy&type=repositories).
|
||||||
|
2. Get a server with rotating ip addresses or dynamic ip adresses and use the ips at random.
|
||||||
|
3. use a cloudflare proxy worker.
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { getEmbedSu } from "./src/extractors/embedsu.js";
|
import { getEmbedSu } from "./src/extractors/embedsu.js";
|
||||||
|
import { getTwoEmbed } from "./src/extractors/2Embed.js";
|
||||||
|
|
||||||
const port = 3000;
|
const port = 3000;
|
||||||
|
|
||||||
@@ -7,12 +8,14 @@ const app = express()
|
|||||||
|
|
||||||
app.get('/', (req, res) => {
|
app.get('/', (req, res) => {
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
intro: "Welcome to the unofficial vidsrcPro provider",
|
INTRO: "Welcome to the unofficial vidsrcPro provider",
|
||||||
routes: {
|
ROUTES: {
|
||||||
movie: "/embedsu/:movieTMDBid",
|
movie: "/embedsu|2embed/:movieTMDBid",
|
||||||
show: "/embedsu/:showTMDBid?s=seasonNumber&e=episodeNumber"
|
show: "/embedsu|2embed/:showTMDBid?s=seasonNumber&e=episodeNumber",
|
||||||
|
all_movie: "/combined/:movieTMDBid",
|
||||||
|
all_show: "/combined/:showTMDBid?s=seasonNumber&e=episodeNumber"
|
||||||
},
|
},
|
||||||
author: "This api is developed and created by Inside4ndroid Studios"
|
AUTHOR: "This api is developed and created by Inside4ndroid Studios"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -35,6 +38,54 @@ app.get('/embedsu/:tmdbId', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/2embed/:tmdbId', async (req, res) => {
|
||||||
|
const id = req.params.tmdbId;
|
||||||
|
const season = req.query.s;
|
||||||
|
const episode = req.query.e;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (season && episode) {
|
||||||
|
const vidsrcresponse = await getTwoEmbed(id, season, episode);
|
||||||
|
res.status(200).json(vidsrcresponse);
|
||||||
|
} else {
|
||||||
|
const vidsrcresponse = await getTwoEmbed(id, 0, 0);
|
||||||
|
res.status(200).json(vidsrcresponse);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch data' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/combined/:tmdbId', async (req, res) => {
|
||||||
|
const id = req.params.tmdbId;
|
||||||
|
const season = req.query.s;
|
||||||
|
const episode = req.query.e;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let embedSuResponse;
|
||||||
|
let twoEmbedResponse;
|
||||||
|
|
||||||
|
if (season && episode) {
|
||||||
|
embedSuResponse = await getEmbedSu(id, season, episode);
|
||||||
|
twoEmbedResponse = await getTwoEmbed(id, season, episode);
|
||||||
|
} else {
|
||||||
|
embedSuResponse = await getEmbedSu(id);
|
||||||
|
twoEmbedResponse = await getTwoEmbed(id, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const combinedResponse = {
|
||||||
|
embedsu: embedSuResponse,
|
||||||
|
twoembed: twoEmbedResponse,
|
||||||
|
};
|
||||||
|
|
||||||
|
res.status(200).json(combinedResponse);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching data:', error);
|
||||||
|
res.status(500).json({ error: 'Failed to fetch data' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.listen(port, () => {
|
app.listen(port, () => {
|
||||||
console.log(`Example app listening on port http://localhost:${port}`);
|
console.log(`Api listening on port http://localhost:${port}`);
|
||||||
});
|
});
|
||||||
Generated
+279
@@ -9,6 +9,7 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
@@ -68,6 +69,12 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/bytes": {
|
"node_modules/bytes": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||||
@@ -96,6 +103,48 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cheerio": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cheerio-select": "^2.1.0",
|
||||||
|
"dom-serializer": "^2.0.0",
|
||||||
|
"domhandler": "^5.0.3",
|
||||||
|
"domutils": "^3.1.0",
|
||||||
|
"encoding-sniffer": "^0.2.0",
|
||||||
|
"htmlparser2": "^9.1.0",
|
||||||
|
"parse5": "^7.1.2",
|
||||||
|
"parse5-htmlparser2-tree-adapter": "^7.0.0",
|
||||||
|
"parse5-parser-stream": "^7.1.2",
|
||||||
|
"undici": "^6.19.5",
|
||||||
|
"whatwg-mimetype": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
|
},
|
||||||
|
"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==",
|
||||||
|
"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/content-disposition": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||||
@@ -132,6 +181,34 @@
|
|||||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/css-select": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||||
|
"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.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/fb55"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/data-uri-to-buffer": {
|
"node_modules/data-uri-to-buffer": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||||
@@ -185,6 +262,61 @@
|
|||||||
"npm": "1.2.8000 || >= 1.4.16"
|
"npm": "1.2.8000 || >= 1.4.16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"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/domelementtype": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||||
|
"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==",
|
||||||
|
"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==",
|
||||||
|
"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/ee-first": {
|
"node_modules/ee-first": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
@@ -208,6 +340,43 @@
|
|||||||
"iconv-lite": "~0.4.13"
|
"iconv-lite": "~0.4.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/encoding-sniffer": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
|
||||||
|
"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/encoding-sniffer/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/entities": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/es-define-property": {
|
"node_modules/es-define-property": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||||
@@ -452,6 +621,25 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/htmlparser2": {
|
||||||
|
"version": "9.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
|
||||||
|
"integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
|
||||||
|
"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.1.0",
|
||||||
|
"entities": "^4.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/http-errors": {
|
"node_modules/http-errors": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
|
||||||
@@ -602,6 +790,18 @@
|
|||||||
"url": "https://opencollective.com/node-fetch"
|
"url": "https://opencollective.com/node-fetch"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"boolbase": "^1.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.2",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
||||||
@@ -626,6 +826,43 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/parse5": {
|
||||||
|
"version": "7.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
|
||||||
|
"integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"entities": "^4.5.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==",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"parse5": "^7.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/parseurl": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||||
@@ -855,6 +1092,15 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/undici": {
|
||||||
|
"version": "6.21.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz",
|
||||||
|
"integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unpipe": {
|
"node_modules/unpipe": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||||
@@ -889,6 +1135,39 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"iconv-lite": "0.6.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/whatwg-encoding/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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"author": "Inside4ndroid Studios Ltd",
|
"author": "Inside4ndroid Studios Ltd",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cheerio": "^1.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"fetch": "^1.1.0",
|
"fetch": "^1.1.0",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import JsUnpacker from '../utils/jsunpack.js';
|
||||||
|
import * as cheerio from 'cheerio';
|
||||||
|
|
||||||
|
const DOMAIN = "https://www.2embed.cc";
|
||||||
|
const PLAYER_URL = "https://uqloads.xyz";
|
||||||
|
|
||||||
|
let subtitles = [];
|
||||||
|
let sources = [];
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'User-Agent': "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||||
|
'Referer': DOMAIN,
|
||||||
|
'Origin': DOMAIN,
|
||||||
|
};
|
||||||
|
|
||||||
|
function parseQuality(qualityString) {
|
||||||
|
if (!qualityString) return 0;
|
||||||
|
const q = qualityString.toUpperCase();
|
||||||
|
if (q === '4K') return 4000;
|
||||||
|
if (q.includes('1080P')) return 1080;
|
||||||
|
if (q.includes('720P')) return 720;
|
||||||
|
const numMatch = q.match(/(\d+)/);
|
||||||
|
return numMatch ? parseInt(numMatch[1], 10) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createErrorObject(errorMessage) {
|
||||||
|
return {
|
||||||
|
provider: "2Embed",
|
||||||
|
ERROR: [{
|
||||||
|
error: `ERROR`,
|
||||||
|
what_happened: errorMessage,
|
||||||
|
report_issue: 'https://github.com/Inside4ndroid/vidsrc-api-js/issues'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTwoEmbed(tmdb_id, s, e) {
|
||||||
|
subtitles = [];
|
||||||
|
sources = [];
|
||||||
|
|
||||||
|
const tmdbId = tmdb_id;
|
||||||
|
const initialReferer = s && e ? `${DOMAIN}/embedtv/${tmdbId}&s=${s}&e=${e}` : `${DOMAIN}/embed/${tmdbId}`;
|
||||||
|
const url = initialReferer;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Referer: url,
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
"User-Agent": headers['User-Agent'],
|
||||||
|
},
|
||||||
|
body: "pls=pls"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return { sources: [createErrorObject(`HTTP error fetching initial URL! Status: ${response.status}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.text();
|
||||||
|
let extractedValue = null;
|
||||||
|
let isSwishId = false;
|
||||||
|
|
||||||
|
let match = data.match(/swish\?id=(?<id>[\w\d]+)/);
|
||||||
|
|
||||||
|
if (match && match.groups && match.groups.id) {
|
||||||
|
extractedValue = match.groups.id;
|
||||||
|
isSwishId = true;
|
||||||
|
} else {
|
||||||
|
match = data.match(/'(.*?player4u.*?)'/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
extractedValue = match[1];
|
||||||
|
} else {
|
||||||
|
return { sources: [createErrorObject("No relevant swish ID or player4u URL found in initial data.")] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extractedValue) {
|
||||||
|
if (isSwishId) {
|
||||||
|
const streamUrl = await resolve(`${PLAYER_URL}/e/${extractedValue}`, DOMAIN);
|
||||||
|
if (!streamUrl) {
|
||||||
|
return { sources: [createErrorObject(`Could not resolve stream URL for swish ID: ${extractedValue}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedSources = await parseM3U8(streamUrl, PLAYER_URL);
|
||||||
|
|
||||||
|
if (parsedSources.length > 0) {
|
||||||
|
sources.push({
|
||||||
|
provider: "2Embed/Swish",
|
||||||
|
files: parsedSources,
|
||||||
|
subtitles: subtitles,
|
||||||
|
headers: {
|
||||||
|
"Referer": PLAYER_URL,
|
||||||
|
"User-Agent": headers['User-Agent'],
|
||||||
|
"Origin": PLAYER_URL
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return { sources: [createErrorObject("No valid sources found in M3U8 for swish ID")] };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const listPageResponse = await fetch(extractedValue, {
|
||||||
|
headers: {
|
||||||
|
Referer: url,
|
||||||
|
"User-Agent": headers['User-Agent']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!listPageResponse.ok) {
|
||||||
|
return { sources: [createErrorObject(`Failed to fetch player4u list page (${extractedValue}): ${listPageResponse.status}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const listPageHtml = await listPageResponse.text();
|
||||||
|
|
||||||
|
const $ = cheerio.load(listPageHtml);
|
||||||
|
let highestQuality = -1;
|
||||||
|
let bestPartialUrl = null;
|
||||||
|
|
||||||
|
$('li.slide-toggle a.playbtnx').each((index, element) => {
|
||||||
|
const linkText = $(element).text();
|
||||||
|
const onclickAttr = $(element).attr('onclick');
|
||||||
|
|
||||||
|
if (!linkText || !onclickAttr) return;
|
||||||
|
|
||||||
|
const qualityMatch = linkText.match(/\s*(\d+p|4K)\s*/i);
|
||||||
|
const qualityString = qualityMatch ? qualityMatch[1].toUpperCase() : null;
|
||||||
|
|
||||||
|
const urlMatch = onclickAttr.match(/go\('([^']+)'\)/);
|
||||||
|
const partialUrl = urlMatch ? urlMatch[1] : null;
|
||||||
|
|
||||||
|
if (!qualityString || !partialUrl) return;
|
||||||
|
|
||||||
|
const qualityValue = parseQuality(qualityString);
|
||||||
|
|
||||||
|
if (qualityValue > highestQuality) {
|
||||||
|
highestQuality = qualityValue;
|
||||||
|
bestPartialUrl = partialUrl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bestPartialUrl) {
|
||||||
|
const idMatch = bestPartialUrl.match(/\?id=([\w\d]+)/);
|
||||||
|
if (idMatch && idMatch[1]) {
|
||||||
|
const player4uId = idMatch[1];
|
||||||
|
const resolveUrl = `${PLAYER_URL}/e/${player4uId}`;
|
||||||
|
|
||||||
|
const streamUrl = await resolve(resolveUrl, extractedValue);
|
||||||
|
if (!streamUrl) {
|
||||||
|
return { sources: [createErrorObject(`Could not resolve stream URL for player4u ID: ${player4uId}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedSources = await parseM3U8(streamUrl, PLAYER_URL);
|
||||||
|
|
||||||
|
if (parsedSources.length > 0) {
|
||||||
|
sources.push({
|
||||||
|
provider: "2Embed/Player4u",
|
||||||
|
files: parsedSources,
|
||||||
|
subtitles: subtitles,
|
||||||
|
headers: {
|
||||||
|
"Referer": PLAYER_URL,
|
||||||
|
"User-Agent": headers['User-Agent'],
|
||||||
|
"Origin": PLAYER_URL
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return { sources: [createErrorObject("No valid sources found in M3U8 for player4u ID")] };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { sources: [createErrorObject("Could not extract player4u ID from best quality URL")] };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { sources: [createErrorObject("No valid quality options found on player4u page")] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { sources: sources.length > 0 ? { sources } : [createErrorObject("No sources found after processing")] };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return { sources: [createErrorObject(`Unexpected error: ${error.message}`)] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolve(url, referer) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
headers: {
|
||||||
|
Referer: referer,
|
||||||
|
"User-Agent": headers['User-Agent']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Resolve failed for ${url}: Status ${response.status}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.text();
|
||||||
|
const packedDataRegex = /eval\(function(.*?)split.*\)\)\)/s;
|
||||||
|
const packedDataMatch = data.match(packedDataRegex);
|
||||||
|
|
||||||
|
if (packedDataMatch) {
|
||||||
|
const packedJS = packedDataMatch[0];
|
||||||
|
const unpacker = new JsUnpacker(packedJS);
|
||||||
|
|
||||||
|
if (unpacker.detect()) {
|
||||||
|
const unpackedJS = unpacker.unpack();
|
||||||
|
if (!unpackedJS) {
|
||||||
|
console.error("JsUnpacker failed to unpack");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await parseSubs(unpackedJS);
|
||||||
|
|
||||||
|
const fileRegex = /sources\s*:\s*\[\s*\{\s*file\s*:\s*"([^"]+)"/;
|
||||||
|
const matchUri = unpackedJS.match(fileRegex);
|
||||||
|
|
||||||
|
if (matchUri && matchUri[1]) {
|
||||||
|
return matchUri[1];
|
||||||
|
} else {
|
||||||
|
console.error("Could not find file URL in unpacked JS");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("JsUnpacker could not detect packed data in resolve response");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("No packed JS data found in resolve response for:", url);
|
||||||
|
if (url.includes('.m3u8')) return url;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error during resolve for ${url}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseM3U8(m3u8Url, referer) {
|
||||||
|
const parsedSources = [];
|
||||||
|
try {
|
||||||
|
const response = await fetch(m3u8Url, {
|
||||||
|
headers: {
|
||||||
|
"User-Agent": headers['User-Agent'],
|
||||||
|
"Referer": referer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Failed to fetch M3U8 ${m3u8Url}: Status ${response.status}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const m3u8Content = await response.text();
|
||||||
|
const lines = m3u8Content.split('\n');
|
||||||
|
let currentQuality = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = lines[i].trim();
|
||||||
|
|
||||||
|
if (line.startsWith('#EXT-X-STREAM-INF:')) {
|
||||||
|
currentQuality = null;
|
||||||
|
const resolutionMatch = line.match(/RESOLUTION=\d+x(\d+)/);
|
||||||
|
if (resolutionMatch && resolutionMatch[1]) {
|
||||||
|
currentQuality = resolutionMatch[1] + "p";
|
||||||
|
} else {
|
||||||
|
const bwMatch = line.match(/BANDWIDTH=(\d+)/);
|
||||||
|
if (bwMatch) {
|
||||||
|
currentQuality = `bw${bwMatch[1]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (currentQuality && (line.startsWith('http') || (line.includes('.m3u8') && !line.startsWith('#')))) {
|
||||||
|
let resolvedUrl = line;
|
||||||
|
if (!line.startsWith('http')) {
|
||||||
|
const baseUrl = new URL(m3u8Url);
|
||||||
|
resolvedUrl = new URL(line, baseUrl.origin + baseUrl.pathname.substring(0, baseUrl.pathname.lastIndexOf('/') + 1)).href;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedSources.push({
|
||||||
|
file: resolvedUrl,
|
||||||
|
type: 'hls',
|
||||||
|
quality: currentQuality,
|
||||||
|
lang: 'en'
|
||||||
|
});
|
||||||
|
currentQuality = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsedSources.sort((a, b) => parseQuality(b.quality) - parseQuality(a.quality));
|
||||||
|
|
||||||
|
return parsedSources;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error parsing M3U8 ${m3u8Url}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function parseSubs(scriptstring) {
|
||||||
|
if (typeof subtitles === 'undefined') {
|
||||||
|
console.error("Subtitles array is not accessible in parseSubs");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const setupMatch = scriptstring.match(/jwplayer\([\s\S]*?\)\.setup\((\{[\s\S]*?\})\)/);
|
||||||
|
if (!setupMatch || !setupMatch[1]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let setupStr = setupMatch[1];
|
||||||
|
|
||||||
|
setupStr = setupStr.replace(/\\'/g, "'");
|
||||||
|
setupStr = setupStr.replace(/'/g, '"');
|
||||||
|
setupStr = setupStr.replace(/([{,]\s*)([a-zA-Z0-9_$]+)(\s*:)/g, '$1"$2"$3');
|
||||||
|
setupStr = setupStr.replace(/,\s*([}\]])/g, '$1');
|
||||||
|
|
||||||
|
let setupConfig;
|
||||||
|
try {
|
||||||
|
setupConfig = JSON.parse(setupStr);
|
||||||
|
} catch (err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtitleTracks = (setupConfig.tracks || []).filter(track =>
|
||||||
|
track && (track.kind === "captions" || track.kind === "subtitles") && track.file
|
||||||
|
).map(track => ({
|
||||||
|
url: track.file,
|
||||||
|
lang: track.label || track.kind
|
||||||
|
}));
|
||||||
|
|
||||||
|
subtitleTracks.forEach(newSub => {
|
||||||
|
if (!subtitles.some(existingSub => existingSub.url === newSub.url && existingSub.lang === newSub.lang)) {
|
||||||
|
subtitles.push(newSub);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during subtitle parsing:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
+87
-24
@@ -1,5 +1,16 @@
|
|||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
function createErrorObject(errorMessage) {
|
||||||
|
return {
|
||||||
|
provider: "EmbedSU",
|
||||||
|
ERROR: [{
|
||||||
|
error: `ERROR`,
|
||||||
|
what_happened: errorMessage,
|
||||||
|
report_issue: 'https://github.com/Inside4ndroid/vidsrc-api-js/issues'
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function getEmbedSu(tmdb_id, s, e) {
|
export async function getEmbedSu(tmdb_id, s, e) {
|
||||||
const DOMAIN = "https://embed.su";
|
const DOMAIN = "https://embed.su";
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -11,26 +22,54 @@ export async function getEmbedSu(tmdb_id, s, e) {
|
|||||||
try {
|
try {
|
||||||
const urlSearch = s && e ? `${DOMAIN}/embed/tv/${tmdb_id}/${s}/${e}` : `${DOMAIN}/embed/movie/${tmdb_id}`;
|
const urlSearch = s && e ? `${DOMAIN}/embed/tv/${tmdb_id}/${s}/${e}` : `${DOMAIN}/embed/movie/${tmdb_id}`;
|
||||||
const htmlSearch = await fetch(urlSearch, { method: 'GET', headers });
|
const htmlSearch = await fetch(urlSearch, { method: 'GET', headers });
|
||||||
const textSearch = await htmlSearch.text();
|
|
||||||
|
|
||||||
|
if (!htmlSearch.ok) {
|
||||||
|
return { sources: [createErrorObject(`Failed to fetch initial page: HTTP ${htmlSearch.status}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const textSearch = await htmlSearch.text();
|
||||||
const hashEncodeMatch = textSearch.match(/JSON\.parse\(atob\(\`([^\`]+)/i);
|
const hashEncodeMatch = textSearch.match(/JSON\.parse\(atob\(\`([^\`]+)/i);
|
||||||
const hashEncode = hashEncodeMatch ? hashEncodeMatch[1] : "";
|
const hashEncode = hashEncodeMatch ? hashEncodeMatch[1] : "";
|
||||||
|
|
||||||
if (!hashEncode) return;
|
if (!hashEncode) {
|
||||||
|
return { sources: [createErrorObject("No encoded hash found in initial page")] };
|
||||||
|
}
|
||||||
|
|
||||||
|
let hashDecode;
|
||||||
|
try {
|
||||||
|
hashDecode = JSON.parse(await stringAtob(hashEncode));
|
||||||
|
} catch (e) {
|
||||||
|
return { sources: [createErrorObject(`Failed to decode initial hash: ${e.message}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
const hashDecode = JSON.parse(await stringAtob(hashEncode));
|
|
||||||
const mEncrypt = hashDecode.hash;
|
const mEncrypt = hashDecode.hash;
|
||||||
if (!mEncrypt) return;
|
if (!mEncrypt) {
|
||||||
|
return { sources: [createErrorObject("No encrypted hash found in decoded data")] };
|
||||||
|
}
|
||||||
|
|
||||||
const firstDecode = (await stringAtob(mEncrypt)).split(".").map(item => item.split("").reverse().join(""));
|
let firstDecode;
|
||||||
const secondDecode = JSON.parse(await stringAtob(firstDecode.join("").split("").reverse().join("")));
|
try {
|
||||||
|
firstDecode = (await stringAtob(mEncrypt)).split(".").map(item => item.split("").reverse().join(""));
|
||||||
|
} catch (e) {
|
||||||
|
return { sources: [createErrorObject(`Failed to decode first layer: ${e.message}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
if (!secondDecode || secondDecode.length === 0) return;
|
let secondDecode;
|
||||||
|
try {
|
||||||
|
secondDecode = JSON.parse(await stringAtob(firstDecode.join("").split("").reverse().join("")));
|
||||||
|
} catch (e) {
|
||||||
|
return { sources: [createErrorObject(`Failed to decode second layer: ${e.message}`)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!secondDecode || secondDecode.length === 0) {
|
||||||
|
return { sources: [createErrorObject("No valid sources found after decoding")] };
|
||||||
|
}
|
||||||
|
|
||||||
const sources = [];
|
const sources = [];
|
||||||
const subtitles = [];
|
const subtitles = [];
|
||||||
|
|
||||||
for (const item of secondDecode) {
|
for (const item of secondDecode) {
|
||||||
|
try {
|
||||||
const urlDirect = `${DOMAIN}/api/e/${item.hash}`;
|
const urlDirect = `${DOMAIN}/api/e/${item.hash}`;
|
||||||
const dataDirect = await requestGet(urlDirect, {
|
const dataDirect = await requestGet(urlDirect, {
|
||||||
"Referer": DOMAIN,
|
"Referer": DOMAIN,
|
||||||
@@ -38,55 +77,78 @@ export async function getEmbedSu(tmdb_id, s, e) {
|
|||||||
"Origin": DOMAIN
|
"Origin": DOMAIN
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!dataDirect.source) continue;
|
if (!dataDirect || !dataDirect.source) {
|
||||||
|
console.warn(`No source found for hash ${item.hash}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const tracks = dataDirect.subtitles.map(sub => ({
|
const tracks = (dataDirect.subtitles || []).map(sub => ({
|
||||||
url: sub.file,
|
url: sub.file,
|
||||||
lang: sub.label.split('-')[0].trim()
|
lang: sub.label ? sub.label.split('-')[0].trim() : 'en'
|
||||||
})).filter(track => track.lang);
|
})).filter(track => track.url);
|
||||||
|
|
||||||
const requestDirectSize = await fetch(dataDirect.source, { headers, method: "GET" });
|
const requestDirectSize = await fetch(dataDirect.source, { headers, method: "GET" });
|
||||||
const parseRequest = await requestDirectSize.text();
|
if (!requestDirectSize.ok) {
|
||||||
|
console.warn(`Failed to fetch source ${dataDirect.source}: HTTP ${requestDirectSize.status}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseRequest = await requestDirectSize.text();
|
||||||
const patternSize = parseRequest.split('\n').filter(item => item.includes('/proxy/'));
|
const patternSize = parseRequest.split('\n').filter(item => item.includes('/proxy/'));
|
||||||
|
|
||||||
const directQuality = patternSize.map(patternItem => {
|
const directQuality = patternSize.map(patternItem => {
|
||||||
|
try {
|
||||||
const sizeQuality = getSizeQuality(patternItem);
|
const sizeQuality = getSizeQuality(patternItem);
|
||||||
let dURL = `${DOMAIN}${patternItem}`;
|
let dURL = `${DOMAIN}${patternItem}`;
|
||||||
dURL = dURL.replace(".png", ".m3u8");
|
dURL = dURL.replace(".png", ".m3u8");
|
||||||
return { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
|
return { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
|
||||||
});
|
} catch (e) {
|
||||||
|
console.warn(`Failed to process quality for pattern: ${patternItem}`, e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(item => item !== null);
|
||||||
|
|
||||||
if (!directQuality.length) continue;
|
if (!directQuality.length) {
|
||||||
|
console.warn(`No valid qualities found for source ${dataDirect.source}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
sources.push({
|
sources.push({
|
||||||
provider: "EmbedSu",
|
provider: "EmbedSu",
|
||||||
files: directQuality,
|
files: directQuality,
|
||||||
|
subtitles: tracks,
|
||||||
headers: {
|
headers: {
|
||||||
"Referer": DOMAIN,
|
"Referer": DOMAIN,
|
||||||
"User-Agent": headers['User-Agent'],
|
"User-Agent": headers['User-Agent'],
|
||||||
"Origin": DOMAIN
|
"Origin": DOMAIN
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
subtitles.push(...tracks);
|
console.error(`Error processing item ${item.hash}:`, e);
|
||||||
|
// Continue to next item instead of failing entire request
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (sources.length === 0) {
|
||||||
sources,
|
return { sources: [createErrorObject("No valid sources found after processing all items")] };
|
||||||
subtitles
|
}
|
||||||
};
|
|
||||||
|
return { sources };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return { provider: "EmbedSu", sources: [], subtitles: [] };
|
return { sources: [createErrorObject(`Unexpected error: ${e.message}`)] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSizeQuality(url) {
|
function getSizeQuality(url) {
|
||||||
|
try {
|
||||||
const parts = url.split('/');
|
const parts = url.split('/');
|
||||||
const base64Part = parts[parts.length - 2];
|
const base64Part = parts[parts.length - 2];
|
||||||
const decodedPart = atob(base64Part);
|
const decodedPart = atob(base64Part);
|
||||||
const sizeQuality = Number(decodedPart) || 1080;
|
return Number(decodedPart) || 1080;
|
||||||
return sizeQuality;
|
} catch (e) {
|
||||||
|
console.warn(`Failed to get size quality for URL ${url}:`, e);
|
||||||
|
return 720; // Default quality if parsing fails
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stringAtob(input) {
|
async function stringAtob(input) {
|
||||||
@@ -115,6 +177,7 @@ async function requestGet(url, headers = {}) {
|
|||||||
|
|
||||||
return await response.json();
|
return await response.json();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return "";
|
console.error(`Request failed for ${url}:`, error);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
export default class JsUnpacker {
|
||||||
|
static Unbase = class {
|
||||||
|
constructor(radix) {
|
||||||
|
this.ALPHABET_62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
this.ALPHABET_95 = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
|
||||||
|
this.alphabet = null;
|
||||||
|
this.dictionary = {};
|
||||||
|
this.radix = radix;
|
||||||
|
|
||||||
|
if (radix > 36) {
|
||||||
|
if (radix < 62) {
|
||||||
|
this.alphabet = this.ALPHABET_62.substring(0, radix);
|
||||||
|
} else if (radix > 62 && radix < 95) {
|
||||||
|
this.alphabet = this.ALPHABET_95.substring(0, radix);
|
||||||
|
} else if (radix === 62) {
|
||||||
|
this.alphabet = this.ALPHABET_62;
|
||||||
|
} else if (radix === 95) {
|
||||||
|
this.alphabet = this.ALPHABET_95;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < this.alphabet.length; i++) {
|
||||||
|
this.dictionary[this.alphabet[i]] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unbase(str) {
|
||||||
|
let ret = 0;
|
||||||
|
|
||||||
|
if (this.alphabet === null) {
|
||||||
|
ret = parseInt(str, this.radix);
|
||||||
|
} else {
|
||||||
|
const tmp = str.split('').reverse().join('');
|
||||||
|
for (let i = 0; i < tmp.length; i++) {
|
||||||
|
ret += Math.pow(this.radix, i) * this.dictionary[tmp[i]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(packedJS) {
|
||||||
|
this.packedJS = packedJS;
|
||||||
|
}
|
||||||
|
|
||||||
|
detect() {
|
||||||
|
const js = this.packedJS.replace(/\s/g, '');
|
||||||
|
const regex = /eval\(function\(p,a,c,k,e,(?:r|d)/;
|
||||||
|
return regex.test(js);
|
||||||
|
}
|
||||||
|
|
||||||
|
unpack() {
|
||||||
|
let js = this.packedJS;
|
||||||
|
try {
|
||||||
|
const regex = /\}\s*\('(.*)',\s*(.*?),\s*(\d+),\s*'(.*?)'\.split\('\|'\)/s;
|
||||||
|
const match = js.match(regex);
|
||||||
|
if (match && match.length === 5) {
|
||||||
|
let payload = match[1].replace(/\\'/g, "'");
|
||||||
|
const radixStr = match[2];
|
||||||
|
const countStr = match[3];
|
||||||
|
const symtab = match[4].split('|');
|
||||||
|
|
||||||
|
let radix = 36;
|
||||||
|
let count = 0;
|
||||||
|
try {
|
||||||
|
radix = parseInt(radixStr);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
count = parseInt(countStr);
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symtab.length !== count) {
|
||||||
|
throw new Error("Unknown p.a.c.k.e.r. encoding");
|
||||||
|
}
|
||||||
|
|
||||||
|
const unbase = new this.constructor.Unbase(radix);
|
||||||
|
const wordRegex = /\b\w+\b/g;
|
||||||
|
let decoded = payload;
|
||||||
|
let replaceOffset = 0;
|
||||||
|
let wordMatch;
|
||||||
|
|
||||||
|
while ((wordMatch = wordRegex.exec(payload)) !== null) {
|
||||||
|
const word = wordMatch[0];
|
||||||
|
const x = unbase.unbase(word);
|
||||||
|
let value = null;
|
||||||
|
if (x < symtab.length) {
|
||||||
|
value = symtab[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value && value.length > 0) {
|
||||||
|
const start = wordMatch.index + replaceOffset;
|
||||||
|
const end = start + word.length;
|
||||||
|
decoded = decoded.substring(0, start) + value + decoded.substring(end);
|
||||||
|
replaceOffset += (value.length - word.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user