ci: tdd
This commit is contained in:
Indrawan I
2023-04-18 11:36:31 +07:00
committed by GitHub
60 changed files with 3075 additions and 0 deletions

2
.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
npm-debug.log

14
.env.schema Normal file
View File

@@ -0,0 +1,14 @@
# railway, fly.dev, heroku, vercel or any free service
RAILWAY = sinkaroid
# default port
PORT = 3000
# backend storage, default is redis, if not set it will consume memory storage
REDIS_URL = redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.cloud.redislabs.com:1337
# ttl expire cache (in X hour)
EXPIRE_CACHE = 1
# you must identify your origin, if not set it will use default
USER_AGENT = "lustpress/1.6.0 Node.js/16.9.1"

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
build/

41
.eslintrc.json Normal file
View File

@@ -0,0 +1,41 @@
{
"env": {
"es2021": true,
"node": true,
"commonjs": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"linebreak-style": 0,
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
],
"no-empty": "error",
"no-func-assign": "error",
"no-case-declarations": "off",
"no-unreachable": "error",
"no-eval": "error",
"no-global-assign": "error",
"@typescript-eslint/no-explicit-any": ["off"],
"indent": [
"error",
2
]
}
}

30
.github/workflows/pornhub.yml vendored Normal file
View File

@@ -0,0 +1,30 @@
name: Pornhub test
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Pornhub test
run: npm run test:pornhub

29
.github/workflows/status.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Check status
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install
- name: Build
run: npm run build
- name: Check status code
run: npm run test

22
CLOSING_REMARKS.md Normal file
View File

