diff --git a/README.md b/README.md
index 9e5892b..4d2395b 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,8 @@ Don't make it long, make it short, all processed through single rest
| `xvideos` | [](https://github.com/sinkaroid/lustpress/actions/workflows/xvideos.yml) | `Yes` | `Yes` | `Yes` | `Yes` |
| `xhamster` | [](https://github.com/sinkaroid/lustpress/actions/workflows/xhamster.yml) | `Yes` | `Yes` | `Yes` | `Yes` |
| `youporn` | [](https://github.com/sinkaroid/lustpress/actions/workflows/youporn.yml) | `Yes` | `Yes` | `Yes` | `Yes` |
+| `eporner` | — | `Yes` | `Yes` | `Yes` | `Yes` |
+| `txxx` | — | `Yes` | `Yes` | `Yes` | `Yes` |
## Prerequisites
diff --git a/src/controller/eporner/epornerGet.ts b/src/controller/eporner/epornerGet.ts
new file mode 100644
index 0000000..6f78be9
--- /dev/null
+++ b/src/controller/eporner/epornerGet.ts
@@ -0,0 +1,66 @@
+import { scrapeContent } from "../../scraper/eporner/epornerGetController";
+import c from "../../utils/options";
+import { logger } from "../../utils/logger";
+import { maybeError } from "../../utils/modifier";
+import { Request, Response } from "express";
+
+export async function getEporner(req: Request, res: Response) {
+ try {
+ const id = req.query.id as string;
+ if (!id) throw Error("Parameter id is required");
+
+ /**
+ * @api {get} /eporner/get?id=:id Get eporner
+ * @apiName Get eporner
+ * @apiGroup eporner
+ * @apiDescription Get a eporner video based on id
+ *
+ * @apiParam {String} id Video ID
+ *
+ * @apiSuccessExample {json} Success-Response:
+ * HTTP/1.1 200 OK
+ * HTTP/1.1 400 Bad Request
+ *
+ * @apiExample {curl} curl
+ * curl -i https://lust.scathach.id/eporner/get?id=ibvqvezXzcs
+ *
+ * @apiExample {js} JS/TS
+ * import axios from "axios"
+ *
+ * axios.get("https://lust.scathach.id/eporner/get?id=ibvqvezXzcs")
+ * .then(res => console.log(res.data))
+ * .catch(err => console.error(err))
+ *
+ * @apiExample {python} Python
+ * import aiohttp
+ * async with aiohttp.ClientSession() as session:
+ * async with session.get("https://lust.scathach.id/eporner/get?id=ibvqvezXzcs") as resp:
+ * print(await resp.json())
+ */
+ // https://www.eporner.com/video-ibvqvezXzcs/ivy-seduces-her-dad-s-friend/
+ // https://www.eporner.com/hd-porn/hgovoiPexQe/Risa-Tsukino/
+
+ let path: string;
+
+ if (id.startsWith("video-")) {
+ path = id;
+ } else {
+ path = `hd-porn/${id}`;
+ }
+
+ const url = `${c.EPORNER}/${path}`;
+
+ const data = await scrapeContent(url);
+ logger.info({
+ path: req.path,
+ query: req.query,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent"),
+ });
+ return res.json(data);
+ } catch (err) {
+ const e = err as Error;
+ res.status(400).json(maybeError(false, e.message));
+ }
+}
diff --git a/src/controller/eporner/epornerGetRelated.ts b/src/controller/eporner/epornerGetRelated.ts
new file mode 100644
index 0000000..b9ee25d
--- /dev/null
+++ b/src/controller/eporner/epornerGetRelated.ts
@@ -0,0 +1,28 @@
+import { scrapeContent } from "../../scraper/eporner/epornerGetRelatedController";
+import c from "../../utils/options";
+import { logger } from "../../utils/logger";
+import { maybeError } from "../../utils/modifier";
+import { Request, Response } from "express";
+
+export async function relatedEporner(req: Request, res: Response) {
+ try {
+ const id = req.query.id as string;
+ if (!id) throw Error("Parameter id is required");
+
+ const url = `${c.EPORNER}/video-${id}`;
+ const data = await scrapeContent(url);
+
+ logger.info({
+ path: req.path,
+ query: req.query,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent")
+ });
+
+ return res.json(data);
+ } catch (err) {
+ const e = err as Error;
+ res.status(400).json(maybeError(false, e.message));
+ }
+}
diff --git a/src/controller/eporner/epornerRandom.ts b/src/controller/eporner/epornerRandom.ts
new file mode 100644
index 0000000..4589168
--- /dev/null
+++ b/src/controller/eporner/epornerRandom.ts
@@ -0,0 +1,75 @@
+import { scrapeContent as searchScrape } from "../../scraper/eporner/epornerSearchController";
+import { scrapeContent as videoScrape } from "../../scraper/eporner/epornerGetController";
+import c from "../../utils/options";
+import { logger } from "../../utils/logger";
+import { maybeError } from "../../utils/modifier";
+import { Request, Response } from "express";
+import LustPress from "../../LustPress";
+
+const lust = new LustPress();
+
+export async function randomEporner(req: Request, res: Response) {
+ /**
+ * @api {get} /eporner/random Get random eporner
+ * @apiName Get random eporner
+ * @apiGroup eporner
+ * @apiDescription Get a random eporner video
+ *
+ * @apiParam {String} id Video ID
+ *
+ * @apiSuccessExample {json} Success-Response:
+ * HTTP/1.1 200 OK
+ * HTTP/1.1 400 Bad Request
+ *
+ * @apiExample {curl} curl
+ * curl -i https://lust.scathach.id/eporner/random
+ *
+ * @apiExample {js} JS/TS
+ * import axios from "axios"
+ *
+ * axios.get("https://lust.scathach.id/eporner/random")
+ * .then(res => console.log(res.data))
+ * .catch(err => console.error(err))
+ *
+ * @apiExample {python} Python
+ * import aiohttp
+ * async with aiohttp.ClientSession() as session:
+ * async with session.get("https://lust.scathach.id/eporner/random") as resp:
+ * print(await resp.json())
+ */
+
+ // cat/all/SORT-top-weekly/
+
+ try {
+ const weeklyUrl = `${c.EPORNER}/cat/all/SORT-top-weekly/`;
+ const list = await searchScrape(weeklyUrl);
+
+ if (!list.data.length) {
+ throw new Error("No weekly top videos found");
+ }
+
+ const random = list.data[Math.floor(Math.random() * list.data.length)];
+
+ let path: string;
+
+ if (random.id.startsWith("video-")) {
+ path = random.id;
+ } else {
+ path = `hd-porn/${random.id}`;
+ }
+
+ const url = `${c.EPORNER}/${path}`;
+ const data = await videoScrape(url);
+
+ logger.info({
+ path: req.path,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent"),
+ });
+
+ return res.json(data);
+ } catch (err) {
+ res.status(400).json(maybeError(false, (err as Error).message));
+ }
+}
\ No newline at end of file
diff --git a/src/controller/eporner/epornerSearch.ts b/src/controller/eporner/epornerSearch.ts
new file mode 100644
index 0000000..1daa093
--- /dev/null
+++ b/src/controller/eporner/epornerSearch.ts
@@ -0,0 +1,72 @@
+import { scrapeContent } from "../../scraper/eporner/epornerSearchController";
+import c from "../../utils/options";
+import { logger } from "../../utils/logger";
+import { maybeError, spacer } from "../../utils/modifier";
+import { Request, Response } from "express";
+
+export async function searchEporner(req: Request, res: Response) {
+ try {
+ /**
+ * @api {get} /eporner/search Search eporner videos
+ * @apiName Search eporner
+ * @apiGroup eporner
+ * @apiDescription Search eporner videos
+ * @apiParam {String} key Keyword to search
+ * @apiParam {Number} [page=1] Page number
+ *
+ * @apiSuccessExample {json} Success-Response:
+ * HTTP/1.1 200 OK
+ * HTTP/1.1 400 Bad Request
+ *
+ * @apiExample {curl} curl
+ * curl -i https://lust.scathach.id/eporner/search?key=milf
+ * curl -i https://lust.scathach.id/eporner/search?key=milf&page=2
+ *
+ * @apiExample {js} JS/TS
+ * import axios from "axios"
+ *
+ * axios.get("https://lust.scathach.id/eporner/search?key=milf")
+ * .then(res => console.log(res.data))
+ * .catch(err => console.error(err))
+ *
+ * @apiExample {python} Python
+ * import aiohttp
+ * async with aiohttp.ClientSession() as session:
+ * async with session.get("https://lust.scathach.id/eporner/search?key=milf") as resp:
+ * print(await resp.json())
+ */
+
+ // https://www.eporner.com/tag/milf/
+
+ const key = req.query.key as string;
+ const page = Number(req.query.page || 1);
+
+ if (!key) throw Error("Parameter key is required");
+ if (isNaN(page)) throw Error("Parameter page must be a number");
+
+ const slug = key
+ .toLowerCase()
+ .trim()
+ .replace(/\s+/g, "-");
+
+ const url =
+ page === 1
+ ? `${c.EPORNER}/tag/${slug}/`
+ : `${c.EPORNER}/tag/${slug}/${page}/`;
+
+ const data = await scrapeContent(url);
+
+ logger.info({
+ path: req.path,
+ query: req.query,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent")
+ });
+
+ return res.json(data);
+ } catch (err) {
+ const e = err as Error;
+ res.status(400).json(maybeError(false, e.message));
+ }
+}
\ No newline at end of file
diff --git a/src/controller/txxx/txxxGet.ts b/src/controller/txxx/txxxGet.ts
new file mode 100644
index 0000000..134d810
--- /dev/null
+++ b/src/controller/txxx/txxxGet.ts
@@ -0,0 +1,82 @@
+import { Request, Response } from "express";
+import LustPress from "../../LustPress";
+import { maybeError } from "../../utils/modifier";
+import { logger } from "../../utils/logger";
+import { IVideoData } from "../../interfaces";
+
+const lust = new LustPress();
+
+// Generate sharded API URL exactly like TXXX expects
+function getApiUrl(videoId: string): string {
+ const id = Number(videoId);
+ if (Number.isNaN(id)) throw new Error("Invalid video id");
+
+ const million = Math.floor(id / 1_000_000) * 1_000_000;
+ const thousand = Math.floor(id / 1_000) * 1_000;
+
+ return `https://txxx.com/api/json/video/86400/${million}/${thousand}/${id}.json`;
+}
+
+export async function getTxxx(req: Request, res: Response) {
+ try {
+ const id = String(req.query.id || "").trim();
+ if (!id) throw new Error("Parameter id is required");
+
+ const apiUrl = getApiUrl(id);
+
+ const buffer = await lust.fetchBody(apiUrl);
+ const parsed = JSON.parse(buffer.toString("utf-8"));
+
+ if (!parsed?.video) {
+ throw new Error("Invalid API response");
+ }
+
+ const video = parsed.video;
+
+ const categories = Object.values(video.categories || {}).map(
+ (c: any) => c.title,
+ );
+
+ const tags = Object.values(video.tags || {}).map((t: any) => t.title);
+
+ const models = Object.values(video.models || {}).map((m: any) => m.title);
+
+ const videoDir = video.dir || "";
+ const videoId = video.video_id;
+
+ const embed = `https://txxx.com/embed/${videoId}/`;
+
+ const response: IVideoData = {
+ success: true,
+ data: {
+ title: video.title || "None",
+ id: `${videoId}`,
+ image: video.thumbsrc || video.thumb || "None",
+ duration: video.duration || "None",
+ views: video.statistics?.viewed || "0",
+ rating: video.statistics?.rating || "0.00",
+ uploaded: video.post_date || "None",
+ upvoted: String(video.statistics?.likes || "0"),
+ downvoted: String(video.statistics?.dislikes || "0"),
+ channel: video.channel?.title || "",
+ models: models.length > 0 ? models : video.models_suggested || [],
+ tags: [...categories, ...tags],
+ },
+ source: `https://txxx.com/videos/${videoId}/${videoDir}/`,
+ assets: [embed, video.thumbsrc].filter(Boolean),
+ };
+
+ logger.info({
+ path: req.path,
+ query: req.query,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent"),
+ });
+
+ return res.json(response);
+ } catch (err) {
+ const e = err as Error;
+ return res.status(400).json(maybeError(false, e.message));
+ }
+}
diff --git a/src/controller/txxx/txxxGetRelated.ts b/src/controller/txxx/txxxGetRelated.ts
new file mode 100644
index 0000000..c21e6ba
--- /dev/null
+++ b/src/controller/txxx/txxxGetRelated.ts
@@ -0,0 +1,69 @@
+import { Request, Response } from "express";
+import LustPress from "../../LustPress";
+import { maybeError } from "../../utils/modifier";
+import { logger } from "../../utils/logger";
+import { ISearchVideoData } from "../../interfaces";
+
+const lust = new LustPress();
+
+function getRelatedApiUrl(videoId: string, page = 1, count = 50): string {
+ const id = Number(videoId);
+ if (Number.isNaN(id)) throw new Error("Invalid video id");
+
+ const million = Math.floor(id / 1_000_000) * 1_000_000;
+ const thousand = Math.floor(id / 1_000) * 1_000;
+
+ return (
+ `https://txxx.com/api/json/videos_related2/` +
+ `432000/${count}/${million}/${thousand}/${id}.all.${page}.json`
+ );
+}
+
+export async function relatedTxxx(req: Request, res: Response) {
+ try {
+ const id = String(req.query.id || "").trim();
+ const page = Number(req.query.page || 1);
+
+ if (!id) throw new Error("Parameter id is required");
+ if (Number.isNaN(page)) throw new Error("Parameter page must be a number");
+
+ const apiUrl = getRelatedApiUrl(id, page);
+
+ const buffer = await lust.fetchBody(apiUrl);
+ const rawData = JSON.parse(buffer.toString("utf-8"));
+
+ const videos = Array.isArray(rawData.videos) ? rawData.videos : [];
+
+ const data = videos.map((v: any) => ({
+ id: v.video_id,
+ title: v.title,
+ image: v.scr || v.thumb || null,
+ duration: v.duration || "None",
+ views: v.video_viewed || "0",
+ rating: v.rating || "0",
+ uploader: v.username || v.display_name || "",
+ link: `https://txxx.com/videos/${v.video_id}/${v.dir}/`,
+ video: `https://txxx.com/embed/${v.video_id}/`,
+ }));
+
+ logger.info({
+ path: req.path,
+ query: req.query,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent"),
+ });
+
+ return res.json({
+ success: true,
+ total_count: rawData.total_count || "0",
+ pages: rawData.pages || 1,
+ page,
+ data,
+ source: `https://txxx.com/videos/${id}/`,
+ } as ISearchVideoData);
+ } catch (err) {
+ const e = err as Error;
+ return res.status(400).json(maybeError(false, e.message));
+ }
+}
diff --git a/src/controller/txxx/txxxRandom.ts b/src/controller/txxx/txxxRandom.ts
new file mode 100644
index 0000000..be198c4
--- /dev/null
+++ b/src/controller/txxx/txxxRandom.ts
@@ -0,0 +1,58 @@
+import { Request, Response } from "express";
+import LustPress from "../../LustPress";
+import { maybeError } from "../../utils/modifier";
+import { logger } from "../../utils/logger";
+
+const lust = new LustPress();
+
+export async function randomTxxx(req: Request, res: Response) {
+ try {
+ const apiUrl =
+ "https://txxx.com/api/json/videos2/14400/str/most-popular/60/..1.all..day.json";
+
+ const buffer = await lust.fetchBody(apiUrl);
+ const rawData = JSON.parse(buffer.toString("utf-8"));
+
+ const videos = Array.isArray(rawData.videos)
+ ? rawData.videos
+ : [];
+
+ if (videos.length === 0) {
+ throw new Error("No videos returned from upstream");
+ }
+
+ // Pick one random video
+ const v = videos[Math.floor(Math.random() * videos.length)];
+
+ const data = {
+ video_id: v.video_id,
+ title: v.title,
+ dir: v.dir,
+ duration: v.duration,
+ views: v.video_viewed,
+ rating: v.rating,
+ uploaded: v.post_date,
+ likes: v.likes,
+ dislikes: v.dislikes,
+ image: v.scr,
+ categories: v.categories ? v.categories.split(",") : [],
+ embed: `https://txxx.com/embed/${v.video_id}/`,
+ };
+
+ logger.info({
+ path: req.path,
+ method: req.method,
+ ip: req.ip,
+ useragent: req.get("User-Agent"),
+ });
+
+ return res.json({
+ success: true,
+ data,
+ source: apiUrl,
+ });
+ } catch (err) {
+ const e = err as Error;
+ return res.status(400).json(maybeError(false, e.message));
+ }
+}
diff --git a/src/controller/txxx/txxxSearch.ts b/src/controller/txxx/txxxSearch.ts
new file mode 100644
index 0000000..4a43986
--- /dev/null
+++ b/src/controller/txxx/txxxSearch.ts
@@ -0,0 +1,63 @@
+import { Request, Response } from "express";
+import LustPress from "../../LustPress";
+import { maybeError } from "../../utils/modifier";
+
+const lust = new LustPress();
+
+export async function searchTxxx(req: Request, res: Response) {
+ try {
+ const key = String(req.query.key || "").trim();
+ const page = Number(req.query.page || 1);
+
+ if (!key) {
+ return res.json({
+ success: false,
+ error: "Parameter key is required",
+ });
+ }
+
+ if (Number.isNaN(page)) {
+ return res.json({
+ success: false,
+ error: "Parameter page must be a number",
+ });
+ }
+
+ const apiUrl =
+ `https://txxx.com/api/videos2.php` +
+ `?params=259200/str/relevance/60/search..${page}.all..` +
+ `&s=${encodeURIComponent(key)}`;
+
+ // Fetch from API directly
+ const buffer = await lust.fetchBody(apiUrl);
+ const rawData = JSON.parse(buffer.toString("utf-8"));
+
+ const videos = Array.isArray(rawData.videos) ? rawData.videos : [];
+
+ const data = videos.map((v: any) => ({
+ video_id: v.video_id,
+ title: v.title,
+ dir: v.dir,
+ duration: v.duration,
+ views: v.video_viewed,
+ rating: v.rating,
+ uploaded: v.post_date,
+ likes: v.likes,
+ dislikes: v.dislikes,
+ image: v.scr,
+ categories: v.categories ? v.categories.split(",") : [],
+ embed: `https://txxx.com/embed/${v.video_id}/`,
+ }));
+
+ return res.json({
+ success: true,
+ total_count: String(rawData.total_count ?? videos.length),
+ pages: Number(rawData.pages ?? 1),
+ page,
+ data,
+ });
+ } catch (err) {
+ const e = err as Error;
+ return res.json(maybeError(false, e.message));
+ }
+}
diff --git a/src/controller/xhamster/xhamsterGet.ts b/src/controller/xhamster/xhamsterGet.ts
index 981b67a..a9159ec 100644
--- a/src/controller/xhamster/xhamsterGet.ts
+++ b/src/controller/xhamster/xhamsterGet.ts
@@ -14,23 +14,23 @@ export async function getXhamster(req: Request, res: Response) {
* @apiName Get xhamster
* @apiGroup xhamster
* @apiDescription Get a xhamster video based on id
- *
+ *
* @apiParam {String} id Video ID
- *
+ *
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
- *
+ *
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/xhamster/get?id=videos/horny-makima-tests-new-toy-and-cums-intensely-xhAa5wx
- *
+ *
* @apiExample {js} JS/TS
* import axios from "axios"
- *
+ *
* axios.get("https://lust.scathach.id/xhamster/get?id=videos/horny-makima-tests-new-toy-and-cums-intensely-xhAa5wx")
* .then(res => console.log(res.data))
* .catch(err => console.error(err))
- *
+ *
* @apiExample {python} Python
* import aiohttp
* async with aiohttp.ClientSession() as session:
@@ -38,14 +38,14 @@ export async function getXhamster(req: Request, res: Response) {
* print(await resp.json())
*/
- const url = `${c.XHAMSTER}/${id}`;
+ const url = `${c.XHAMSTER}/videos/${id}`;
const data = await scrapeContent(url);
logger.info({
path: req.path,
query: req.query,
method: req.method,
ip: req.ip,
- useragent: req.get("User-Agent")
+ useragent: req.get("User-Agent"),
});
return res.json(data);
} catch (err) {
diff --git a/src/controller/xhamster/xhamsterRandom.ts b/src/controller/xhamster/xhamsterRandom.ts
index b2dd4ae..f72d324 100644
--- a/src/controller/xhamster/xhamsterRandom.ts
+++ b/src/controller/xhamster/xhamsterRandom.ts
@@ -10,51 +10,58 @@ const lust = new LustPress();
export async function randomXhamster(req: Request, res: Response) {
try {
-
-
/**
- * @api {get} /xhamster/random Get random xhamster
+ * @api {get} /xhamster/random Get random xhamster video
* @apiName Get random xhamster
* @apiGroup xhamster
- * @apiDescription Get a random xhamster video
- *
+ * @apiDescription Get a random xhamster video from the list of newest videos.
+ *
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
- *
+ *
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/xhamster/random
- *
+ *
* @apiExample {js} JS/TS
* import axios from "axios"
- *
+ *
* axios.get("https://lust.scathach.id/xhamster/random")
* .then(res => console.log(res.data))
* .catch(err => console.error(err))
- *
+ *
* @apiExample {python} Python
* import aiohttp
* async with aiohttp.ClientSession() as session:
* async with session.get("https://lust.scathach.id/xhamster/random") as resp:
* print(await resp.json())
*/
+
const resolve = await lust.fetchBody(`${c.XHAMSTER}/newest`);
const $ = load(resolve);
- const search = $("a.root-9d8b4.video-thumb-info__name.role-pop.with-dropdown")
- .map((i, el) => $(el).attr("href"))
- .get();
- const search_ = search.map((el) => el.replace(c.XHAMSTER, ""));
- const random = Math.floor(Math.random() * search_.length);
- const url = c.XHAMSTER + search_[random];
- const data = await scrapeContent(url);
+ const videoLinks = $("div.thumb-list__item[data-video-id]")
+ .map((_, el) => {
+ const href = $(el).find("a[data-role='thumb-link']").attr("href");
+ return href && href.includes("/videos/") ? href : null;
+ })
+ .get()
+ .filter(Boolean);
+
+ // Select a random video URL from the list
+ const randomIndex = Math.floor(Math.random() * videoLinks.length);
+ const randomUrl = videoLinks[randomIndex];
+
+ const data = await scrapeContent(randomUrl);
+
logger.info({
path: req.path,
query: req.query,
method: req.method,
ip: req.ip,
- useragent: req.get("User-Agent")
+ useragent: req.get("User-Agent"),
});
+
return res.json(data);
} catch (err) {
const e = err as Error;
diff --git a/src/interfaces.ts b/src/interfaces.ts
index 5131eb2..49c35f9 100644
--- a/src/interfaces.ts
+++ b/src/interfaces.ts
@@ -3,13 +3,14 @@ export interface IVideoData {
data: {
title: string;
id: string;
- image: string;
+ image?: string;
duration: string;
views: string;
- rating: string;
+ rating?: string;
uploaded: string;
upvoted: string | null;
downvoted: string | null;
+ channel?: string;
models: string[];
tags: string[];
};
@@ -23,6 +24,19 @@ export interface ISearchVideoData {
source: string;
}
+export interface ISearchItem {
+ link: string;
+ id: string;
+ title?: string;
+ image?: string;
+ duration?: string;
+ rating?: string;
+ views?: string;
+ uploader?: string;
+ video?: string;
+}
+
+
export interface MaybeError {
message: string;
}
diff --git a/src/router/endpoint.ts b/src/router/endpoint.ts
index 8303d73..379ccc5 100644
--- a/src/router/endpoint.ts
+++ b/src/router/endpoint.ts
@@ -2,6 +2,19 @@ import cors from "cors";
import { Router } from "express";
import { slow, limiter } from "../utils/limit-options";
+
+// EPorner
+import { getEporner } from "../controller/eporner/epornerGet";
+import { searchEporner } from "../controller/eporner/epornerSearch";
+import { relatedEporner } from "../controller/eporner/epornerGetRelated";
+import { randomEporner } from "../controller/eporner/epornerRandom";
+
+// TXXX
+import { getTxxx } from "../controller/txxx/txxxGet";
+import { searchTxxx } from "../controller/txxx/txxxSearch";
+import { relatedTxxx } from "../controller/txxx/txxxGetRelated";
+import { randomTxxx } from "../controller/txxx/txxxRandom";
+
// PornHub
import { getPornhub } from "../controller/pornhub/pornhubGet";
import { searchPornhub } from "../controller/pornhub/pornhubSearch";
@@ -65,6 +78,14 @@ function scrapeRoutes() {
router.get("/youporn/search", cors(), slow, limiter, searchYouporn);
router.get("/youporn/related", cors(), slow, limiter, relatedYouporn);
router.get("/youporn/random", cors(), slow, limiter, randomYouporn);
+ router.get("/eporner/get", cors(), slow, limiter, getEporner);
+ router.get("/eporner/search", cors(), slow, limiter, searchEporner);
+ router.get("/eporner/related", cors(), slow, limiter, relatedEporner);
+ router.get("/eporner/random", cors(), slow, limiter, randomEporner);
+ router.get("/txxx/get", cors(), slow, limiter, getTxxx);
+ router.get("/txxx/search", cors(), slow, limiter, searchTxxx);
+ router.get("/txxx/related", cors(), slow, limiter, relatedTxxx);
+ router.get("/txxx/random", cors(), slow, limiter, randomTxxx);
return router;
}
diff --git a/src/scraper/eporner/epornerGetController.ts b/src/scraper/eporner/epornerGetController.ts
new file mode 100644
index 0000000..101f1b1
--- /dev/null
+++ b/src/scraper/eporner/epornerGetController.ts
@@ -0,0 +1,96 @@
+import { load } from "cheerio";
+import LustPress from "../../LustPress";
+import { IVideoData } from "../../interfaces";
+
+const lust = new LustPress();
+
+function calculateRatingFromStrings(
+ upVote: string,
+ downVote: string
+): number {
+ const up = parseInt(upVote.replace(/,/g, ""), 10) || 0;
+ const down = parseInt(downVote.replace(/,/g, ""), 10) || 0;
+
+ const total = up + down;
+ if (total === 0) return 0;
+
+ return up / total;
+}
+
+export async function scrapeContent(url: string) {
+ try {
+ const resolve = await lust.fetchBody(url);
+ const $ = load(resolve);
+
+ class EPorner {
+ link: string;
+ id: string;
+ title: string;
+ image: string;
+ duration: string;
+ views: string;
+ rating: string;
+ publish: string;
+ upVote: string;
+ downVote: string;
+ video: string;
+ tags: string[];
+ models: string[];
+ constructor() {
+ // https://www.eporner.com/video-ibvqvezXzcs/ivy-seduces-her-dad-s-friend/
+
+ this.link = $("link[rel='canonical']").attr("href") || "None";
+ this.id = this.link.split("/")[3] + "/" + this.link.split("/")[4] || "None";
+
+ this.title = $("meta[property='og:title']").attr("content") || "None";
+ this.image = $("meta[property='og:image']").attr("content") || "None";
+ this.duration = $("meta[property='og:duration']").attr("content") || "0";
+ this.views = $("#cinemaviews1").text().trim() || "0";
+ this.upVote = $(".likeup i").first().text().trim() || "0";
+ this.downVote = $(".likedown i").first().text().trim() || "0";
+
+ const jsonLdText = $('script[type="application/ld+json"]').first().html() || "{}";
+ const jsonLd = JSON.parse(jsonLdText);
+ const isoDuration = jsonLd.duration; // "PT00H8M12S"
+ this.publish = jsonLd.uploadDate || "None";
+
+ const ratingValue = calculateRatingFromStrings(this.upVote, this.downVote);
+ this.rating = (ratingValue * 100).toFixed(2);
+
+ const videoPart = this.link.split("/")[3] || "None";
+ const code = videoPart.replace("video-", "");
+ this.video = `https://www.eporner.com/embed/${code}/`;
+
+ this.tags = $("li.vit-category a")
+ .map((_, el) => $(el).text().trim()).get();
+
+ this.models = $("li.vit-pornstar a")
+ .map((_, el) => $(el).text().trim()).get();
+ }
+ }
+
+ const ep = new EPorner();
+ const data: IVideoData = {
+ success: true,
+ data: {
+ title: lust.removeHtmlTagWithoutSpace(ep.title),
+ id: ep.id,
+ image: ep.image,
+ duration: lust.secondToMinute(Number(ep.duration)),
+ views: ep.views,
+ rating: ep.rating,
+ uploaded: ep.publish,
+ upvoted: ep.upVote,
+ downvoted: ep.downVote,
+ models: ep.models,
+ tags: ep.tags
+ },
+ source: ep.link,
+ assets: [ep.video, ep.image]
+ };
+ return data;
+ } catch (err) {
+ const e = err as Error;
+ throw Error(e.message);
+ }
+}
\ No newline at end of file
diff --git a/src/scraper/eporner/epornerGetRelatedController.ts b/src/scraper/eporner/epornerGetRelatedController.ts
new file mode 100644
index 0000000..6d55c7a
--- /dev/null
+++ b/src/scraper/eporner/epornerGetRelatedController.ts
@@ -0,0 +1,46 @@
+import { load } from "cheerio";
+import LustPress from "../../LustPress";
+import { ISearchVideoData } from "../../interfaces";
+
+const lust = new LustPress();
+
+export async function scrapeContent(url: string) {
+ const html = await lust.fetchBody(url);
+ const $ = load(html);
+
+ const data = $("#relateddiv .mb")
+ .map((i, el) => {
+ const link = $(el).find(".mbimg a").first().attr("href") || "";
+ const match = link.match(/\/(video-|hd-porn\/)([^/]+)/);
+ let id = "";
+ if (match) {
+ if (match[1] === "video-") {
+ id = `video-${match[2]}`;
+ } else {
+ // hd-porn
+ id = match[2];
+ }
+ }
+
+ return {
+ link: `https://www.eporner.com${link}`,
+ id,
+ title: $(el).find(".mbtit a").text().trim(),
+ image:
+ $(el).find("img").attr("data-src") || $(el).find("img").attr("src"),
+ duration: $(el).find(".mbtim").text().trim(),
+ rating: $(el).find(".mbrate").text().trim(),
+ views: $(el).find(".mbvie").text().trim(),
+ uploader: $(el).find(".mb-uploader a").text().trim(),
+ video: `https://www.eporner.com/embed/${id}`,
+ };
+ })
+ .get()
+ .filter((v) => v.id && v.image);
+
+ return {
+ success: true,
+ data: data as unknown as string[],
+ source: url,
+ } as ISearchVideoData;
+}
\ No newline at end of file
diff --git a/src/scraper/eporner/epornerSearchController.ts b/src/scraper/eporner/epornerSearchController.ts
new file mode 100644
index 0000000..0855ee7
--- /dev/null
+++ b/src/scraper/eporner/epornerSearchController.ts
@@ -0,0 +1,70 @@
+import { load } from "cheerio";
+import LustPress from "../../LustPress";
+import { ISearchItem } from "../../interfaces";
+
+const lust = new LustPress();
+
+export async function scrapeContent(url: string) {
+ try {
+ const res = await lust.fetchBody(url);
+ const $ = load(res);
+
+ class EPornerSearch {
+ data: ISearchItem[];
+
+ constructor() {
+ this.data = $("#vidresults .mb")
+ .map((i, el) => {
+ const link =
+ $(el).find(".mbimg a").first().attr("href") || "";
+
+ const match = link.match(/\/(video-|hd-porn\/)([^/]+)/);
+
+ let id = "";
+
+ if (match) {
+ if (match[1] === "video-") {
+ id = `video-${match[2]}`;
+ } else {
+ id = match[2];
+ }
+ }
+
+ const code = id.startsWith("video-")
+ ? id.replace("video-", "")
+ : id;
+
+ return {
+ link: `https://www.eporner.com${link}`,
+ id,
+ title: $(el).find(".mbtit a").text().trim(),
+ image: $(el).find(".mbimg img").attr("src"),
+ duration: $(el).find(".mbtim").text().trim(),
+ rating: $(el).find(".mbrate").text().trim(),
+ views: $(el).find(".mbvie").text().trim(),
+ uploader: $(el)
+ .find(".mb-uploader a")
+ .text()
+ .trim(),
+ video: code
+ ? `https://www.eporner.com/embed/${code}/`
+ : "None",
+ };
+ })
+ .get();
+ }
+ }
+
+ const ep = new EPornerSearch();
+ if (ep.data.length === 0) throw Error("No result found");
+
+ return {
+ success: true,
+ data: ep.data as ISearchItem[],
+ source: url,
+ };
+ } catch (err) {
+ const e = err as Error;
+ throw Error(e.message);
+ }
+}
\ No newline at end of file
diff --git a/src/scraper/pornhub/pornhubGetController.ts b/src/scraper/pornhub/pornhubGetController.ts
index 14d5248..0d3a091 100644
--- a/src/scraper/pornhub/pornhubGetController.ts
+++ b/src/scraper/pornhub/pornhubGetController.ts
@@ -9,7 +9,7 @@ export async function scrapeContent(url: string) {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
- class PornHub {
+ class PornHub {
link: string;
id: string;
title: string;
@@ -29,28 +29,29 @@ export async function scrapeContent(url: string) {
this.title = $("meta[property='og:title']").attr("content") || "None";
this.image = $("meta[property='og:image']").attr("content") || "None";
//get span.count").text() || "None";
this.rating = $("div.ratingPercent > span.percent").text() || "None";
this.videoInfo = $("div.videoInfo").text() || "None";
this.upVote = $("span.votesUp").attr("data-rating") || "None";
this.downVote = $("span.votesDown").attr("data-rating") || "None";
- this.video = $("meta[property='og:video:url']").attr("content") || "None";
+ this.video =
+ $("meta[property='og:video:url']").attr("content") || "None";
this.tags = $("div.video-info-row")
.find("a")
.map((i, el) => {
return $(el).text();
- }).get();
+ })
+ .get();
this.tags.shift();
this.tags = this.tags.map((el) => lust.removeHtmlTagWithoutSpace(el));
- this.models = $("div.pornstarsWrapper.js-pornstarsWrapper")
- .find("a")
- .map((i, el) => {
- return $(el).attr("data-mxptext");
- }).get();
+ this.models = $("div.pornstarsWrapper.js-pornstarsWrapper a")
+ .map((i, el) => $(el).text().trim())
+ .get();
}
}
-
+
const ph = new PornHub();
const data: IVideoData = {
success: true,
@@ -65,14 +66,14 @@ export async function scrapeContent(url: string) {
upvoted: ph.upVote,
downvoted: ph.downVote,
models: ph.models,
- tags: ph.tags.filter((el) => el !== "Suggest" && el !== " Suggest")
+ tags: ph.tags.filter((el) => el !== "Suggest" && el !== " Suggest"),
},
source: ph.link,
- assets: [ph.video, ph.image]
+ assets: [ph.video, ph.image],
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
-}
\ No newline at end of file
+}
diff --git a/src/scraper/xhamster/xhamsterGetController.ts b/src/scraper/xhamster/xhamsterGetController.ts
index 7e73ac5..18ccba8 100644
--- a/src/scraper/xhamster/xhamsterGetController.ts
+++ b/src/scraper/xhamster/xhamsterGetController.ts
@@ -6,78 +6,106 @@ const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
- const resolve = await lust.fetchBody(url);
- const $ = load(resolve);
+ const buffer = await lust.fetchBody(url);
+ const $ = load(buffer.toString("utf8"));
- class Xhamster {
+ const raw = $("#initials-script").html();
+ const initials = raw
+ ? JSON.parse(
+ raw.replace(/^window\.initials\s*=\s*/, "").replace(/;$/, ""),
+ )
+ : null;
+
+ class Xhamster {
link: string;
id: string;
title: string;
image: string;
- duration: any;
+ duration: string;
views: string;
rating: string;
- publish: string;
upVote: string;
downVote: string;
- video: string;
+ publish: string;
tags: string[];
models: string[];
+ video: string;
+
constructor() {
this.link = $("link[rel='canonical']").attr("href") || "None";
- this.id = this.link.split("/")[3] + "/" + this.link.split("/")[4] || "None";
+ this.id = this.link.split("/")[4] || "None";
this.title = $("meta[property='og:title']").attr("content") || "None";
- this.image = $("meta[property='og:image']").attr("content") || "None";
- this.duration = $("script#initials-script").html() || "None";
- //remove window.initials={ and };
- this.duration = this.duration.replace("window.initials=", "");
- this.duration = this.duration.replace(/;/g, "");
- this.duration = JSON.parse(this.duration);
- this.duration = this.duration.videoModel.duration || "None";
- this.views = $("div.header-icons").find("span").first().text() || "None";
- this.rating = $("div.header-icons").find("span").eq(1).text() || "None";
- this.publish = $("div.entity-info-container__date").attr("data-tooltip") || "None";
- this.upVote = $("div.rb-new__info").text().split("/")[0].trim() || "None";
- this.downVote = $("div.rb-new__info").text().split("/")[1].trim() || "None";
- this.video = "https://xheve2.com/embed/" + this.link.split("-").pop() || "None";
- this.tags = $("a.video-tag")
- .map((i, el) => {
- return $(el).text();
- }).get();
- this.tags = this.tags.map((el) => lust.removeHtmlTagWithoutSpace(el));
- this.models = $("a.video-tag")
- .map((i, el) => {
- return $(el).attr("href");
- }
- ).get();
- this.models = this.models.filter((el) => el.startsWith("https://xheve2.com/pornstars/"));
- this.models = this.models.map((el) => el.replace("https://xheve2.com/pornstars/", ""));
+ this.image = $("meta[property='og:image']").attr("content") || "None";
+
+ // defaults
+ this.duration = "None";
+ this.views = "None";
+
+ const scripts = $("script")
+ .map((i, el) => $(el).html())
+ .get()
+ .filter(Boolean);
+
+ const videoScript = scripts.find(
+ (s) => s.includes('"videoModel"') && s.includes('"duration"'),
+ );
+
+ if (videoScript) {
+ const durMatch = videoScript.match(/"duration"\s*:\s*(\d+)/);
+ if (durMatch) this.duration = durMatch[1];
+
+ const viewMatch = videoScript.match(/"views"\s*:\s*(\d+)/);
+ if (viewMatch) this.views = viewMatch[1];
+ }
+
+ this.rating =
+ initials?.ratingComponent?.ratingModel?.value?.toString() || "None";
+ this.upVote =
+ initials?.ratingComponent?.ratingModel?.likes?.toString() || "None";
+ this.downVote =
+ initials?.ratingComponent?.ratingModel?.dislikes?.toString() ||
+ "None";
+
+ this.publish =
+ $("div.entity-info-container__date").attr("data-tooltip") || "None";
+
+ this.tags =
+ initials?.videoTagsComponent?.tags
+ ?.filter((t: any) => t.isTag)
+ .map((t: any) => t.name) || [];
+
+ this.models =
+ initials?.videoTagsComponent?.tags
+ ?.filter((t: any) => t.isPornstar)
+ .map((t: any) => t.name) || [];
+
+ const embedId = this.link.split("-").pop()?.replace("/", "");
+ this.video = embedId ? `https://xhamster.com/embed/${embedId}` : "None";
}
}
-
+
const xh = new Xhamster();
- const data: IVideoData = {
+
+ return {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(xh.title),
id: xh.id,
image: xh.image,
- duration: lust.secondToMinute(Number(xh.duration)),
+ duration: xh.duration,
views: xh.views,
rating: xh.rating,
uploaded: xh.publish,
upvoted: xh.upVote,
downvoted: xh.downVote,
models: xh.models,
- tags: xh.tags
+ tags: xh.tags,
},
source: xh.link,
- assets: [xh.video, xh.image]
- };
- return data;
-
+ assets: [xh.video, xh.image],
+ } satisfies IVideoData;
} catch (err) {
const e = err as Error;
- throw Error(e.message);
+ throw new Error(e.message);
}
-}
\ No newline at end of file
+}
diff --git a/src/scraper/xhamster/xhamsterSearchController.ts b/src/scraper/xhamster/xhamsterSearchController.ts
index 07608c1..8ff2dea 100644
--- a/src/scraper/xhamster/xhamsterSearchController.ts
+++ b/src/scraper/xhamster/xhamsterSearchController.ts
@@ -17,29 +17,32 @@ export async function scrapeContent(url: string) {
.map((i, el) => {
const views = $(el).text();
return views;
- }).get();
+ })
+ .get();
const duration = $("span[data-role='video-duration']")
.map((i, el) => {
const duration = $(el).text();
return duration;
- }).get();
+ })
+ .get();
this.search = $("a.video-thumb__image-container")
.map((i, el) => {
const link = $(el).attr("href");
return {
link: `${link}`,
- id: link?.split("/")[3] + "/" + link?.split("/")[4],
+ id: link?.split("/")[4],
title: $(el).find("img").attr("alt"),
image: $(el).find("img").attr("src"),
duration: duration[i],
views: views[i],
- video: `${c.XHAMSTER}/embed/${link?.split("-").pop()}`
+ video: `${c.XHAMSTER}/embed/${link?.split("-").pop()}`,
};
- }).get();
+ })
+ .get();
}
}
-
+
const xh = new XhamsterSearch();
if (xh.search.length === 0) throw Error("No result found");
const data = xh.search as unknown as string[];
@@ -49,7 +52,6 @@ export async function scrapeContent(url: string) {
source: url,
};
return result;
-
} catch (err) {
const e = err as Error;
throw Error(e.message);
diff --git a/src/scraper/youporn/youpornGetController.ts b/src/scraper/youporn/youpornGetController.ts
index 6518a7d..2e86985 100644
--- a/src/scraper/youporn/youpornGetController.ts
+++ b/src/scraper/youporn/youpornGetController.ts
@@ -9,7 +9,7 @@ export async function scrapeContent(url: string) {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
- class YouPorn {
+ class YouPorn {
link: string;
id: string;
title: string;
@@ -25,27 +25,37 @@ export async function scrapeContent(url: string) {
models: string[];
constructor() {
this.link = $("link[rel='canonical']").attr("href") || "None";
- this.id = this.link.replace("https://www.youporn.com/watch/", "") || "None";
+ this.id =
+ this.link.replace("https://www.youporn.com/watch/", "") || "None";
this.title = $("meta[property='og:title']").attr("content") || "None";
this.image = $("meta[property='og:image']").attr("content") || "None";
- this.duration = $("meta[property='video:duration']").attr("content") || "0";
- this.views = $("div.feature.infoValueBlock").find("div[data-value]").attr("data-value") || "0";
- this.rating = $("div.feature").find("span").text().replace(/[^0-9.,%]/g, "") || "0";
- this.publish = $("div.video-uploaded").find("span").text() || "None";
- this.upVote = this.views;
+ this.duration =
+ $("meta[property='video:duration']").attr("content") || "0";
+ this.publish =
+ $("span.publishedDate").text().replace("Published on", "").trim() ||
+ "None";
+
+ this.upVote = "None";
this.downVote = "None";
this.video = `https://www.youporn.com/embed/${this.id}`;
- this.tags = $("a[data-espnode='category_tag'], a[data-espnode='porntag_tag']")
- .map((i, el) => {
- return $(el).text();
- }).get();
- this.models = $("a[data-espnode='pornstar_tag']")
- .map((i, el) => {
- return $(el).text();
- }).get();
+ this.views = $("span.tm_infoValue").first().text() || "0";
+ this.rating = $("span.tm_rating_percent").text() || "0";
+
+ this.tags = $(
+ "div.js_scrollableContent a.bubble-porntag, \
+ a[data-espnode='category_tag'], \
+ a[data-espnode='porntag_tag']",
+ )
+ .map((i, el) => $(el).text().trim())
+ .get()
+ .filter(Boolean);
+
+ this.models = $("#metaDataPornstarInfo a.tm_pornstar_link span")
+ .map((i, el) => $(el).text().replace(",", "").trim())
+ .get();
}
}
-
+
const yp = new YouPorn();
const data: IVideoData = {
success: true,
@@ -60,14 +70,14 @@ export async function scrapeContent(url: string) {
upvoted: yp.upVote,
downvoted: yp.downVote,
models: yp.models,
- tags: yp.tags
+ tags: yp.tags,
},
source: yp.link,
- assets: [yp.video, yp.image]
+ assets: [yp.video, yp.image],
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
-}
\ No newline at end of file
+}
diff --git a/src/scraper/youporn/youpornSearchController.ts b/src/scraper/youporn/youpornSearchController.ts
index 2382fe3..b6f8d24 100644
--- a/src/scraper/youporn/youpornSearchController.ts
+++ b/src/scraper/youporn/youpornSearchController.ts
@@ -11,31 +11,74 @@ export async function scrapeContent(url: string) {
const $ = load(res);
class YouPornSearch {
- dur: string[];
+ links: string[];
+ ids: string[];
+ titles: string[];
+ images: string[];
+ durations: string[];
+ views: string[];
search: object[];
+
constructor() {
- this.dur = $("div.video-duration").map((i, el) => {
- return $(el).text();
- }).get();
- this.search = $("a[href^='/watch/']")
+ const cards = $("div.video-box.pc");
+
+ this.links = cards
+ .map((i, el) => {
+ return $(el).find("a.tm_video_link").attr("href");
+ })
+ .get();
+
+ this.ids = this.links.map((link) => link?.split("/")[2]);
+
+ this.titles = cards
+ .map((i, el) => {
+ return $(el).find("a.tm_video_title span").text().trim();
+ })
+ .get();
+
+ this.images = cards
+ .map((i, el) => {
+ return (
+ $(el).find("img.thumb-image").attr("data-src") ||
+ $(el).find("img.thumb-image").attr("src")
+ );
+ })
+ .get();
+
+ this.durations = cards
+ .map((i, el) => {
+ return $(el).find("div.tm_video_duration span").text().trim();
+ })
+ .get();
+
+ this.views = cards
+ .map((i, el) => {
+ return (
+ $(el)
+ .find(".view-rating-container .info-views")
+ .first()
+ .text()
+ .trim() || "None"
+ );
+ })
+ .get();
+
+ this.search = cards
.map((i, el) => {
- const link = $(el).attr("href");
- const id = `${link}`.split("/")[2] + "/" + `${link}`.split("/")[3];
- const title = $(el).find("div.video-box-title").text();
- const image = $(el).find("img").attr("data-thumbnail");
return {
- link: `${c.YOUPORN}${link}`,
- id: id,
- title: lust.removeHtmlTagWithoutSpace(title),
- image: image,
- duration: this.dur[i],
- views: "None",
- video: `https://www.youporn.com/embed/${id}`,
+ link: `${c.YOUPORN}${this.links[i]}`,
+ id: this.ids[i],
+ title: lust.removeHtmlTagWithoutSpace(this.titles[i]),
+ image: this.images[i],
+ duration: this.durations[i],
+ views: this.views[i],
+ video: `https://www.youporn.com/embed/${this.ids[i]}`,
};
- }).get();
+ })
+ .get();
}
}
-
+
const yp = new YouPornSearch();
if (yp.search.length === 0) throw Error("No result found");
const data = yp.search as unknown as string[];
@@ -45,9 +88,8 @@ export async function scrapeContent(url: string) {
source: url,
};
return result;
-
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
-}
\ No newline at end of file
+}
diff --git a/src/utils/options.ts b/src/utils/options.ts
index ffcdbb3..e42da93 100644
--- a/src/utils/options.ts
+++ b/src/utils/options.ts
@@ -1,9 +1,11 @@
export default {
+ EPORNER: "https://www.eporner.com",
PORNHUB: "https://www.pornhub.com",
XNXX: "https://www.xnxx.com",
REDTUBE: "https://www.redtube.com",
XVIDEOS: "https://www.xvideos.com",
- XHAMSTER: "https://xheve2.com",
+ XHAMSTER: "https://xhamster.com/",
YOUPORN: "https://www.youporn.com",
+ TXXX: "https://txxx.com/",
JAVHD: "https://javhd.today"
};
\ No newline at end of file
diff --git a/test/test.ts b/test/test.ts
index 2bd6a71..ac3a2c1 100644
--- a/test/test.ts
+++ b/test/test.ts
@@ -2,7 +2,7 @@ import c from "../src/utils/options";
import p from "phin";
for (const url of
- [c.PORNHUB, c.XNXX, c.REDTUBE, c.XVIDEOS, c.XHAMSTER, c.YOUPORN]) {
+ [c.PORNHUB, c.XNXX, c.REDTUBE, c.XVIDEOS, c.XHAMSTER, c.YOUPORN, c.EPORNER, c.TXXX]) {
p({ url }).then(res => {
if (res.statusCode !== 200) {
console.log(`${url} is not available, status code: ${res.statusCode}, check the sites or your own user-agent`);