├── .eslintignore ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── playground.yml │ ├── status.yml │ ├── xnxx.yml │ ├── build.yml │ ├── pornhub.yml │ ├── redtube.yml │ ├── xvideos.yml │ ├── youporn.yml │ ├── xhamster.yml │ └── docker.yml ├── .dockerignore ├── SECURITY.md ├── src ├── utils │ ├── logger.ts │ ├── options.ts │ ├── limit-options.ts │ └── modifier.ts ├── interfaces.ts ├── index.ts ├── controller │ ├── pornhub │ │ ├── pornhubRandom.ts │ │ ├── pornhubGet.ts │ │ ├── pornhubGetRelated.ts │ │ └── pornhubSearch.ts │ ├── xnxx │ │ ├── xnxxGet.ts │ │ ├── xnxxGetRelated.ts │ │ ├── xnxxSearch.ts │ │ └── xnxxRandom.ts │ ├── redtube │ │ ├── redtubeGetRelated.ts │ │ ├── redtubeGet.ts │ │ ├── redtubeSearch.ts │ │ └── redtubeRandom.ts │ ├── youporn │ │ ├── youpornGet.ts │ │ ├── youpornGetRelated.ts │ │ ├── youpornRandom.ts │ │ └── youpornSearch.ts │ ├── xhamster │ │ ├── xhamsterGet.ts │ │ ├── xhamsterGetRelated.ts │ │ ├── xhamsterSearch.ts │ │ └── xhamsterRandom.ts │ └── xvideos │ │ ├── xvideosGet.ts │ │ ├── xvideosGetRelated.ts │ │ ├── xvideosSearch.ts │ │ └── xvideosRandom.ts ├── scraper │ ├── xvideos │ │ ├── xvideosGetRelatedController.ts │ │ ├── xvideosSearchController.ts │ │ └── xvideosGetController.ts │ ├── youporn │ │ ├── youpornSearchController.ts │ │ └── youpornGetController.ts │ ├── xnxx │ │ ├── xnxxSearchController.ts │ │ ├── xnxxGetRelatedController.ts │ │ └── xnxxGetController.ts │ ├── xhamster │ │ ├── xhamsterSearchController.ts │ │ └── xhamsterGetController.ts │ ├── pornhub │ │ ├── pornhubSearchController.ts │ │ └── pornhubGetController.ts │ └── redtube │ │ ├── redtubeSearchController.ts │ │ └── redtubeGetController.ts ├── router │ └── endpoint.ts └── LustPress.ts ├── .gitignore ├── Dockerfile ├── test ├── test.ts └── mock.ts ├── .env.schema ├── CONTRIBUTING.md ├── CLOSING_REMARKS.md ├── tsconfig.json ├── fly.toml ├── .eslintrc.json ├── LICENSE ├── package.json ├── CODE_OF_CONDUCT.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: sinkaroid -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Lustpress Security 2 | 3 | ## Reporting vulnerabilities 4 | 5 | To report sensitive vulnerabilities, alert the author by email at anakmancasan@gmail.com. -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import pino from "pino"; 2 | 3 | export const logger = pino({ 4 | level: "info", 5 | transport: { 6 | target: "pino-pretty" 7 | }, 8 | }); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | yarn.lock 3 | package-lock.json 4 | /build 5 | /playground 6 | /template 7 | /docs 8 | p.ts 9 | .env 10 | .idea 11 | CHANGELOG.md 12 | theme.zip -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | 3 | WORKDIR /srv/app 4 | COPY package*.json ./ 5 | RUN npm install 6 | COPY . . 7 | RUN npm run build 8 | EXPOSE 3000 9 | CMD ["node", "build/src/index.js"] -------------------------------------------------------------------------------- /src/utils/options.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | PORNHUB: "https://www.pornhub.com", 3 | XNXX: "https://www.xnxx.com", 4 | REDTUBE: "https://www.redtube.com", 5 | XVIDEOS: "https://www.xvideos.com", 6 | XHAMSTER: "https://xheve2.com", 7 | YOUPORN: "https://www.youporn.com", 8 | JAVHD: "https://javhd.today" 9 | }; -------------------------------------------------------------------------------- /src/utils/limit-options.ts: -------------------------------------------------------------------------------- 1 | import rateLimit from "express-rate-limit"; 2 | import slowDown from "express-slow-down"; 3 | 4 | const limiter = rateLimit({ 5 | windowMs: 15 * 60 * 1000, 6 | max: 50, 7 | message: "Too nasty, please slow down" 8 | }); 9 | 10 | const slow = slowDown({ 11 | delayAfter: 50, 12 | windowMs: 15 * 60 * 1000, 13 | delayMs: 1000, 14 | maxDelayMs: 20000, 15 | }); 16 | 17 | export { limiter, slow }; -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | import c from "../src/utils/options"; 2 | import p from "phin"; 3 | 4 | for (const url of 5 | [c.PORNHUB, c.XNXX, c.REDTUBE, c.XVIDEOS, c.XHAMSTER, c.YOUPORN]) { 6 | p({ url }).then(res => { 7 | if (res.statusCode !== 200) { 8 | console.log(`${url} is not available, status code: ${res.statusCode}, check the sites or your own user-agent`); 9 | } 10 | else { 11 | console.log(`${url} is available, can be scraped`); 12 | } 13 | }); 14 | } -------------------------------------------------------------------------------- /.env.schema: -------------------------------------------------------------------------------- 1 | # railway, fly.dev, heroku, vercel or any free service 2 | RAILWAY = sinkaroid 3 | 4 | # default port 5 | PORT = 3000 6 | 7 | # backend storage, default is redis, if not set it will consume memory storage 8 | REDIS_URL = redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.cloud.redislabs.com:1337 9 | 10 | # ttl expire cache (in X hour) 11 | EXPIRE_CACHE = 1 12 | 13 | # you must identify your origin, if not set it will use default 14 | USER_AGENT = "lustpress/1.6.0 Node.js/16.9.1" -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Making major changes 4 | Please first discuss the change you wish to make via issue, 5 | email, or any other method with the owners before making a change. 6 | 7 | Please note we have a code of conduct, follow it in all your interactions with the project. 8 | 9 | ## Making minor changes 10 | @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. -------------------------------------------------------------------------------- /test/mock.ts: -------------------------------------------------------------------------------- 1 | import p from "phin"; 2 | import { load } from "cheerio"; 3 | 4 | const url = "https://www.pornhub.com/view_video.php?viewkey=ph63c4e1dc48fe7"; 5 | 6 | async function test() { 7 | const res = await p({ 8 | url: url, 9 | "headers": { 10 | "User-Agent": process.env.USER_AGENT || "lustpress/1.6.0 Node.js/16.9.1", 11 | }, 12 | }); 13 | 14 | const $ = load(res.body); 15 | const title = $("meta[property='og:title']").attr("content"); 16 | console.log(title); 17 | console.log(res.statusCode); 18 | } 19 | 20 | test().catch(console.error); 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: bug 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Desktop (please complete the following information):** 20 | - OS: [e.g. iOS] 21 | - Node version [node -v] 22 | - Typescript version [tsc -v] 23 | 24 | **Additional context** 25 | Add any other context about the problem here. 26 | -------------------------------------------------------------------------------- /.github/workflows/playground.yml: -------------------------------------------------------------------------------- 1 | name: Playground 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build-and-deploy: 11 | concurrency: ci-${{ github.ref }} 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v3 16 | 17 | - name: Installing dependencies 18 | run: npm install 19 | - name: Generating docs 20 | run: npm run build:apidoc 21 | 22 | - name: Deploy 23 | uses: JamesIves/github-pages-deploy-action@v4.4.1 24 | with: 25 | branch: gh-pages 26 | folder: docs -------------------------------------------------------------------------------- /src/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IVideoData { 2 | success: boolean; 3 | data: { 4 | title: string; 5 | id: string; 6 | image: string; 7 | duration: string; 8 | views: string; 9 | rating: string; 10 | uploaded: string; 11 | upvoted: string | null; 12 | downvoted: string | null; 13 | models: string[]; 14 | tags: string[]; 15 | }; 16 | source: string; 17 | assets: string[]; 18 | } 19 | 20 | export interface ISearchVideoData { 21 | success: boolean; 22 | data: string[]; 23 | source: string; 24 | } 25 | 26 | export interface MaybeError { 27 | message: string; 28 | } 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/status.yml: -------------------------------------------------------------------------------- 1 | name: Check status 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | - name: Check status code 29 | run: npm run test -------------------------------------------------------------------------------- /.github/workflows/xnxx.yml: -------------------------------------------------------------------------------- 1 | name: Xnxx test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Xnxx test 30 | run: npm run test:xnxx -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Run build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | 27 | - name: Check lint 28 | run: npm run lint 29 | 30 | - name: Build 31 | run: npm run build -------------------------------------------------------------------------------- /.github/workflows/pornhub.yml: -------------------------------------------------------------------------------- 1 | name: Pornhub test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Pornhub test 30 | run: npm run test:pornhub -------------------------------------------------------------------------------- /.github/workflows/redtube.yml: -------------------------------------------------------------------------------- 1 | name: Redtube test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Redtube test 30 | run: npm run test:redtube -------------------------------------------------------------------------------- /.github/workflows/xvideos.yml: -------------------------------------------------------------------------------- 1 | name: Xvideos test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Xvideos test 30 | run: npm run test:xvideos -------------------------------------------------------------------------------- /.github/workflows/youporn.yml: -------------------------------------------------------------------------------- 1 | name: Youporn test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Youporn test 30 | run: npm run test:youporn -------------------------------------------------------------------------------- /.github/workflows/xhamster.yml: -------------------------------------------------------------------------------- 1 | name: Xhamster test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [16.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install dependencies 25 | run: npm install 26 | - name: Build 27 | run: npm run build 28 | 29 | - name: Xhamster test 30 | run: npm run test:xhamster -------------------------------------------------------------------------------- /CLOSING_REMARKS.md: -------------------------------------------------------------------------------- 1 | # Closing remarks 2 | 3 | I hope you have found this project useful. All the major credit really goes to the all the doujin sites, which 4 | allows this services to operate. 5 | 6 | - [PornHub](https://pornhub.com) 7 | - [Xnxx](https://xnxx.com) 8 | - [RedTube](https://redtube.com) 9 | - [Xvideos](https://xvideos.com) 10 | - [Xhamster](https://xhamster.com) 11 | - [YouPorn](https://youporn.com) 12 | - [JavHD](https://javhd.com) 13 | 14 | Core dependencies: 15 | - [express](https://github.com/expressjs/express) 16 | - [cheerio](https://cheerio.js.org/) 17 | - [keyv](https://github.com/jaredwray/keyv) 18 | 19 | # Alternative-links 20 | Just in case if https://lust.scathach.id down, here some alternative deployment 21 | 22 | - https://lustpress.cyclic.app 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe new features you want** 11 | A clear and concise description of what new features you want. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | 22 | **A Gif to describe how you feel when you made this request** 23 | Add a gif here to express how you feel when you made this request. 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/tsconfig", 3 | "compilerOptions": { 4 | "outDir": "./build", 5 | "allowJs": true, 6 | "target": "ESNext", 7 | "baseUrl": "src", 8 | "resolveJsonModule": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "moduleResolution": "node", 12 | "module": "commonjs", 13 | "paths": {}, 14 | "typeRoots": ["./node_modules/@types"], 15 | "inlineSourceMap": true, 16 | "downlevelIteration": true, 17 | "newLine": "lf", 18 | "strict": true, 19 | "strictBindCallApply": true, 20 | "strictPropertyInitialization": false, 21 | "declaration": true 22 | }, 23 | "include": [ 24 | "src/**/*" 25 | ], 26 | "exclude": ["node_modules", "build", "out", "tmp", "logs", "test"] 27 | } 28 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml file generated for lust on 2023-04-18T12:01:37+07:00 2 | 3 | app = "lust" 4 | kill_signal = "SIGINT" 5 | kill_timeout = 5 6 | processes = [] 7 | 8 | [env] 9 | 10 | [experimental] 11 | auto_rollback = true 12 | 13 | [[services]] 14 | autostart = true 15 | autostop = false 16 | http_checks = [] 17 | internal_port = 3000 18 | processes = ["app"] 19 | protocol = "tcp" 20 | script_checks = [] 21 | [services.concurrency] 22 | hard_limit = 25 23 | soft_limit = 20 24 | type = "connections" 25 | 26 | [[services.ports]] 27 | force_https = true 28 | handlers = ["http"] 29 | port = 80 30 | 31 | [[services.ports]] 32 | handlers = ["tls", "http"] 33 | port = 443 34 | 35 | [[services.tcp_checks]] 36 | grace_period = "1s" 37 | interval = "15s" 38 | restart_limit = 0 39 | timeout = "2s" 40 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2021": true, 4 | "node": true, 5 | "commonjs": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "parser": "@typescript-eslint/parser", 12 | "parserOptions": { 13 | "ecmaVersion": 12, 14 | "sourceType": "module" 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint" 18 | ], 19 | "rules": { 20 | "linebreak-style": 0, 21 | "quotes": [ 22 | "error", 23 | "double" 24 | ], 25 | "semi": [ 26 | "error", 27 | "always" 28 | ], 29 | "no-empty": "error", 30 | "no-func-assign": "error", 31 | "no-case-declarations": "off", 32 | "no-unreachable": "error", 33 | "no-eval": "error", 34 | "no-global-assign": "error", 35 | "@typescript-eslint/no-explicit-any": ["off"], 36 | "indent": [ 37 | "error", 38 | 2 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | name: Docker 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Log into GitHub Container Registry 15 | run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin 16 | 17 | - name: Get the package.json version 18 | id: package-version 19 | run: echo ::set-output name=version::$(jq -r .version package.json) 20 | - name: Build the Docker image 21 | run: docker build . --file Dockerfile --tag ghcr.io/${{ github.repository }}:${{ steps.package-version.outputs.version }} 22 | - name: Tag the Docker image 23 | run: docker tag ghcr.io/${{ github.repository }}:${{ steps.package-version.outputs.version }} ghcr.io/${{ github.repository }}:latest 24 | - name: Push the Docker image 25 | run: docker push ghcr.io/${{ github.repository }} --all-tags 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 sinkaroid. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/utils/modifier.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Auto space on url 3 | * @param str the string to be spaced 4 | * @returns string 5 | */ 6 | export function spacer(str: string) { 7 | return str.replace(/\s/g, "+"); 8 | } 9 | 10 | /** 11 | * Error handler 12 | * @param success when success is false, it will return error 13 | * @param message error message 14 | * @returns object 15 | */ 16 | export function maybeError(success: boolean, message: string) { 17 | return { success, message }; 18 | } 19 | 20 | export function timeAgo(input: Date) { 21 | const date = new Date(input); 22 | const formatter: any = new Intl.RelativeTimeFormat("en"); 23 | const ranges: { [key: string]: number } = { 24 | years: 3600 * 24 * 365, 25 | months: 3600 * 24 * 30, 26 | weeks: 3600 * 24 * 7, 27 | days: 3600 * 24, 28 | hours: 3600, 29 | minutes: 60, 30 | seconds: 1 31 | }; 32 | const secondsElapsed = (date.getTime() - Date.now()) / 1000; 33 | for (const key in ranges) { 34 | if (ranges[key] < Math.abs(secondsElapsed)) { 35 | const delta = secondsElapsed / ranges[key]; 36 | return formatter.format(Math.round(delta), key); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import LustPress from "./LustPress"; 3 | import express from "express"; 4 | import { Request, Response, NextFunction } from "express"; 5 | import scrapeRoutes from "./router/endpoint"; 6 | import { slow, limiter } from "./utils/limit-options"; 7 | import { logger } from "./utils/logger"; 8 | import * as pkg from "../package.json"; 9 | 10 | const lust = new LustPress(); 11 | const app = express(); 12 | 13 | 14 | app.get("/", slow, limiter, async (req, res) => { 15 | res.send({ 16 | success: true, 17 | playground: "https://sinkaroid.github.io/lustpress", 18 | endpoint: "https://github.com/sinkaroid/lustpress/blob/master/README.md#routing", 19 | date: new Date().toLocaleString(), 20 | rss: lust.currentProccess().rss, 21 | heap: lust.currentProccess().heap, 22 | server: await lust.getServer(), 23 | version: `${pkg.version}`, 24 | }); 25 | logger.info({ 26 | path: req.path, 27 | method: req.method, 28 | ip: req.ip, 29 | useragent: req.get("User-Agent") 30 | }); 31 | }); 32 | 33 | app.use(scrapeRoutes()); 34 | app.use((req: Request, res: Response, next: NextFunction) => { 35 | res.status(404); 36 | next(Error(`The page not found in path ${req.url} and method ${req.method}`)); 37 | logger.error({ 38 | path: req.url, 39 | method: req.method, 40 | ip: req.ip, 41 | useragent: req.get("User-Agent") 42 | }); 43 | }); 44 | 45 | app.use((error: any, res: Response) => { 46 | res.status(500).json({ 47 | message: error.message, 48 | stack: error.stack 49 | }); 50 | }); 51 | 52 | app.listen(process.env.PORT || 3000, () => console.log(`${pkg.name} is running on port ${process.env.PORT || 3000}`)); -------------------------------------------------------------------------------- /src/controller/pornhub/pornhubRandom.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { scrapeContent } from "../../scraper/pornhub/pornhubGetController"; 3 | import c from "../../utils/options"; 4 | import { logger } from "../../utils/logger"; 5 | import { maybeError } from "../../utils/modifier"; 6 | 7 | export async function randomPornhub(req: Request, res: Response) { 8 | try { 9 | /** 10 | * @api {get} /pornhub/random Random pornhub video 11 | * @apiName Random pornhub 12 | * @apiGroup pornhub 13 | * @apiDescription Gets random pornhub video 14 | * 15 | * @apiSuccessExample {json} Success-Response: 16 | * HTTP/1.1 200 OK 17 | * HTTP/1.1 400 Bad Request 18 | * 19 | * @apiExample {curl} curl 20 | * curl -i https://lust.scathach.id/pornhub/random 21 | * 22 | * @apiExample {js} JS/TS 23 | * import axios from "axios" 24 | * 25 | * axios.get("https://lust.scathach.id/pornhub/random") 26 | * .then(res => console.log(res.data)) 27 | * .catch(err => console.error(err)) 28 | * 29 | * @apiExample {python} Python 30 | * import aiohttp 31 | * async with aiohttp.ClientSession() as session: 32 | * async with session.get("https://lust.scathach.id/pornhub/random") as resp: 33 | * print(await resp.json()) 34 | * 35 | */ 36 | const url = `${c.PORNHUB}/video/random`; 37 | const data = await scrapeContent(url); 38 | logger.info({ 39 | path: req.path, 40 | query: req.query, 41 | method: req.method, 42 | ip: req.ip, 43 | useragent: req.get("User-Agent") 44 | }); 45 | return res.json(data); 46 | } catch (err) { 47 | const e = err as Error; 48 | res.status(400).json(maybeError(false, e.message)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/scraper/xvideos/xvideosGetRelatedController.ts: -------------------------------------------------------------------------------- 1 | import { load } from "cheerio"; 2 | import LustPress from "../../LustPress"; 3 | import c from "../../utils/options"; 4 | import { ISearchVideoData } from "../../interfaces"; 5 | 6 | const lust = new LustPress(); 7 | 8 | export async function scrapeContent(url: string) { 9 | try { 10 | const res = await lust.fetchBody(url); 11 | const $ = load(res); 12 | 13 | class XvideosSearch { 14 | search: object[]; 15 | data: object; 16 | constructor() { 17 | this.search = $("div#video-player-bg") 18 | .map((i, el) => { 19 | const script = $(el).find("script").html(); 20 | const video_related = script?.split("var video_related=")[1]; 21 | const badJson = video_related?.split("];")[0] + "]"; 22 | const actualResult = JSON.parse(String(badJson)); 23 | const result = actualResult.map((el: any) => { 24 | return { 25 | link: `${c.XVIDEOS}${el.u}`, 26 | id: el.u.slice(1, -1), 27 | title: el.t, 28 | image: el.i, 29 | duration: el.d, 30 | views: `${el.n}, ${el.r}`, 31 | video: `${c.XVIDEOS}/embedframe/${el.id}` 32 | }; 33 | }); 34 | return result; 35 | }).get(); 36 | } 37 | } 38 | 39 | const x = new XvideosSearch(); 40 | if (x.search.length === 0) throw Error("No result found"); 41 | const data = x.search as unknown as string[]; 42 | const result: ISearchVideoData = { 43 | success: true, 44 | data: data, 45 | source: url, 46 | }; 47 | return result; 48 | 49 | } catch (err) { 50 | const e = err as Error; 51 | throw Error(e.message); 52 | } 53 | } -------------------------------------------------------------------------------- /src/scraper/youporn/youpornSearchController.ts: -------------------------------------------------------------------------------- 1 | import { load } from "cheerio"; 2 | import LustPress from "../../LustPress"; 3 | import c from "../../utils/options"; 4 | import { ISearchVideoData } from "../../interfaces"; 5 | 6 | const lust = new LustPress(); 7 | 8 | export async function scrapeContent(url: string) { 9 | try { 10 | const res = await lust.fetchBody(url); 11 | const $ = load(res); 12 | 13 | class YouPornSearch { 14 | dur: string[]; 15 | search: object[]; 16 | constructor() { 17 | this.dur = $("div.video-duration").map((i, el) => { 18 | return $(el).text(); 19 | }).get(); 20 | this.search = $("a[href^='/watch/']") 21 | .map((i, el) => { 22 | const link = $(el).attr("href"); 23 | const id = `${link}`.split("/")[2] + "/" + `${link}`.split("/")[3]; 24 | const title = $(el).find("div.video-box-title").text(); 25 | const image = $(el).find("img").attr("data-thumbnail"); 26 | return { 27 | link: `${c.YOUPORN}${link}`, 28 | id: id, 29 | title: lust.removeHtmlTagWithoutSpace(title), 30 | image: image, 31 | duration: this.dur[i], 32 | views: "None", 33 | video: `https://www.youporn.com/embed/${id}`, 34 | }; 35 | }).get(); 36 | } 37 | } 38 | 39 | const yp = new YouPornSearch(); 40 | if (yp.search.length === 0) throw Error("No result found"); 41 | const data = yp.search as unknown as string[]; 42 | const result: ISearchVideoData = { 43 | success: true, 44 | data: data, 45 | source: url, 46 | }; 47 | return result; 48 | 49 | } catch (err) { 50 | const e = err as Error; 51 | throw Error(e.message); 52 | } 53 | } -------------------------------------------------------------------------------- /src/scraper/xnxx/xnxxSearchController.ts: -------------------------------------------------------------------------------- 1 | import { load } from "cheerio"; 2 | import LustPress from "../../LustPress"; 3 | import c from "../../utils/options"; 4 | import { ISearchVideoData } from "../../interfaces"; 5 | 6 | const lust = new LustPress(); 7 | 8 | export async function scrapeContent(url: string) { 9 | try { 10 | const res = await lust.fetchBody(url); 11 | const $ = load(res); 12 | 13 | class PornhubSearch { 14 | search: object[]; 15 | constructor() { 16 | this.search = $("div.mozaique > div") 17 | .map((i, el) => { 18 | return { 19 | link: `${c.XNXX}${$(el).find("a").attr("href")}`, 20 | // remove first "/" and last "/" 21 | id: $(el).find("a").attr("href")?.slice(1, -1), 22 | title: $(el).find("div.thumb-under").text().split("\n") 23 | .map((el) => el.trim()).filter((el) => el !== "")[0], 24 | image: $(el).find("img").attr("data-src"), 25 | duration: $(el).find("div.thumb-under").text().split("\n") 26 | .map((el) => el.trim()).filter((el) => el !== "")[2], 27 | rating: $(el).find("div.thumb-under").text().split("\n") 28 | .map((el) => el.trim()).filter((el) => el !== "")[1], 29 | video: `${c.XNXX}/embedframe/${$(el).find("img").attr("data-videoid")}` 30 | }; 31 | }).get(); 32 | } 33 | } 34 | 35 | const x = new PornhubSearch(); 36 | if (x.search.length === 0) throw Error("No result found"); 37 | const data = x.search as unknown as string[]; 38 | const result: ISearchVideoData = { 39 | success: true, 40 | data: data, 41 | source: url, 42 | }; 43 | return result; 44 | 45 | } catch (err) { 46 | const e = err as Error; 47 | throw Error(e.message); 48 | } 49 | } -------------------------------------------------------------------------------- /src/scraper/xhamster/xhamsterSearchController.ts: -------------------------------------------------------------------------------- 1 | import { load } from "cheerio"; 2 | import LustPress from "../../LustPress"; 3 | import c from "../../utils/options"; 4 | import { ISearchVideoData } from "../../interfaces"; 5 | 6 | const lust = new LustPress(); 7 | 8 | export async function scrapeContent(url: string) { 9 | try { 10 | const res = await lust.fetchBody(url); 11 | const $ = load(res); 12 | 13 | class XhamsterSearch { 14 | search: any; 15 | constructor() { 16 | const views = $("div.video-thumb-views") 17 | .map((i, el) => { 18 | const views = $(el).text(); 19 | return views; 20 | }).get(); 21 | const duration = $("span[data-role='video-duration']") 22 | .map((i, el) => { 23 | const duration = $(el).text(); 24 | return duration; 25 | }).get(); 26 | this.search = $("a.video-thumb__image-container") 27 | .map((i, el) => { 28 | const link = $(el).attr("href"); 29 | 30 | return { 31 | link: `${link}`, 32 | id: link?.split("/")[3] + "/" + link?.split("/")[4], 33 | title: $(el).find("img").attr("alt"), 34 | image: $(el).find("img").attr("src"), 35 | duration: duration[i], 36 | views: views[i], 37 | video: `${c.XHAMSTER}/embed/${link?.split("-").pop()}` 38 | }; 39 | }).get(); 40 | } 41 | } 42 | 43 | const xh = new XhamsterSearch(); 44 | if (xh.search.length === 0) throw Error("No result found"); 45 | const data = xh.search as unknown as string[]; 46 | const result: ISearchVideoData = { 47 | success: true, 48 | data: data, 49 | source: url, 50 | }; 51 | return result; 52 | 53 | } catch (err) { 54 | const e = err as Error; 55 | throw Error(e.message); 56 | } 57 | } -------------------------------------------------------------------------------- /src/scraper/pornhub/pornhubSearchController.ts: -------------------------------------------------------------------------------- 1 | import { load } from "cheerio"; 2 | import LustPress from "../../LustPress"; 3 | import c from "../../utils/options"; 4 | import { ISearchVideoData } from "../../interfaces"; 5 | 6 | const lust = new LustPress(); 7 | 8 | export async function scrapeContent(url: string) { 9 | try { 10 | const res = await lust.fetchBody(url); 11 | const $ = load(res); 12 | 13 | class PornhubSearch { 14 | search: object[]; 15 | data: object; 16 | constructor() { 17 | this.search = $("div.wrap") 18 | .map((i, el) => { 19 | const link = $(el).find("a").attr("href"); 20 | const id = link?.split("=")[1]; 21 | const title = $(el).find("a").attr("title"); 22 | const image = $(el).find("img").attr("src"); 23 | const duration = $(el).find("var.duration").text(); 24 | const views = $(el).find("div.videoDetailsBlock").find("span.views").text(); 25 | return { 26 | link: `${c.PORNHUB}${link}`, 27 | id: id, 28 | title: title, 29 | image: image, 30 | duration: duration, 31 | views: views, 32 | video: `${c.PORNHUB}/embed/${id}`, 33 | }; 34 | }).get(); 35 | 36 | this.data = this.search.filter((el: any) => { 37 | return el.link.includes("javascript:void(0)") === false && el.image?.startsWith("data:image") === false; 38 | }); 39 | } 40 | 41 | } 42 | 43 | const ph = new PornhubSearch(); 44 | if (ph.search.length === 0) throw Error("No result found"); 45 | const data = ph.data as string[]; 46 | const result: ISearchVideoData = { 47 | success: true, 48 | data: data, 49 | source: url, 50 | }; 51 | return result; 52 | 53 | 54 | 55 | } catch (err) { 56 | const e = err as Error; 57 | throw Error(e.message); 58 | } 59 | } -------------------------------------------------------------------------------- /src/scraper/xnxx/xnxxGetRelatedController.ts: -------------------------------------------------------------------------------- 1 | import { load } from "cheerio"; 2 | import LustPress from "../../LustPress"; 3 | import c from "../../utils/options"; 4 | import { ISearchVideoData } from "../../interfaces"; 5 | 6 | const lust = new LustPress(); 7 | 8 | export async function scrapeContent(url: string) { 9 | try { 10 | const res = await lust.fetchBody(url); 11 | const $ = load(res); 12 | 13 | class PornhubSearch { 14 | search: object[]; 15 | data: object; 16 | constructor() { 17 | // in