@@ -0,0 +1,22 @@
# Closing remarks
I hope you have found this project useful. All the major credit really goes to the all the doujin sites, which
allows this services to operate.
- [PornHub](https://pornhub.com)
- [Xnxx](https://xnxx.com)
- [RedTube](https://redtube.com)
- [Xvideos](https://xvideos.com)
- [Xhamster](https://xhamster.com)
- [YouPorn](https://youporn.com)
- [JavHD](https://javhd.com)
Core dependencies:
- [express](https://github.com/expressjs/express)
- [cheerio](https://cheerio.js.org/)
- [keyv](https://github.com/jaredwray/keyv)
# Alternative-links
Just in case if https://lust.scathach.id down, here some alternative deployment
- TBA

76
CODE_OF_CONDUCT.md Normal file
View File

@@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at anakmancasan@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

10
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,10 @@
# Contributing
## Making major changes
Please first discuss the change you wish to make via issue,
email, or any other method with the owners before making a change.
Please note we have a code of conduct, follow it in all your interactions with the project.
## Making minor changes
@sinkaroid are not the best coder, so there are sure some problematic coding decision, every slight of changes will helps however. I always happy to receive Pull requests to improve things.

9
Dockerfile Normal file
View File

@@ -0,0 +1,9 @@
FROM node:latest
WORKDIR /srv/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "build/src/index.js"]

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Lustpress Security
## Reporting vulnerabilities
To report sensitive vulnerabilities, alert the author by email at anakmancasan@gmail.com.

69
package.json Normal file
View File

@@ -0,0 +1,69 @@
{
"name": "lustpress",
"version": "1.6.0-alpha",
"description": "RESTful and experimental API for PornHub and other porn sites, which official is lack even isn't exist.",
"main": "build/src/index.js",
"scripts": {
"build": "rimraf build && tsc",
"start": "node build/src/index.js",
"test": "ts-node test/test.ts",
"test:mock": "ts-node test/mock.ts",
"start:prod": "npm run build && node build/src/index.js",
"start:dev": "ts-node-dev src/index.ts",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --fix",
"build:freshdoc": "rimraf docs && rimraf template && rimraf theme.zip",
"build:template": " npm run build:freshdoc && curl https://codeload.github.com/ScathachGrip/apidocjs-theme/zip/refs/tags/v11 -o theme.zip && unzip theme.zip && mv apidocjs-theme-11 template",
"build:apidoc": "npm run build:template && apidoc -i src -o ./docs -t ./template/template-scarlet",
"test:pornhub": "start-server-and-test 3000 \"curl -v http://localhost:3000/pornhub/random | jq '.'\"",
"test:xnxx": "start-server-and-test 3000 \"curl -v http://localhost:3000/xnxx/random | jq '.'\"",
"test:redtube": "start-server-and-test 3000 \"curl -v http://localhost:3000/redtube/random | jq '.'\"",
"test:xvideos": "start-server-and-test 3000 \"curl -v http://localhost:3000/xvideos/random | jq '.'\"",
"test:xhamster": "start-server-and-test 3000 \"curl -v http://localhost:3000/xhamster/random | jq '.'\"",
"test:youporn": "start-server-and-test 3000 \"curl -v http://localhost:3000/youporn/random | jq '.'\""
},
"apidoc": {
"title": "Lustpress API Documentation",
"url": "https://lust.scathach.id",
"sampleUrl": "https://lust.scathach.id",
"name": "Lustpress"
},
"keywords": [],
"author": "sinkaroid",
"repository": {
"type": "git",
"url": "git+https://github.com/sinkaroid/lustpress.git"
},
"license": "MIT",
"dependencies": {
"@keyv/redis": "^2.5.7",
"cheerio": "^1.0.0-rc.12",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"express-slow-down": "^1.6.0",
"keyv": "^4.5.2",
"phin": "^3.7.0",
"pino": "^8.11.0",
"pino-pretty": "^10.0.0"
},
"devDependencies": {
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/express-slow-down": "^1.3.2",
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"apidoc": "0.29.0",
"eslint": "^8.38.0",
"rimraf": "^5.0.0",
"start-server-and-test": "^2.0.0",
"ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0",
"typescript": "5.0.4"
},
"engines": {
"node": ">=14"
}
}

153
src/LustPress.ts Normal file
View File

@@ -0,0 +1,153 @@
import p, { IResponse } from "phin";
import Keyv from "keyv";
import pkg from "../package.json";
const keyv = new Keyv(process.env.REDIS_URL);
keyv.on("error", err => console.log("Connection Error", err));
const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE);
class LustPress {
url: string;
useragent: string;
constructor() {
this.url = "";
this.useragent = `${pkg.name}/${pkg.version} Node.js/16.9.1`;
}
/**
* Fetch body from url and check if it's cached
* @param url url to fetch
* @returns Buffer
*/
async fetchBody(url: string): Promise<Buffer> {
const cached = await keyv.get(url);
if (cached) {
console.log("Fetching from cache");
return cached;
} else if (url.includes("/random")) {
console.log("Random should not be cached");
const res = await p({
url: url,
"headers": {
"User-Agent": process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/16.9.1`,
},
followRedirects: true
});
return res.body;
} else {
console.log("Fetching from source");
const res = await p({
url: url,
"headers": {
"User-Agent": process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/16.9.1`,
},
followRedirects: true
});
await keyv.set(url, res.body, ttl);
return res.body;
}
}
/**
* remove html tag and bunch of space
* @param str string to remove html tag
* @returns string
*/
removeHtmlTag(str: string): string {
str = str.replace(/(\r\n|\n|\r)/gm, "");
str = str.replace(/\s+/g, "");
return str;
}
/**
* remove html tag without space
* @param str string to remove html tag
* @returns string
*/
removeHtmlTagWithoutSpace(str: string): string {
str = str.replace(/(\r\n|\n|\r|\t)/gm, "");
str = str.replace(/\\/g, "");
return str.trim();
}
/**
* remove all single quote on array
* @param arr array to remove single quote
* @returns string[]
*/
removeAllSingleQuoteOnArray(arr: string[]): string[] {
return arr.map((item) => item.replace(/'/g, ""));
}
/**
* time ago converter
* @param input date to convert
* @returns string
*/
timeAgo(input: Date) {
const date = new Date(input);
const formatter: any = new Intl.RelativeTimeFormat("en");
const ranges: { [key: string]: number } = {
years: 3600 * 24 * 365,
months: 3600 * 24 * 30,
weeks: 3600 * 24 * 7,
days: 3600 * 24,
hours: 3600,
minutes: 60,
seconds: 1
};
const secondsElapsed = (date.getTime() - Date.now()) / 1000;
for (const key in ranges) {
if (ranges[key] < Math.abs(secondsElapsed)) {
const delta = secondsElapsed / ranges[key];
return formatter.format(Math.round(delta), key);
}
}
}
/**
* convert seconds to minute
* @param seconds seconds to convert
* @returns string
*/
secondToMinute(seconds: number): string {
const minutes = Math.floor(seconds / 60);
const second = seconds % 60;
return `${minutes}min, ${second}sec`;
}
/**
* get current process memory usage
* @returns object
*/
currentProccess() {
const arr = [1, 2, 3, 4, 5, 6, 9, 7, 8, 9, 10];
arr.reverse();
const rss = process.memoryUsage().rss / 1024 / 1024;
const heap = process.memoryUsage().heapUsed / 1024 / 1024;
const heaptotal = process.memoryUsage().heapTotal / 1024 / 1024;
return {
rss: `${Math.round(rss * 100) / 100} MB`,
heap: `${Math.round(heap * 100) / 100}/${Math.round(heaptotal * 100) / 100} MB`
};
}
/**
* fetch this server location
* @returns <Promise<string>>
*/
async getServer(): Promise<string> {
const raw = await p({
"url": "http://ip-api.com/json",
"parse": "json"
}) as IResponse;
const data = raw.body as unknown as { country: string, regionName: string };
return `${data.country}, ${data.regionName}`;
}
}
export default LustPress;

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/pornhub/pornhubGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function getPornhub(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /pornhub/get?id=:id Get Pornhub
* @apiName Get pornhub
* @apiGroup pornhub
* @apiDescription Get a pornhub 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/pornhub/get?id=ph63c4e1dc48fe7
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/pornhub/get?id=ph63c4e1dc48fe7")
* .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/pornhub/get?id=ph63c4e1dc48fe7") as resp:
* print(await resp.json())
*/
const url = `${c.PORNHUB}/view_video.php?viewkey=${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/pornhub/pornhubSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function relatedPornhub(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /pornhub/get?id=:id Get Pornhub related videos
* @apiName Get pornhub related videos
* @apiGroup pornhub
* @apiDescription Get a related pornhub videos 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/pornhub/get?id=ph63c4e1dc48fe7
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/pornhub/get?id=ph63c4e1dc48fe7")
* .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/pornhub/get?id=ph63c4e1dc48fe7") as resp:
* print(await resp.json())
*/
const url = `${c.PORNHUB}/view_video.php?viewkey=${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));
}
}

View File

@@ -0,0 +1,50 @@
import { Request, Response } from "express";
import { scrapeContent } from "../../scraper/pornhub/pornhubGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
export async function randomPornhub(req: Request, res: Response) {
try {
/**
* @api {get} /pornhub/random Random pornhub video
* @apiName Random pornhub
* @apiGroup pornhub
* @apiDescription Gets random pornhub video
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
*
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/pornhub/random
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/pornhub/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/pornhub/random") as resp:
* print(await resp.json())
*
*/
const url = `${c.PORNHUB}/video/random`;
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));
}
}

View File

@@ -0,0 +1,66 @@
import { scrapeContent } from "../../scraper/pornhub/pornhubSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError, spacer } from "../../utils/modifier";
import { Request, Response } from "express";
const sorting = ["mr", "mv", "tr", "lg"];
export async function searchPornhub(req: Request, res: Response) {
try {
/**
* @api {get} /pornhub/search Search pornhub videos
* @apiName Search pornhub
* @apiGroup pornhub
* @apiDescription Search pornhub videos
* @apiParam {String} key Keyword to search
* @apiParam {Number} [page=1] Page number
* @apiParam {String} [sort=mr] Sort by
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
*
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/pornhub/search?key=milf
* curl -i https://lust.scathach.id/pornhub/search?key=milf&page=2&sort=mr
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/pornhub/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/pornhub/search?key=milf") as resp:
* print(await resp.json())
*/
const key = req.query.key as string;
const page = req.query.page || 1;
const sort = req.query.sort as string;
if (!key) throw Error("Parameter key is required");
if (isNaN(Number(page))) throw Error("Parameter page must be a number");
let url;
if (!sort) url = `${c.PORNHUB}/video/search?search=${spacer(key)}`;
else if (!sorting.includes(sort)) url = `${c.PORNHUB}/video/search?search=${spacer(key)}&page=${page}`;
else url = `${c.PORNHUB}/video/search?search=${spacer(key)}&o=${sort}&page=${page}`;
console.log(url);
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));
}
}

View File

@@ -0,0 +1,56 @@
import { scrapeContent } from "../../scraper/redtube/redtubeGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function getRedtube(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
if (isNaN(Number(id))) throw Error("Parameter id must be a number");
/**
* @api {get} /redtube/get?id=:id Get Redtube
* @apiName Get redtube
* @apiGroup redtube
* @apiDescription Get a redtube 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/redtube/get?id=42763661
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/redtube/get?id=42763661")
* .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/redtube/get?id=42763661") as resp:
* print(await resp.json())
*/
const url = `${c.REDTUBE}/${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));
}
}

View File

@@ -0,0 +1,54 @@
import { scrapeContent } from "../../scraper/redtube/redtubeSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function relatedRedtube(req: Request, res: Response) {
try {
/**
* @api {get} /redtube/get?id=:id Get redtube related videos
* @apiName Get redtube related videos
* @apiGroup redtube
* @apiDescription Get a related redtube videos based on 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/redtube/get?id=41698751
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/redtube/get?id=41698751")
* .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/redtube/get?id=41698751") as resp:
* print(await resp.json())
*/
const id = req.query.id as string;
if (!id) throw Error("Parameter key is required");
if (isNaN(Number(id))) throw Error("Parameter id must be a number");
const url = `${c.REDTUBE}/${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));
}
}

View File

@@ -0,0 +1,65 @@
import { scrapeContent } from "../../scraper/redtube/redtubeGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
import { load } from "cheerio";
import LustPress from "../../LustPress";
const lust = new LustPress();
export async function randomRedtube(req: Request, res: Response) {
try {
/**
* @api {get} /redtube/random Get random redtube
* @apiName Get random redtube
* @apiGroup redtube
* @apiDescription Get a random redtube 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/redtube/random
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/redtube/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/redtube/random") as resp:
* print(await resp.json())
*/
const resolve = await lust.fetchBody(c.REDTUBE);
const $ = load(resolve);
const search = $("a.video_link")
.map((i, el) => {
return $(el).attr("href");
}).get();
const random = Math.floor(Math.random() * search.length);
const url = c.REDTUBE + search[random];
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));
}
}

