Major Update : Added 2Embed, Update api routes, update json response format.

This commit is contained in:
Inside4ndroid
2025-04-04 23:36:41 +01:00
parent 179c8c148f
commit ce1288490d
7 changed files with 1039 additions and 62 deletions
+140 -3
View File
@@ -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.
+57 -6
View File
@@ -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}`);
}); });
+279
View File
@@ -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"
}
} }
} }
} }
+1
View File
@@ -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"
+340
View File
@@ -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);
}
}
+116 -53
View File
@@ -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,82 +22,133 @@ 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) {
const urlDirect = `${DOMAIN}/api/e/${item.hash}`; try {
const dataDirect = await requestGet(urlDirect, { const urlDirect = `${DOMAIN}/api/e/${item.hash}`;
"Referer": DOMAIN, const dataDirect = await requestGet(urlDirect, {
"User-Agent": headers['User-Agent'],
"Origin": DOMAIN
});
if (!dataDirect.source) continue;
const tracks = dataDirect.subtitles.map(sub => ({
url: sub.file,
lang: sub.label.split('-')[0].trim()
})).filter(track => track.lang);
const requestDirectSize = await fetch(dataDirect.source, { headers, method: "GET" });
const parseRequest = await requestDirectSize.text();
const patternSize = parseRequest.split('\n').filter(item => item.includes('/proxy/'));
const directQuality = patternSize.map(patternItem => {
const sizeQuality = getSizeQuality(patternItem);
let dURL = `${DOMAIN}${patternItem}`;
dURL = dURL.replace(".png", ".m3u8");
return { file: dURL, type: 'hls', quality: `${sizeQuality}p`, lang: 'en' };
});
if (!directQuality.length) continue;
sources.push({
provider: "EmbedSu",
files: directQuality,
headers: {
"Referer": DOMAIN, "Referer": DOMAIN,
"User-Agent": headers['User-Agent'], "User-Agent": headers['User-Agent'],
"Origin": DOMAIN "Origin": DOMAIN
} });
});
subtitles.push(...tracks); if (!dataDirect || !dataDirect.source) {
console.warn(`No source found for hash ${item.hash}`);
continue;
}
const tracks = (dataDirect.subtitles || []).map(sub => ({
url: sub.file,
lang: sub.label ? sub.label.split('-')[0].trim() : 'en'
})).filter(track => track.url);
const requestDirectSize = await fetch(dataDirect.source, { headers, method: "GET" });
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 directQuality = patternSize.map(patternItem => {
try {
const sizeQuality = getSizeQuality(patternItem);
let dURL = `${DOMAIN}${patternItem}`;
dURL = dURL.replace(".png", ".m3u8");
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) {
console.warn(`No valid qualities found for source ${dataDirect.source}`);
continue;
}
sources.push({
provider: "EmbedSu",
files: directQuality,
subtitles: tracks,
headers: {
"Referer": DOMAIN,
"User-Agent": headers['User-Agent'],
"Origin": DOMAIN
}
});
} catch (e) {
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) {
const parts = url.split('/'); try {
const base64Part = parts[parts.length - 2]; const parts = url.split('/');
const decodedPart = atob(base64Part); const base64Part = parts[parts.length - 2];
const sizeQuality = Number(decodedPart) || 1080; const decodedPart = atob(base64Part);
return sizeQuality; return Number(decodedPart) || 1080;
} 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;
} }
} }
+106
View File
@@ -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;
}
}