feat: add Eporner and Txxx along with minor fixes (#16)
Co-authored-by: Pi Patel <pi.patel@gmail.com>
This commit is contained in:
@@ -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
|
||||
<table>
|
||||
|
||||
66
src/controller/eporner/epornerGet.ts
Normal file
66
src/controller/eporner/epornerGet.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
28
src/controller/eporner/epornerGetRelated.ts
Normal file
28
src/controller/eporner/epornerGetRelated.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
75
src/controller/eporner/epornerRandom.ts
Normal file
75
src/controller/eporner/epornerRandom.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
72
src/controller/eporner/epornerSearch.ts
Normal file
72
src/controller/eporner/epornerSearch.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
82
src/controller/txxx/txxxGet.ts
Normal file
82
src/controller/txxx/txxxGet.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
69
src/controller/txxx/txxxGetRelated.ts
Normal file
69
src/controller/txxx/txxxGetRelated.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
58
src/controller/txxx/txxxRandom.ts
Normal file
58
src/controller/txxx/txxxRandom.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
63
src/controller/txxx/txxxSearch.ts
Normal file
63
src/controller/txxx/txxxSearch.ts
Normal file
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
96
src/scraper/eporner/epornerGetController.ts
Normal file
96
src/scraper/eporner/epornerGetController.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
46
src/scraper/eporner/epornerGetRelatedController.ts
Normal file
46
src/scraper/eporner/epornerGetRelatedController.ts
Normal file
@@ -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;
|
||||
}
|
||||
70
src/scraper/eporner/epornerSearchController.ts
Normal file
70
src/scraper/eporner/epornerSearchController.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 <meta property="video:duration" content="
|
||||
this.duration = $("meta[property='video:duration']").attr("content") || "0";
|
||||
this.duration =
|
||||
$("meta[property='video:duration']").attr("content") || "0";
|
||||
this.views = $("div.views > 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
};
|
||||
@@ -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`);
|
||||
|
||||
Reference in New Issue
Block a user