View File

@@ -0,0 +1,58 @@
import { scrapeContent } from "../../scraper/redtube/redtubeSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError, spacer } from "../../utils/modifier";
import { Request, Response } from "express";
export async function searchRedtube(req: Request, res: Response) {
try {
/**
* @api {get} /redtube/search Search redtube videos
* @apiName Search redtube
* @apiGroup redtube
* @apiDescription Search redtube 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/redtube/search?key=milf
* curl -i https://lust.scathach.id/redtube/search?key=milf&page=2
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/redtube/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/redtube/search?key=milf") as resp:
* print(await resp.json())
*/
const key = req.query.key as string;
const page = req.query.page || 1;
if (!key) throw Error("Parameter key is required");
if (isNaN(Number(page))) throw Error("Parameter page must be a number");
const url = `${c.REDTUBE}/?search=${spacer(key)}&page=${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/xhamster/xhamsterGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function getXhamster(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /xhamster/get?id=:id Get xhamster
* @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:
* async with session.get("https://lust.scathach.id/xhamster/get?id=videos/horny-makima-tests-new-toy-and-cums-intensely-xhAa5wx") as resp:
* print(await resp.json())
*/
const url = `${c.XHAMSTER}/${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/xhamster/xhamsterSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function relatedXhamster(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /xhamster/get?id=:id Get related xhamster
* @apiName Get related xhamster
* @apiGroup xhamster
* @apiDescription Get a xhamster video based on related 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/related?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/related?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:
* async with session.get("https://lust.scathach.id/xhamster/related?id=videos/horny-makima-tests-new-toy-and-cums-intensely-xhAa5wx") as resp:
* print(await resp.json())
*/
const url = `${c.XHAMSTER}/${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));
}
}

View File

@@ -0,0 +1,63 @@
import { scrapeContent } from "../../scraper/xhamster/xhamsterGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
import { load } from "cheerio";
import LustPress from "../../LustPress";
const lust = new LustPress();
export async function randomXhamster(req: Request, res: Response) {
try {
/**
* @api {get} /xhamster/random Get random xhamster
* @apiName Get random xhamster
* @apiGroup xhamster
* @apiDescription Get a random xhamster video
*
* @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);
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));
}
}

View File

@@ -0,0 +1,58 @@
import { scrapeContent } from "../../scraper/xhamster/xhamsterSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError, spacer } from "../../utils/modifier";
import { Request, Response } from "express";
export async function searchXhamster(req: Request, res: Response) {
try {
/**
* @api {get} /xhamster/search Search xhamster videos
* @apiName Search xhamster
* @apiGroup xhamster
* @apiDescription Search xhamster 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/xhamster/search?key=milf
* curl -i https://lust.scathach.id/xhamster/search?key=milf&page=2
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xhamster/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/xhamster/search?key=milf") as resp:
* print(await resp.json())
*/
const key = req.query.key as string;
const page = req.query.page || 1;
if (!key) throw Error("Parameter key is required");
if (isNaN(Number(page))) throw Error("Parameter page must be a number");
const url = `${c.XHAMSTER}/search/${spacer(key)}?page=${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/xnxx/xnxxGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function getXnxx(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /xnxx/get?id=:id Get xnxx
* @apiName Get xnxx
* @apiGroup xnxx
* @apiDescription Get a xnxx 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/xnxx/get?id=video-17vah71a/makima_y_denji
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xnxx/get?id=video-17vah71a/makima_y_denji")
* .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/xnxx/get?id=video-17vah71a/makima_y_denji") as resp:
* print(await resp.json())
*/
const url = `${c.XNXX}/${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/xnxx/xnxxGetRelatedController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function relatedXnxx(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /xnxx/get?id=:id Get related xnxx
* @apiName Get related xnxx
* @apiGroup xnxx
* @apiDescription Get a xnxx video based on related 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/xnxx/related?id=video-17vah71a/makima_y_denji
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xnxx/related?id=video-17vah71a/makima_y_denji")
* .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/xnxx/related?id=video-17vah71a/makima_y_denji") as resp:
* print(await resp.json())
*/
const url = `${c.XNXX}/${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));
}
}

View File

@@ -0,0 +1,63 @@
import { scrapeContent } from "../../scraper/xnxx/xnxxGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
import { load } from "cheerio";
import LustPress from "../../LustPress";
const lust = new LustPress();
export async function randomXnxx(req: Request, res: Response) {
try {
/**
* @api {get} /xnxx/random Get random xnxx
* @apiName Get random xnxx
* @apiGroup xnxx
* @apiDescription Get a random xnxx video
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
*
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/xnxx/random
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xnxx/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/xnxx/random") as resp:
* print(await resp.json())
*/
const resolve = await lust.fetchBody("https://www.xnxx.com/search/random/random");
const $ = load(resolve);
const search = $("div.mozaique > div")
.map((i, el) => {
return $(el).find("a").attr("href");
}).get();
const random = Math.floor(Math.random() * search.length);
const url = c.XNXX + search[random];
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));
}
}

View File

@@ -0,0 +1,58 @@
import { scrapeContent } from "../../scraper/xnxx/xnxxSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError, spacer } from "../../utils/modifier";
import { Request, Response } from "express";
export async function searchXnxx(req: Request, res: Response) {
try {
/**
* @api {get} /xnxx/search Search xnxx videos
* @apiName Search xnxx
* @apiGroup xnxx
* @apiDescription Search xnxx videos
* @apiParam {String} key Keyword to search
* @apiParam {Number} [page=0] 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/xnxx/search?key=milf
* curl -i https://lust.scathach.id/xnxx/search?key=milf&page=2
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xnxx/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/xnxx/search?key=milf") as resp:
* print(await resp.json())
*/
const key = req.query.key as string;
const page = req.query.page || 0;
if (!key) throw Error("Parameter key is required");
if (isNaN(Number(page))) throw Error("Parameter page must be a number");
const url = `${c.XNXX}/search/${spacer(key)}/${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/xvideos/xvideosGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function getXvideos(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /xvideos/get?id=:id Get xvideos
* @apiName Get xvideos
* @apiGroup xvideos
* @apiDescription Get a xvideos 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/xvideos/get?id=video73564387/cute_hentai_maid_with_pink_hair_fucking_uncensored_
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xvideos/get?id=video73564387/cute_hentai_maid_with_pink_hair_fucking_uncensored_")
* .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/xvideos/get?id=video73564387/cute_hentai_maid_with_pink_hair_fucking_uncensored_") as resp:
* print(await resp.json())
*/
const url = `${c.XVIDEOS}/${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/xvideos/xvideosGetRelatedController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function relatedXvideos(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /xvideos/get?id=:id Get related xvideos
* @apiName Get related xvideos
* @apiGroup xvideos
* @apiDescription Get a xvideos video based on related 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/xvideos/related?id=video73564387/cute_hentai_maid_with_pink_hair_fucking_uncensored_
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xvideos/related?id=video73564387/cute_hentai_maid_with_pink_hair_fucking_uncensored_")
* .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/xvideos/related?id=video73564387/cute_hentai_maid_with_pink_hair_fucking_uncensored_") as resp:
* print(await resp.json())
*/
const url = `${c.XVIDEOS}/${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));
}
}

View File

@@ -0,0 +1,63 @@
import { scrapeContent } from "../../scraper/xvideos/xvideosGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
import { load } from "cheerio";
import LustPress from "../../LustPress";
const lust = new LustPress();
export async function randomXvideos(req: Request, res: Response) {
try {
/**
* @api {get} /xvideos/random Get random xvideos
* @apiName Get random xvideos
* @apiGroup xvideos
* @apiDescription Get a random xvideos video
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
*
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/xvideos/random
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xvideos/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/xvideos/random") as resp:
* print(await resp.json())
*/
const resolve = await lust.fetchBody(c.XVIDEOS);
const $ = load(resolve);
const search = $("div.thumb-under")
.find("a")
.map((i, el) => $(el).attr("href"))
.get();
const filtered = search.filter((el) => el.includes("/video"));
const filtered_ = filtered.filter((el) => !el.includes("THUMBNUM"));
const random = Math.floor(Math.random() * filtered_.length);
const url = c.XVIDEOS + filtered[random];
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));
}
}

View File

@@ -0,0 +1,58 @@
import { scrapeContent } from "../../scraper/xvideos/xvideosSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError, spacer } from "../../utils/modifier";
import { Request, Response } from "express";
export async function searchXvideos(req: Request, res: Response) {
try {
/**
* @api {get} /xvideos/search Search xvideos videos
* @apiName Search xvideos
* @apiGroup xvideos
* @apiDescription Search xvideos videos
* @apiParam {String} key Keyword to search
* @apiParam {Number} [page=0] 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/xvideos/search?key=milf
* curl -i https://lust.scathach.id/xvideos/search?key=milf&page=2
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/xvideos/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/xvideos/search?key=milf") as resp:
* print(await resp.json())
*/
const key = req.query.key as string;
const page = req.query.page || 0;
if (!key) throw Error("Parameter key is required");
if (isNaN(Number(page))) throw Error("Parameter page must be a number");
const url = `${c.XVIDEOS}/?k=${spacer(key)}&p=${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/youporn/youpornGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function getYouporn(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /youporn/get?id=:id Get youporn
* @apiName Get youporn
* @apiGroup youporn
* @apiDescription Get a youporn 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/youporn/get?id=16621192/chainsaw-man-fuck-makima-3d-porn-60-fps
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/youporn/get?id=16621192/chainsaw-man-fuck-makima-3d-porn-60-fps")
* .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/youporn/get?id=16621192/chainsaw-man-fuck-makima-3d-porn-60-fps") as resp:
* print(await resp.json())
*/
const url = `${c.YOUPORN}/watch/${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));
}
}

View File

@@ -0,0 +1,55 @@
import { scrapeContent } from "../../scraper/youporn/youpornSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
export async function relatedYouporn(req: Request, res: Response) {
try {
const id = req.query.id as string;
if (!id) throw Error("Parameter id is required");
/**
* @api {get} /youporn/get?id=:id Get related youporn
* @apiName Get related youporn
* @apiGroup youporn
* @apiDescription Get a youporn video based on related 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/youporn/related?id=16621192/chainsaw-man-fuck-makima-3d-porn-60-fps
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/youporn/related?id=16621192/chainsaw-man-fuck-makima-3d-porn-60-fps")
* .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/youporn/related?id=16621192/chainsaw-man-fuck-makima-3d-porn-60-fps") as resp:
* print(await resp.json())
*/
const url = `${c.YOUPORN}/watch/${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));
}
}

View File

@@ -0,0 +1,62 @@
import { scrapeContent } from "../../scraper/youporn/youpornGetController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError } from "../../utils/modifier";
import { Request, Response } from "express";
import { load } from "cheerio";
import LustPress from "../../LustPress";
const lust = new LustPress();
export async function randomYouporn(req: Request, res: Response) {
try {
/**
* @api {get} /youporn/random Get random youporn
* @apiName Get random youporn
* @apiGroup youporn
* @apiDescription Get a random youporn video
*
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* HTTP/1.1 400 Bad Request
*
* @apiExample {curl} curl
* curl -i https://lust.scathach.id/youporn/random
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/youporn/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/youporn/random") as resp:
* print(await resp.json())
*/
const resolve = await lust.fetchBody(`${c.YOUPORN}`);
const $ = load(resolve);
const search = $("a[href^='/watch/']")
.map((i, el) => {
return $(el).attr("href");
}).get();
const random = Math.floor(Math.random() * search.length);
const url = c.YOUPORN + search[random];
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));
}
}

View File

@@ -0,0 +1,58 @@
import { scrapeContent } from "../../scraper/youporn/youpornSearchController";
import c from "../../utils/options";
import { logger } from "../../utils/logger";
import { maybeError, spacer } from "../../utils/modifier";
import { Request, Response } from "express";
export async function searchYouporn(req: Request, res: Response) {
try {
/**
* @api {get} /youporn/search Search youporn videos
* @apiName Search youporn
* @apiGroup youporn
* @apiDescription Search youporn 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/youporn/search?key=milf
* curl -i https://lust.scathach.id/youporn/search?key=milf&page=2
*
* @apiExample {js} JS/TS
* import axios from "axios"
*
* axios.get("https://lust.scathach.id/youporn/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/youporn/search?key=milf") as resp:
* print(await resp.json())
*/
const key = req.query.key as string;
const page = req.query.page || 1;
if (!key) throw Error("Parameter key is required");
if (isNaN(Number(page))) throw Error("Parameter page must be a number");
const url = `${c.YOUPORN}/search/?query=${spacer(key)}&page=${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));
}
}

52
src/index.ts Normal file
View File

@@ -0,0 +1,52 @@
import "dotenv/config";
import LustPress from "./LustPress";
import express from "express";
import { Request, Response, NextFunction } from "express";
import scrapeRoutes from "./router/endpoint";
import { slow, limiter } from "./utils/limit-options";
import { logger } from "./utils/logger";
import * as pkg from "../package.json";
const lust = new LustPress();
const app = express();
app.get("/", slow, limiter, async (req, res) => {
res.send({
success: true,
playground: "https://sinkaroid.github.io/lustpress",
endpoint: "https://github.com/sinkaroid/lustpress/blob/master/README.md#routing",
date: new Date().toLocaleString(),
rss: lust.currentProccess().rss,
heap: lust.currentProccess().heap,
server: await lust.getServer(),
version: `${pkg.version}`,
});
logger.info({
path: req.path,
method: req.method,
ip: req.ip,
useragent: req.get("User-Agent")
});
});
app.use(scrapeRoutes());
app.use((req: Request, res: Response, next: NextFunction) => {
res.status(404);
next(Error(`The page not found in path ${req.url} and method ${req.method}`));
logger.error({
path: req.url,
method: req.method,
ip: req.ip,
useragent: req.get("User-Agent")
});
});
app.use((error: any, res: Response) => {
res.status(500).json({
message: error.message,
stack: error.stack
});
});
app.listen(process.env.PORT || 3000, () => console.log(`${pkg.name} is running on port ${process.env.PORT || 3000}`));

29
src/interfaces.ts Normal file
View File

@@ -0,0 +1,29 @@
export interface IVideoData {
success: boolean;
data: {
title: string;
id: string;
image: string;
duration: string;
views: string;
rating: string;
uploaded: string;
upvoted: string | null;
downvoted: string | null;
models: string[];
tags: string[];
};
source: string;
assets: string[];
}
export interface ISearchVideoData {
success: boolean;
data: string[];
source: string;
}
export interface MaybeError {
message: string;
}

72
src/router/endpoint.ts Normal file
View File

@@ -0,0 +1,72 @@
import cors from "cors";
import { Router } from "express";
import { slow, limiter } from "../utils/limit-options";
// PornHub
import { getPornhub } from "../controller/pornhub/pornhubGet";
import { searchPornhub } from "../controller/pornhub/pornhubSearch";
import { randomPornhub } from "../controller/pornhub/pornhubRandom";
import { relatedPornhub } from "../controller/pornhub/pornhubGetRelated";
// XNXX
import { getXnxx } from "../controller/xnxx/xnxxGet";
import { searchXnxx } from "../controller/xnxx/xnxxSearch";
import { relatedXnxx } from "../controller/xnxx/xnxxGetRelated";
import { randomXnxx } from "../controller/xnxx/xnxxRandom";
// RedTube
import { getRedtube } from "../controller/redtube/redtubeGet";
import { searchRedtube } from "../controller/redtube/redtubeSearch";
import { relatedRedtube } from "../controller/redtube/redtubeGetRelated";
import { randomRedtube } from "../controller/redtube/redtubeRandom";
// Xvideos
import { getXvideos } from "../controller/xvideos/xvideosGet";
import { searchXvideos } from "../controller/xvideos/xvideosSearch";
import { randomXvideos } from "../controller/xvideos/xvideosRandom";
import { relatedXvideos } from "../controller/xvideos/xvideosGetRelated";
// Xhamster
import { getXhamster } from "../controller/xhamster/xhamsterGet";
import { searchXhamster } from "../controller/xhamster/xhamsterSearch";
import { randomXhamster } from "../controller/xhamster/xhamsterRandom";
import { relatedXhamster } from "../controller/xhamster/xhamsterGetRelated";
// YouPorn
import { getYouporn } from "../controller/youporn/youpornGet";
import { searchYouporn } from "../controller/youporn/youpornSearch";
import { relatedYouporn } from "../controller/youporn/youpornGetRelated";
import { randomYouporn } from "../controller/youporn/youpornRandom";
function scrapeRoutes() {
const router = Router();
router.get("/pornhub/get", cors(), slow, limiter, getPornhub);
router.get("/pornhub/search", cors(), slow, limiter, searchPornhub);
router.get("/pornhub/random", cors(), slow, limiter, randomPornhub);
router.get("/pornhub/related", cors(), slow, limiter, relatedPornhub);
router.get("/xnxx/get", cors(), slow, limiter, getXnxx);
router.get("/xnxx/search", cors(), slow, limiter, searchXnxx);
router.get("/xnxx/related", cors(), slow, limiter, relatedXnxx);
router.get("/xnxx/random", cors(), slow, limiter, randomXnxx);
router.get("/redtube/get", cors(), slow, limiter, getRedtube);
router.get("/redtube/search", cors(), slow, limiter, searchRedtube);
router.get("/redtube/related", cors(), slow, limiter, relatedRedtube);
router.get("/redtube/random", cors(), slow, limiter, randomRedtube);
router.get("/xvideos/get", cors(), slow, limiter, getXvideos);
router.get("/xvideos/search", cors(), slow, limiter, searchXvideos);
router.get("/xvideos/random", cors(), slow, limiter, randomXvideos);
router.get("/xvideos/related", cors(), slow, limiter, relatedXvideos);
router.get("/xhamster/get", cors(), slow, limiter, getXhamster);
router.get("/xhamster/search", cors(), slow, limiter, searchXhamster);
router.get("/xhamster/random", cors(), slow, limiter, randomXhamster);
router.get("/xhamster/related", cors(), slow, limiter, relatedXhamster);
router.get("/youporn/get", cors(), slow, limiter, getYouporn);
router.get("/youporn/search", cors(), slow, limiter, searchYouporn);
router.get("/youporn/related", cors(), slow, limiter, relatedYouporn);
router.get("/youporn/random", cors(), slow, limiter, randomYouporn);
return router;
}
export default scrapeRoutes;

View File

@@ -0,0 +1,78 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { IVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
class PornHub {
link: string;
id: string;
title: string;
image: string;
duration: string;
views: string;
rating: string;
videoInfo: string;
upVote: string;
downVote: string;
video: string;
tags: string[];
models: string[];
constructor() {
this.link = $("link[rel='canonical']").attr("href") || "None";
this.id = this.link.split("=")[1] || "None";
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.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.tags = $("div.video-info-row")
.find("a")
.map((i, el) => {
return $(el).text();
}).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();
}
}
const ph = new PornHub();
const data: IVideoData = {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(ph.title),
id: ph.id,
image: ph.image,
duration: lust.secondToMinute(Number(ph.duration)),
views: ph.views,
rating: ph.rating,
uploaded: ph.videoInfo,
upvoted: ph.upVote,
downvoted: ph.downVote,
models: ph.models,
tags: ph.tags.filter((el) => el !== "Suggest" && el !== " Suggest")
},
source: ph.link,
assets: [ph.video, ph.image]
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,59 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
class PornhubSearch {
search: object[];
data: object;
constructor() {
this.search = $("div.wrap")
.map((i, el) => {
const link = $(el).find("a").attr("href");
const id = link?.split("=")[1];
const title = $(el).find("a").attr("title");
const image = $(el).find("img").attr("src");
const duration = $(el).find("var.duration").text();
const views = $(el).find("div.videoDetailsBlock").find("span.views").text();
return {
link: `${c.PORNHUB}${link}`,
id: id,
title: title,
image: image,
duration: duration,
views: views,
video: `${c.PORNHUB}/embed/${id}`,
};
}).get();
this.data = this.search.filter((el: any) => {
return el.link.includes("javascript:void(0)") === false && el.image?.startsWith("data:image") === false;
});
}
}
const ph = new PornhubSearch();
if (ph.search.length === 0) throw Error("No result found");
const data = ph.data as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,79 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { IVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
class RedTube {
link: string;
id: string;
title: string;
image: string;
duration: string;
views: string;
rating: string;
publish: string;
upVote: string;
downVote: null;
video: string;
tags: string[];
models: string[];
constructor() {
this.link = $("link[rel='canonical']").attr("href") || "None";
this.id = this.link.split("/")[3] || "None";
this.title = $("meta[property='og:title']").attr("content") || "None";
this.image = $("meta[property='og:image']").attr("content") || "None";
this.duration = $("meta[property='og:video:duration']").attr("content") || "0";
this.views = $("span.video_view_count").text() || "None";
this.rating = $("div.rating_percent.js_rating_percent").attr("data-percent") + "%" || "None";
this.publish = $("span.video-infobox-date-added").text().replace("Published on ", "") || "None";
this.upVote = this.rating;
this.downVote = null;
this.video = $("meta[name='twitter:player']").attr("content") || "None";
this.tags = $("a.item.video_carousel_item.video_carousel_category, a.item.video_carousel_item.video_carousel_tag")
.map((i, el) => {
return $(el).text();
}).get();
this.tags = this.tags.map((el) => lust.removeHtmlTagWithoutSpace(el));
this.models = $("div.pornstar-name.pornstarPopupWrapper")
.find("a")
.map((i, el) => {
return $(el).text();
}
).get();
this.models = this.models.map((el) => lust.removeHtmlTagWithoutSpace(el));
this.models = this.models.filter((el) => !el.includes("Subscribe") && !el.includes("Rank"))
.filter((el, i, arr) => arr.indexOf(el) === i);
}
}
const red = new RedTube();
const data: IVideoData = {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(red.title),
id: red.id,
image: red.image,
duration: lust.secondToMinute(Number(red.duration)),
views: red.views,
rating: red.rating,
uploaded: red.publish,
upvoted: red.upVote,
downvoted: red.downVote,
models: red.models,
tags: red.tags
},
source: red.link,
assets: [red.video, red.image]
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,72 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
class RedTubeSearch {
views: string[];
search: object[];
data: object;
constructor() {
this.views = $("span.video_count")
.map((i, el) => {
const views = $(el).text();
return views;
}).get();
this.search = $("a.video_link")
.map((i, el) => {
const link = $(el).attr("href");
const id = link?.split("/")[1];
const title = $(el).find("img").attr("alt");
const image = $(el).find("img").attr("data-src");
const duration = $(el).find("span.duration").text().split(" ").map((el: string) => {
return el.replace(/[^0-9:]/g, "");
}).filter((el: string) => {
return el.includes(":");
}).join(" ");
return {
link: `${c.REDTUBE}${link}`,
id: id,
title: title,
image: image,
duration: duration,
views: this.views[i],
video: `https://embed.redtube.com/?id=${id}`,
};
}).get();
this.data = this.search.filter((el: any) => {
return el.link.includes("javascript:void(0)") === false && el.image?.startsWith("data:image") === false;
});
}
}
const red = new RedTubeSearch();
if (red.search.length === 0) throw Error("No result found");
const data = red.data as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,83 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { IVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
class Xhamster {
link: string;
id: string;
title: string;
image: string;
duration: any;
views: string;
rating: string;
publish: string;
upVote: string;
downVote: string;
video: string;
tags: string[];
models: string[];
constructor() {
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 = $("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/", ""));
}
}
const xh = new Xhamster();
const data: IVideoData = {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(xh.title),
id: xh.id,
image: xh.image,
duration: lust.secondToMinute(Number(xh.duration)),
views: xh.views,
rating: xh.rating,
uploaded: xh.publish,
upvoted: xh.upVote,
downvoted: xh.downVote,
models: xh.models,
tags: xh.tags
},
source: xh.link,
assets: [xh.video, xh.image]
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,56 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
class XhamsterSearch {
search: any;
constructor() {
const views = $("div.video-thumb-views")
.map((i, el) => {
const views = $(el).text();
return views;
}).get();
const duration = $("span[data-role='video-duration']")
.map((i, el) => {
const duration = $(el).text();
return duration;
}).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],
title: $(el).find("img").attr("alt"),
image: $(el).find("img").attr("src"),
duration: duration[i],
views: views[i],
video: $(el).attr("data-previewvideo"),
};
}).get();
}
}
const xh = new XhamsterSearch();
if (xh.search.length === 0) throw Error("No result found");
const data = xh.search as unknown as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,102 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { IVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
console.log(url);
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
class Xnxx {
link: string;
id: string;
title: string;
image: string;
duration: string;
views: string;
uploaded: string;
action: string[];
upVote: string;
downVote: string;
favVote: string;
tags: string[];
models: string[];
thumbnail: string;
bigimg: string;
video: string;
constructor() {
const thumb = $("script")
.map((i, el) => {
return $(el).text();
}).get()
.filter((el) => el.includes("html5player.setThumbSlideBig"))[0] || "None";
this.thumbnail = thumb.match(/html5player.setThumbSlideBig\((.*?)\)/)?.[1] || "None";
this.bigimg = thumb.match(/html5player.setThumbUrl169\((.*?)\)/)?.[1] || "None";
this.video = thumb.match(/html5player.setVideoUrlHigh\((.*?)\)/)?.[1] || "None";
this.link = $("meta[property='og:url']").attr("content") || "None";
this.id = this.link.split(".com/")[1] || "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") || "None";
this.views = $("span.metadata").text() || "None";
this.views = this.views.split("-")[2] || "None";
this.uploaded = $("script[type='application/ld+json']").text() || "None";
this.uploaded = this.uploaded
.split("uploadDate")[1]
.split("}")[0]
.split(":")[1]
.replace(/"/g, "")
.replace(/,/g, "") || "None";
this.action = $("span.vote-actions")
.find("span.value")
.map((i, el) => {
return $(el).text();
}).get();
this.upVote = this.action[0] || "None";
this.downVote = this.action[1] || "None";
this.favVote = $("span.rating-box.value").text() || "None";
this.models = $("a.is-pornstar")
.map((i, el) => {
return $(el).text();
}).get();
this.tags = $("div.metadata-row.video-tags")
.find("a")
.map((i, el) => {
return $(el).text();
}).get();
}
}
const x = new Xnxx();
const data: IVideoData = {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(x.title),
id: x.id,
image: x.image,
duration: lust.secondToMinute(Number(x.duration)),
views: lust.removeHtmlTag(x.views),
rating: x.favVote,
uploaded: x.uploaded.trim(),
upvoted: x.upVote,
downvoted: x.downVote,
models: x.models,
tags: x.tags.filter((el) => el !== "Edit tags and models")
},
source: x.link,
assets: lust.removeAllSingleQuoteOnArray([x.thumbnail, x.bigimg, x.video])
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,55 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
class PornhubSearch {
search: object[];
data: object;
constructor() {
// in <div id="video-player-bg"> get <script>var video_related=
this.search = $("div#video-player-bg")
.map((i, el) => {
const script = $(el).find("script").html();
const video_related = script?.split("var video_related=")[1];
//stop and replace everything after the last ];
const badJson = video_related?.split("];")[0] + "]";
const actualResult = JSON.parse(String(badJson));
const result = actualResult.map((el: any) => {
return {
link: `${c.XNXX}${el.u}`,
id: el.u,
title: el.t,
image: el.i,
duration: el.d,
views: `${el.n}, ${el.r}`,
video: null
};
});
return result;
}).get();
}
}
const x = new PornhubSearch();
if (x.search.length === 0) throw Error("No result found");
const data = x.search as unknown as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,49 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
class PornhubSearch {
search: object[];
constructor() {
this.search = $("div.mozaique > div")
.map((i, el) => {
return {
link: `${c.XNXX}${$(el).find("a").attr("href")}`,
id: $(el).find("a").attr("href"),
title: $(el).find("div.thumb-under").text().split("\n")
.map((el) => el.trim()).filter((el) => el !== "")[0],
image: $(el).find("img").attr("data-src"),
duration: $(el).find("div.thumb-under").text().split("\n")
.map((el) => el.trim()).filter((el) => el !== "")[2],
rating: $(el).find("div.thumb-under").text().split("\n")
.map((el) => el.trim()).filter((el) => el !== "")[1],
video: null
};
}).get();
}
}
const x = new PornhubSearch();
if (x.search.length === 0) throw Error("No result found");
const data = x.search as unknown as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,92 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { IVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
class Xvideos {
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[];
thumbnail: string;
bigimg: string;
constructor() {
this.link = $("meta[property='og:url']").attr("content") || "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 = $("div#v-views").find("strong.mobile-hide").text() || "None";
this.rating = $("span.rating-total-txt").text() || "None";
this.publish = $("script[type='application/ld+json']").text() || "None";
this.publish = this.publish
.split("uploadDate")[1]
.split("}")[0]
.split(":")[1]
.replace(/"/g, "")
.replace(/,/g, "") || "None";
this.upVote = $("span.rating-good-nbr").text() || "None";
this.downVote = $("span.rating-bad-nbr").text() || "None";
const thumb = $("script")
.map((i, el) => {
return $(el).text();
}).get()
.filter((el) => el.includes("html5player.setThumbSlideBig"))[0] || "None";
this.thumbnail = thumb.match(/html5player.setThumbSlideBig\((.*?)\)/)?.[1] || "None";
this.bigimg = thumb.match(/html5player.setThumbUrl169\((.*?)\)/)?.[1] || "None";
this.video = thumb.match(/html5player.setVideoUrlHigh\((.*?)\)/)?.[1] || "None";
this.tags = $("a.is-keyword.btn.btn-default")
.map((i, el) => {
return $(el).text();
}).get();
this.models = $("li.model")
.map((i, el) => {
return $(el).find("a").attr("href") || "None";
}
).get();
this.models = this.models.map((el) => el.split("/")[2]);
}
}
const xv = new Xvideos();
const data: IVideoData = {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(xv.title),
id: xv.id,
image: xv.image,
duration: lust.secondToMinute(Number(xv.duration)),
views: lust.removeHtmlTag(xv.views),
rating: xv.rating,
uploaded: xv.publish,
upvoted: xv.upVote,
downvoted: xv.downVote,
models: xv.models,
tags: xv.tags,
},
source: xv.link,
assets: lust.removeAllSingleQuoteOnArray([xv.thumbnail, xv.bigimg, xv.video])
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,54 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
console.log(url);
class XvideosSearch {
search: object[];
data: object;
constructor() {
this.search = $("div#video-player-bg")
.map((i, el) => {
const script = $(el).find("script").html();
const video_related = script?.split("var video_related=")[1];
const badJson = video_related?.split("];")[0] + "]";
const actualResult = JSON.parse(String(badJson));
const result = actualResult.map((el: any) => {
return {
link: `${c.XVIDEOS}${el.u}`,
id: el.u,
title: el.t,
image: el.i,
duration: el.d,
views: `${el.n}, ${el.r}`,
video: null
};
});
return result;
}).get();
}
}
const x = new XvideosSearch();
if (x.search.length === 0) throw Error("No result found");
const data = x.search as unknown as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,66 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
console.log(url);
const res = await lust.fetchBody(url);
const $ = load(res);
class XvideosSearch {
search: object[];
constructor() {
const data = $("div.thumb-under")
.map((i, el) => {
return {
title: $(el).find("a").attr("title"),
duration: $(el).find("span.duration")
.map((i, el) => {
return $(el).text();
}).get()[0],
};
}).get();
this.search = $("div.mozaique.cust-nb-cols")
.find("div.thumb")
.map((i, el) => {
return {
link: `${c.XVIDEOS}${$(el).find("a").attr("href")}` || "None",
id: $(el).find("a").attr("href") || "None",
image: $(el).find("img").attr("data-src") || "None",
title: data[i].title || "None",
duration: data[i].duration === data[i + 1]?.duration
? ""
: data[i].duration || "None",
rating: null,
video: null
};
}).get();
this.search = this.search.filter((el: any) => {
return !el.id.includes("THUMBNUM");
});
this.search = this.search.filter((el: any) => {
return el.id.includes("/video");
});
}
}
const xv = new XvideosSearch();
if (xv.search.length === 0) throw Error("No result found");
const data = xv.search as unknown as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,73 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import { IVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const resolve = await lust.fetchBody(url);
const $ = load(resolve);
class YouPorn {
link: string;
id: string;
title: string;
image: string;
duration: string;
views: string;
rating: string;
publish: string;
upVote: string;
downVote: null;
video: string;
tags: string[];
models: string[];
constructor() {
this.link = $("link[rel='canonical']").attr("href") || "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.downVote = null;
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();
}
}
const yp = new YouPorn();
const data: IVideoData = {
success: true,
data: {
title: lust.removeHtmlTagWithoutSpace(yp.title),
id: yp.id,
image: yp.image,
duration: lust.secondToMinute(Number(yp.duration)),
views: yp.views,
rating: yp.rating,
uploaded: yp.publish,
upvoted: yp.upVote,
downvoted: yp.downVote,
models: yp.models,
tags: yp.tags
},
source: yp.link,
assets: [yp.video, yp.image]
};
return data;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,53 @@
import { load } from "cheerio";
import LustPress from "../../LustPress";
import c from "../../utils/options";
import { ISearchVideoData } from "../../interfaces";
const lust = new LustPress();
export async function scrapeContent(url: string) {
try {
const res = await lust.fetchBody(url);
const $ = load(res);
class YouPornSearch {
dur: string[];
search: object[];
constructor() {
this.dur = $("div.video-duration").map((i, el) => {
return $(el).text();
}).get();
this.search = $("a[href^='/watch/']")
.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: null,
video: `https://www.youporn.com/embed/${id}`,
};
}).get();
}
}
const yp = new YouPornSearch();
if (yp.search.length === 0) throw Error("No result found");
const data = yp.search as unknown as string[];
const result: ISearchVideoData = {
success: true,
data: data,
source: url,
};
return result;
} catch (err) {
const e = err as Error;
throw Error(e.message);
}
}

View File

@@ -0,0 +1,17 @@
import rateLimit from "express-rate-limit";
import slowDown from "express-slow-down";
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 50,
message: "Too nasty, please slow down"
});
const slow = slowDown({
delayAfter: 50,
windowMs: 15 * 60 * 1000,
delayMs: 1000,
maxDelayMs: 20000,
});
export { limiter, slow };

8
src/utils/logger.ts Normal file
View File

@@ -0,0 +1,8 @@
import pino from "pino";
export const logger = pino({
level: "info",
transport: {
target: "pino-pretty"
},
});

39
src/utils/modifier.ts Normal file
View File

@@ -0,0 +1,39 @@
/**
* Auto space on url
* @param str the string to be spaced
* @returns string
*/
export function spacer(str: string) {
return str.replace(/\s/g, "+");
}
/**
* Error handler
* @param success when success is false, it will return error
* @param message error message
* @returns object
*/
export function maybeError(success: boolean, message: string) {
return { success, message };
}
export function timeAgo(input: Date) {
const date = new Date(input);
const formatter: any = new Intl.RelativeTimeFormat("en");
const ranges: { [key: string]: number } = {
years: 3600 * 24 * 365,
months: 3600 * 24 * 30,
weeks: 3600 * 24 * 7,
days: 3600 * 24,
hours: 3600,
minutes: 60,
seconds: 1
};
const secondsElapsed = (date.getTime() - Date.now()) / 1000;
for (const key in ranges) {
if (ranges[key] < Math.abs(secondsElapsed)) {
const delta = secondsElapsed / ranges[key];
return formatter.format(Math.round(delta), key);
}
}
}

9
src/utils/options.ts Normal file
View File

@@ -0,0 +1,9 @@
export default {
PORNHUB: "https://www.pornhub.com",
XNXX: "https://www.xnxx.com",
REDTUBE: "https://www.redtube.com",
XVIDEOS: "https://www.xvideos.com",
XHAMSTER: "https://xheve2.com",
YOUPORN: "https://www.youporn.com",
JAVHD: "https://javhd.today"
};

20
test/mock.ts Normal file
View File

@@ -0,0 +1,20 @@
import p from "phin";
import { load } from "cheerio";
const url = "https://www.pornhub.com/view_video.php?viewkey=ph63c4e1dc48fe7";
async function test() {
const res = await p({
url: url,
"headers": {
"User-Agent": process.env.USER_AGENT || "lustpress/1.6.0 Node.js/16.9.1",
},
});
const $ = load(res.body);
const title = $("meta[property='og:title']").attr("content");
console.log(title);
console.log(res.statusCode);
}
test().catch(console.error);

14
test/test.ts Normal file
View File

@@ -0,0 +1,14 @@
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]) {
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`);
}
else {
console.log(`${url} is available, can be scraped`);
}
});
}