Compare commits
10 Commits
8.1.2-alph
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67be3acc6e | ||
|
|
e92cb6ca8a | ||
|
|
85a45cf575 | ||
|
|
fe225e1090 | ||
|
|
6ed1567f0e | ||
|
|
67445ed648 | ||
|
|
e2c59b032b | ||
|
|
de73724e17 | ||
|
|
cd32689b47 | ||
|
|
90d858ae9d |
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -10,24 +10,24 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Check lint
|
||||
run: npm run lint
|
||||
run: bun run lint
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
run: bun run build
|
||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
|
||||
- name: Set image name
|
||||
run: echo "IMAGE_NAME=ghcr.io/$GITHUB_REPOSITORY" >> $GITHUB_ENV
|
||||
|
||||
23
.github/workflows/eporner.yml
vendored
23
.github/workflows/eporner.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Eporner test
|
||||
run: npm run test:eporner
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:eporner
|
||||
61
.github/workflows/playground.yml
vendored
61
.github/workflows/playground.yml
vendored
@@ -3,35 +3,64 @@ name: Playground
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
concurrency: ci-${{ github.ref }}
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Generating docs
|
||||
run: npm run build:apidoc
|
||||
- name: Generate Swagger JSON and HTML
|
||||
run: |
|
||||
mkdir public
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
curl -s http://localhost:3000/docs/json > public/swagger.json
|
||||
cat << 'EOF' > public/index.html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>LustPress API Playground</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
|
||||
<style>body { margin: 0; padding: 0; } .swagger-ui .topbar { display: none; }</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js"></script>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js"></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle({
|
||||
url: "./swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
|
||||
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
|
||||
});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
EOF
|
||||
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@9d877eea73427180ae43cf98e8914934fe157a1a
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: docs
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./public
|
||||
publish_branch: gh-pages
|
||||
|
||||
23
.github/workflows/pornhub.yml
vendored
23
.github/workflows/pornhub.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Pornhub test
|
||||
run: npm run test:pornhub
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:pornhub
|
||||
23
.github/workflows/redtube.yml
vendored
23
.github/workflows/redtube.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Redtube test
|
||||
run: npm run test:redtube
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:redtube
|
||||
56
.github/workflows/release.yml
vendored
Normal file
56
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set release metadata
|
||||
id: meta
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="$(jq -r .version package.json)"
|
||||
TAG="v${VERSION}"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check if tag already exists
|
||||
id: tag_check
|
||||
shell: bash
|
||||
run: |
|
||||
if git ls-remote --tags origin "refs/tags/${{ steps.meta.outputs.tag }}" | grep -q "${{ steps.meta.outputs.tag }}"; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create GitHub release
|
||||
if: steps.tag_check.outputs.exists == 'false'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.meta.outputs.tag }}
|
||||
name: ${{ steps.meta.outputs.tag }}
|
||||
target_commitish: master
|
||||
generate_release_notes: true
|
||||
|
||||
- name: Skip release when tag exists
|
||||
if: steps.tag_check.outputs.exists == 'true'
|
||||
run: echo "Tag ${{ steps.meta.outputs.tag }} already exists. Skipping release creation."
|
||||
17
.github/workflows/status.yml
vendored
17
.github/workflows/status.yml
vendored
@@ -14,20 +14,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
run: bun run build
|
||||
|
||||
- name: Check status code
|
||||
run: npm run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test
|
||||
23
.github/workflows/txxx.yml
vendored
23
.github/workflows/txxx.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Txxx test
|
||||
run: npm run test:txxx
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:txxx
|
||||
23
.github/workflows/xhamster.yml
vendored
23
.github/workflows/xhamster.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Xhamster test
|
||||
run: npm run test:xhamster
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:xhamster
|
||||
23
.github/workflows/xnxx.yml
vendored
23
.github/workflows/xnxx.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Xnxx test
|
||||
run: npm run test:xnxx
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:xnxx
|
||||
23
.github/workflows/xvideos.yml
vendored
23
.github/workflows/xvideos.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Xvideos test
|
||||
run: npm run test:xvideos
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:xvideos
|
||||
23
.github/workflows/youporn.yml
vendored
23
.github/workflows/youporn.yml
vendored
@@ -14,23 +14,20 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
|
||||
with:
|
||||
node-version: 24
|
||||
bun-version: latest
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
run: bun install
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Youporn test
|
||||
run: npm run test:youporn
|
||||
- name: Run test
|
||||
run: |
|
||||
bun run start:dev &
|
||||
sleep 3
|
||||
bun run test:youporn
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -9,4 +9,6 @@ p.ts
|
||||
.env
|
||||
.idea
|
||||
CHANGELOG.md
|
||||
theme.zip
|
||||
theme.zip
|
||||
AGENTS.md
|
||||
lustpress-express
|
||||
@@ -4,7 +4,7 @@ I hope you have found this project useful. All the major credit really goes to t
|
||||
allows this services to operate.
|
||||
|
||||
Core dependencies:
|
||||
- [express](https://github.com/expressjs/express)
|
||||
- [elysia](https://elysiajs.com/)
|
||||
- [cheerio](https://cheerio.js.org/)
|
||||
- [keyv](https://github.com/jaredwray/keyv)
|
||||
|
||||
|
||||
29
Dockerfile
29
Dockerfile
@@ -1,9 +1,26 @@
|
||||
FROM node:22
|
||||
|
||||
FROM oven/bun:1.3.13-alpine AS base
|
||||
WORKDIR /srv/app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
|
||||
# 1. Install production dependencies only
|
||||
FROM base AS deps
|
||||
COPY package.json bun.lock ./
|
||||
RUN bun install --production
|
||||
|
||||
# 2. Builder stage to compile TypeScript
|
||||
FROM base AS builder
|
||||
COPY package.json bun.lock ./
|
||||
RUN bun install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
RUN bun run build
|
||||
|
||||
# 3. Final release stage
|
||||
FROM base AS release
|
||||
COPY --from=deps /srv/app/node_modules ./node_modules
|
||||
COPY --from=builder /srv/app/build ./build
|
||||
COPY package.json ./
|
||||
|
||||
# Run as non-root user
|
||||
USER bun
|
||||
EXPOSE 3000
|
||||
CMD ["node", "build/src/index.js"]
|
||||
|
||||
CMD ["bun", "run", "start"]
|
||||
81
README.md
81
README.md
@@ -7,8 +7,8 @@
|
||||
<a href="https://codeclimate.com/github/sinkaroid/lustpress/maintainability"><img src="https://api.codeclimate.com/v1/badges/29a2be78f853f9e3a4a3/maintainability" /></a>
|
||||
</p>
|
||||
|
||||
Lustpress stand for Lust and **Express**, rebuild from [Jandapress](https://github.com/sinkaroid/jandapress) with completely different approach.
|
||||
The motivation of this project is to bring you an actionable data related to pornhub and other r18 sites with gather, similar design pattern, endpoint bindings, and consistent structure in mind.
|
||||
Lustpress was originally named Lust and Express (legacy name) and now runs on **Bun** + **Elysia**.
|
||||
The motivation of this project is carry an actionable data related to pornhub and other r18 sites with gather, similar design pattern, endpoint bindings, and consistent structure in mind.
|
||||
|
||||
<a href="https://sinkaroid.github.io/lustpress">Playground</a> •
|
||||
<a href="https://github.com/sinkaroid/lustpress/blob/master/CONTRIBUTING.md">Contributing</a> •
|
||||
@@ -17,7 +17,7 @@ The motivation of this project is to bring you an actionable data related to por
|
||||
|
||||
---
|
||||
|
||||
<a href="http://localhost:3000/"><img align="right" src="resources/project/images/bnnuy.png" width="320"></a>
|
||||
<a href="http://localhost:3000/"><img align="right" src="resources/project/images/nun.png" width="320"></a>
|
||||
|
||||
- [Lustpress](#)
|
||||
- [The problem](#the-problem)
|
||||
@@ -28,15 +28,14 @@ The motivation of this project is to bring you an actionable data related to por
|
||||
- [Installation](#installation)
|
||||
- [Docker](#docker)
|
||||
- [Manual](#manual)
|
||||
- [Tests case](#tests)
|
||||
- [Tests](#tests)
|
||||
- [Playground](https://sinkaroid.github.io/lustpress)
|
||||
- [Routing](#playground)
|
||||
- [Status response](#status-response)
|
||||
- [Ph Challenge Solver](#pornhub-js-challenge-solver)
|
||||
- [CLosing remarks](https://github.com/sinkaroid/lustpress/blob/master/CLOSING_REMARKS.md)
|
||||
- [Alternative links](https://github.com/sinkaroid/lustpress/blob/master/CLOSING_REMARKS.md#alternative-links)
|
||||
- [Pronunciation](#Pronunciation)
|
||||
- [Client libraries](#client-libraries)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
- [Legal](#legal)
|
||||
- [Discontinued playground](#frequently-asked-questions)
|
||||
- [again, discontinued playground](#frequently-asked-questions)
|
||||
@@ -81,7 +80,7 @@ Some tests may fail in CI environments because certain websites restrict or bloc
|
||||
|
||||
## Prerequisites
|
||||
<table>
|
||||
<td><b>NOTE:</b> NodeJS 22.x or higher</td>
|
||||
<td><b>NOTE:</b> Bun 1.3.13 or higher</td>
|
||||
</table>
|
||||
|
||||
To handle several requests from each website, You will also need [Redis](https://redis.io/) for persistent caching, free tier is available on [Redis Labs](https://redislabs.com/), You can also choose another adapters as we using [keyv](https://github.com/jaredwray/keyv) Key-value storage with support for multiple backends. When you choosing your own adapter, all data must be stored with `<Buffer>` type.
|
||||
@@ -90,9 +89,6 @@ To handle several requests from each website, You will also need [Redis](https:/
|
||||
Rename `.env.schema` to `.env` and fill the value with your own
|
||||
|
||||
```bash
|
||||
# railway, fly.dev, heroku, vercel or any free service
|
||||
RAILWAY = sinkaroid
|
||||
|
||||
# default port
|
||||
PORT = 3000
|
||||
|
||||
@@ -103,7 +99,7 @@ REDIS_URL = redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.clo
|
||||
EXPIRE_CACHE = 1
|
||||
|
||||
# you must identify your origin, if not set it will use default
|
||||
USER_AGENT = "lustpress/8.0.1 Node.js/22.22.0"
|
||||
USER_AGENT = "lustpress/8.2.3-alpha Bun/1.3.13"
|
||||
```
|
||||
|
||||
### Docker
|
||||
@@ -111,14 +107,14 @@ USER_AGENT = "lustpress/8.0.1 Node.js/22.22.0"
|
||||
docker pull ghcr.io/sinkaroid/lustpress:latest
|
||||
docker run -p 3000:3000 -d ghcr.io/sinkaroid/lustpress:latest
|
||||
|
||||
### Docker (your own)
|
||||
### Docker (adjust your own)
|
||||
```bash
|
||||
docker run -d \
|
||||
--name=lustpress \
|
||||
-p 3000:3000 \
|
||||
-p 3028:3000 \
|
||||
-e REDIS_URL='redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.cloud.redislabs.com:1337' \
|
||||
-e EXPIRE_CACHE='1' \
|
||||
-e USER_AGENT='lustpress/8.0.1 Node.js/22.22.0' \
|
||||
-e USER_AGENT='lustpress/8.2.3-alpha Bun/1.3.13' \
|
||||
ghcr.io/sinkaroid/lustpress:latest
|
||||
```
|
||||
|
||||
@@ -127,43 +123,42 @@ docker run -d \
|
||||
git clone https://github.com/sinkaroid/lustpress.git
|
||||
|
||||
- Install dependencies
|
||||
- `npm install / yarn install`
|
||||
- `bun install`
|
||||
- Lustpress production
|
||||
- `npm run start:prod`
|
||||
- `bun run start:prod`
|
||||
- Lustpress testing and hot reload
|
||||
- `npm run start:dev`
|
||||
- `bun run start:dev`
|
||||
|
||||
## Tests
|
||||
Run the following commands to execute tests for each supported source:
|
||||
|
||||
```bash
|
||||
# Check whether all supported sites are available for scraping
|
||||
npm run test
|
||||
bun run test
|
||||
|
||||
# Check whether ph and (maybe the others do..) do Solving challenge in their website
|
||||
npm run test:mock
|
||||
bun run test:mock
|
||||
|
||||
# Run tests for individual sources
|
||||
npm run test:pornhub
|
||||
npm run test:xnxx
|
||||
npm run test:redtube
|
||||
npm run test:xvideos
|
||||
npm run test:xhamster
|
||||
npm run test:youporn
|
||||
npm run test:eporner
|
||||
npm run test:txxx
|
||||
bun run test:pornhub
|
||||
bun run test:xnxx
|
||||
bun run test:redtube
|
||||
bun run test:xvideos
|
||||
bun run test:xhamster
|
||||
bun run test:youporn
|
||||
bun run test:eporner
|
||||
bun run test:txxx
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Start the production server
|
||||
`npm run start:prod`
|
||||
`bun run start:prod`
|
||||
|
||||
### Running development server
|
||||
`npm run start:dev`
|
||||
`bun run start:dev`
|
||||
|
||||
### Generating playground like swagger from apidoc definition
|
||||
`npm run build:apidoc`
|
||||
`bun run build:apidoc`
|
||||
|
||||
> To running other tests, you can see object scripts in file `package.json` or modify the `lustpress.test.ts` according your needs
|
||||
|
||||
@@ -298,6 +293,21 @@ https://sinkaroid.github.io/lustpress/#api-txxx
|
||||
HTTP/1.1 400 Bad Request
|
||||
HTTP/1.1 500 Fail to get data
|
||||
|
||||
## Pornhub JS Challenge Solver
|
||||
Pornhub serves a JavaScript challenge page to detect automated requests. Instead of relying on a headless browser (Playwright/Puppeteer), Lustpress solves this natively with zero external dependencies.
|
||||
|
||||
**How it works:**
|
||||
- Fetch the index page and capture initial session cookies
|
||||
- Detect the `leastFactor` math challenge in the response HTML
|
||||
- Parse obfuscated variables (`p`, `s`) and conditional bitwise operations
|
||||
- Compute the correct `KEY` cookie value
|
||||
- Verify the solved cookie against the index
|
||||
- Cache and reuse cookies for subsequent requests, auto-refresh on expiry
|
||||
|
||||
This approach runs in **< 1ms** compared to 5-10s with a headless browser, uses virtually no memory, and is fully **Docker-friendly** — no Chromium installation required.
|
||||
|
||||
The solver logic lives in [`src/utils/ph-solver.ts`](src/utils/ph-solver.ts). If Pornhub changes its obfuscation pattern, update the regex patterns in that file.
|
||||
|
||||
## Frequently asked questions
|
||||
**Q: The website response is slow**
|
||||
> That's unfortunate, this repository was opensource already, You can host and deploy Lustpress with your own instance. Any fixes and improvements will updating to this repo.
|
||||
@@ -311,14 +321,7 @@ To continue using Lustpress, please deploy and run your own self-hosted instance
|
||||
`en_US` • **/lʌstˈprɛs/** — "lust" stand for this project and "press" for express.
|
||||
|
||||
|
||||
## Client libraries
|
||||
Seamlessly integrate with the languages you love, simplified the usage, and intelisense definitions on your IDEs
|
||||
|
||||
- TBD
|
||||
- Or [create your own](https://github.com/sinkaroid/lustpress/edit/master/README.md)
|
||||
|
||||
|
||||
## Legal
|
||||
This tool can be freely copied, modified, altered, distributed without any attribution whatsoever. However, if you feel
|
||||
like this tool deserves an attribution, mention it. It won't hurt anybody.
|
||||
> Licence: WTF.
|
||||
> Licence: WTF.
|
||||
365
bun.lock
Normal file
365
bun.lock
Normal file
@@ -0,0 +1,365 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "lustpress",
|
||||
"dependencies": {
|
||||
"@elysiajs/cors": "^1.2.0",
|
||||
"@elysiajs/swagger": "^1.2.2",
|
||||
"@keyv/redis": "^5.1.6",
|
||||
"cheerio": "^1.2.0",
|
||||
"elysia": "^1.2.24",
|
||||
"keyv": "^5.6.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
||||
"@typescript-eslint/parser": "^8.24.1",
|
||||
"eslint": "^9.20.1",
|
||||
"typescript": "^6.0.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="],
|
||||
|
||||
"@elysiajs/cors": ["@elysiajs/cors@1.4.2", "", { "peerDependencies": { "elysia": ">= 1.4.0" } }, "sha512-FTCcbH35brTLigF1W7BYySRZomgI/dBEMK9BgK9RP9Nez7zmpGh4koL/Yr1BFv8nYz7CfhRvcM8d/c+XnwMaVQ=="],
|
||||
|
||||
"@elysiajs/swagger": ["@elysiajs/swagger@1.3.1", "", { "dependencies": { "@scalar/themes": "^0.9.52", "@scalar/types": "^0.0.12", "openapi-types": "^12.1.3", "pathe": "^1.1.2" }, "peerDependencies": { "elysia": ">= 1.3.0" } }, "sha512-LcbLHa0zE6FJKWPWKsIC/f+62wbDv3aXydqcNPVPyqNcaUgwvCajIi+5kHEU6GO3oXUCpzKaMsb3gsjt8sLzFQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
|
||||
|
||||
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
|
||||
|
||||
"@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
|
||||
|
||||
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
|
||||
|
||||
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
|
||||
|
||||
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.5", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg=="],
|
||||
|
||||
"@eslint/js": ["@eslint/js@9.39.4", "", {}, "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw=="],
|
||||
|
||||
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
|
||||
|
||||
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
|
||||
|
||||
"@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="],
|
||||
|
||||
"@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="],
|
||||
|
||||
"@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="],
|
||||
|
||||
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
|
||||
|
||||
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
|
||||
|
||||
"@keyv/redis": ["@keyv/redis@5.1.6", "", { "dependencies": { "@redis/client": "^5.10.0", "cluster-key-slot": "^1.1.2", "hookified": "^1.13.0" }, "peerDependencies": { "keyv": "^5.6.0" } }, "sha512-eKvW6pspvVaU5dxigaIDZr635/Uw6urTXL3gNbY9WTR8d3QigZQT+r8gxYSEOsw4+1cCBsC4s7T2ptR0WC9LfQ=="],
|
||||
|
||||
"@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="],
|
||||
|
||||
"@redis/client": ["@redis/client@5.12.1", "", { "dependencies": { "cluster-key-slot": "1.1.2" }, "peerDependencies": { "@node-rs/xxhash": "^1.1.0", "@opentelemetry/api": ">=1 <2" }, "optionalPeers": ["@node-rs/xxhash", "@opentelemetry/api"] }, "sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA=="],
|
||||
|
||||
"@scalar/openapi-types": ["@scalar/openapi-types@0.1.1", "", {}, "sha512-NMy3QNk6ytcCoPUGJH0t4NNr36OWXgZhA3ormr3TvhX1NDgoF95wFyodGVH8xiHeUyn2/FxtETm8UBLbB5xEmg=="],
|
||||
|
||||
"@scalar/themes": ["@scalar/themes@0.9.86", "", { "dependencies": { "@scalar/types": "0.1.7" } }, "sha512-QUHo9g5oSWi+0Lm1vJY9TaMZRau8LHg+vte7q5BVTBnu6NuQfigCaN+ouQ73FqIVd96TwMO6Db+dilK1B+9row=="],
|
||||
|
||||
"@scalar/types": ["@scalar/types@0.0.12", "", { "dependencies": { "@scalar/openapi-types": "0.1.1", "@unhead/schema": "^1.9.5" } }, "sha512-XYZ36lSEx87i4gDqopQlGCOkdIITHHEvgkuJFrXFATQs9zHARop0PN0g4RZYWj+ZpCUclOcaOjbCt8JGe22mnQ=="],
|
||||
|
||||
"@sinclair/typebox": ["@sinclair/typebox@0.34.49", "", {}, "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A=="],
|
||||
|
||||
"@tokenizer/inflate": ["@tokenizer/inflate@0.4.1", "", { "dependencies": { "debug": "^4.4.3", "token-types": "^6.1.1" } }, "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA=="],
|
||||
|
||||
"@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.7.0", "", { "dependencies": { "undici-types": "~7.21.0" } }, "sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg=="],
|
||||
|
||||
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.3", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/type-utils": "8.59.3", "@typescript-eslint/utils": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.3", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw=="],
|
||||
|
||||
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.3", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/types": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg=="],
|
||||
|
||||
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.3", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.3", "@typescript-eslint/types": "^8.59.3", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng=="],
|
||||
|
||||
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.3", "", { "dependencies": { "@typescript-eslint/types": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3" } }, "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA=="],
|
||||
|
||||
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.3", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw=="],
|
||||
|
||||
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.3", "", { "dependencies": { "@typescript-eslint/types": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3", "@typescript-eslint/utils": "8.59.3", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ=="],
|
||||
|
||||
"@typescript-eslint/types": ["@typescript-eslint/types@8.59.3", "", {}, "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.3", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.3", "@typescript-eslint/tsconfig-utils": "8.59.3", "@typescript-eslint/types": "8.59.3", "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg=="],
|
||||
|
||||
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.3", "@typescript-eslint/types": "8.59.3", "@typescript-eslint/typescript-estree": "8.59.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.3", "", { "dependencies": { "@typescript-eslint/types": "8.59.3", "eslint-visitor-keys": "^5.0.0" } }, "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg=="],
|
||||
|
||||
"@unhead/schema": ["@unhead/schema@1.11.20", "", { "dependencies": { "hookable": "^5.5.3", "zhead": "^2.2.4" } }, "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
|
||||
|
||||
"ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||
|
||||
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||
|
||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||
|
||||
"cheerio": ["cheerio@1.2.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.1.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.19.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg=="],
|
||||
|
||||
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
|
||||
|
||||
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||
|
||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||
|
||||
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||
|
||||
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
|
||||
|
||||
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
|
||||
|
||||
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||
|
||||
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"elysia": ["elysia@1.4.28", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="],
|
||||
|
||||
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
|
||||
|
||||
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
|
||||
|
||||
"eslint": ["eslint@9.39.4", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.5", "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
|
||||
|
||||
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
|
||||
|
||||
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
|
||||
|
||||
"esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"exact-mirror": ["exact-mirror@0.2.7", "", { "peerDependencies": { "@sinclair/typebox": "^0.34.15" }, "optionalPeers": ["@sinclair/typebox"] }, "sha512-+MeEmDcLA4o/vjK2zujgk+1VTxPR4hdp23qLqkWfStbECtAq9gmsvQa3LW6z/0GXZyHJobrCnmy1cdeE7BjsYg=="],
|
||||
|
||||
"fast-decode-uri-component": ["fast-decode-uri-component@1.0.1", "", {}, "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
|
||||
"file-type": ["file-type@22.0.1", "", { "dependencies": { "@tokenizer/inflate": "^0.4.1", "strtok3": "^10.3.5", "token-types": "^6.1.2", "uint8array-extras": "^1.5.0" } }, "sha512-ww5Mhre0EE+jmBvOXTmXAbEMuZE7uX4a3+oRCQFNj8w++g3ev913N6tXQz0XTXbueQ5TWQfm6BdaViEHHn8bhA=="],
|
||||
|
||||
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
|
||||
|
||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||
|
||||
"flatted": ["flatted@3.4.2", "", {}, "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA=="],
|
||||
|
||||
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
|
||||
|
||||
"globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
|
||||
"hookified": ["hookified@1.15.1", "", {}, "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg=="],
|
||||
|
||||
"htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
|
||||
|
||||
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||
|
||||
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
|
||||
|
||||
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
|
||||
|
||||
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
|
||||
|
||||
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
|
||||
|
||||
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
|
||||
|
||||
"keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="],
|
||||
|
||||
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
|
||||
|
||||
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
|
||||
|
||||
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
|
||||
|
||||
"memoirist": ["memoirist@0.4.0", "", {}, "sha512-zxTgA0mSYELa66DimuNQDvyLq36AwDlTuVRbnQtB+VuTcKWm5Qc4z3WkSpgsFWHNhexqkIooqpv4hdcqrX5Nmg=="],
|
||||
|
||||
"minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||
|
||||
"nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="],
|
||||
|
||||
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
|
||||
|
||||
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
|
||||
|
||||
"openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
|
||||
|
||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
|
||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||
|
||||
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
|
||||
|
||||
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
|
||||
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
|
||||
|
||||
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
|
||||
|
||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||
|
||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||
|
||||
"pathe": ["pathe@1.1.2", "", {}, "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||
|
||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||
|
||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||
|
||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"semver": ["semver@7.8.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA=="],
|
||||
|
||||
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||
|
||||
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||
|
||||
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
|
||||
|
||||
"strtok3": ["strtok3@10.3.5", "", { "dependencies": { "@tokenizer/token": "^0.3.0" } }, "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA=="],
|
||||
|
||||
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
|
||||
|
||||
"token-types": ["token-types@6.1.2", "", { "dependencies": { "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
||||
|
||||
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
|
||||
|
||||
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
|
||||
|
||||
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
|
||||
|
||||
"undici": ["undici@7.25.0", "", {}, "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ=="],
|
||||
|
||||
"undici-types": ["undici-types@7.21.0", "", {}, "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ=="],
|
||||
|
||||
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
|
||||
|
||||
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
|
||||
|
||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
||||
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
|
||||
|
||||
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||
|
||||
"zhead": ["zhead@2.2.4", "", {}, "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag=="],
|
||||
|
||||
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||
|
||||
"@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"@scalar/themes/@scalar/types": ["@scalar/types@0.1.7", "", { "dependencies": { "@scalar/openapi-types": "0.2.0", "@unhead/schema": "^1.11.11", "nanoid": "^5.1.5", "type-fest": "^4.20.0", "zod": "^3.23.8" } }, "sha512-irIDYzTQG2KLvFbuTI8k2Pz/R4JR+zUUSykVTbEMatkzMmVFnn1VzNSMlODbadycwZunbnL2tA27AXed9URVjw=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
|
||||
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
|
||||
|
||||
"eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
|
||||
|
||||
"flat-cache/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
|
||||
|
||||
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"@scalar/themes/@scalar/types/@scalar/openapi-types": ["@scalar/openapi-types@0.2.0", "", { "dependencies": { "zod": "^3.23.8" } }, "sha512-waiKk12cRCqyUCWTOX0K1WEVX46+hVUK+zRPzAahDJ7G0TApvbNkuy5wx7aoUyEk++HHde0XuQnshXnt8jsddA=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="],
|
||||
|
||||
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,17 @@ module.exports = [
|
||||
process: "readonly",
|
||||
Buffer: "readonly",
|
||||
console: "readonly",
|
||||
Bun: "readonly",
|
||||
fetch: "readonly",
|
||||
describe: "readonly",
|
||||
test: "readonly",
|
||||
expect: "readonly",
|
||||
beforeAll: "readonly",
|
||||
afterAll: "readonly",
|
||||
it: "readonly",
|
||||
AbortController: "readonly",
|
||||
setTimeout: "readonly",
|
||||
clearTimeout: "readonly",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
|
||||
39
fly.toml
39
fly.toml
@@ -1,39 +0,0 @@
|
||||
# fly.toml file generated for lust on 2023-04-18T12:01:37+07:00
|
||||
|
||||
app = "lust"
|
||||
kill_signal = "SIGINT"
|
||||
kill_timeout = 5
|
||||
processes = []
|
||||
|
||||
[env]
|
||||
|
||||
[experimental]
|
||||
auto_rollback = true
|
||||
|
||||
[[services]]
|
||||
autostart = true
|
||||
autostop = false
|
||||
http_checks = []
|
||||
internal_port = 3000
|
||||
processes = ["app"]
|
||||
protocol = "tcp"
|
||||
script_checks = []
|
||||
[services.concurrency]
|
||||
hard_limit = 25
|
||||
soft_limit = 20
|
||||
type = "connections"
|
||||
|
||||
[[services.ports]]
|
||||
force_https = true
|
||||
handlers = ["http"]
|
||||
port = 80
|
||||
|
||||
[[services.ports]]
|
||||
handlers = ["tls", "http"]
|
||||
port = 443
|
||||
|
||||
[[services.tcp_checks]]
|
||||
grace_period = "1s"
|
||||
interval = "15s"
|
||||
restart_limit = 0
|
||||
timeout = "2s"
|
||||
80
package.json
80
package.json
@@ -1,34 +1,24 @@
|
||||
{
|
||||
"name": "lustpress",
|
||||
"version": "8.1.2-alpha",
|
||||
"version": "8.3.6-alpha",
|
||||
"description": "RESTful and experimental API for PH and other R18 websites",
|
||||
"main": "build/src/index.js",
|
||||
"main": "src/index.ts",
|
||||
"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",
|
||||
"start:flyctl": "flyctl deploy",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"build:freshdoc": "rimraf docs",
|
||||
"build:apidoc": "npm run build:freshdoc && npx apidoc -i src -o docs",
|
||||
"test:pornhub": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^pornhub$ test/lustpress.test.ts\"",
|
||||
"test:xnxx": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^xnxx$ test/lustpress.test.ts\"",
|
||||
"test:redtube": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^redtube$ test/lustpress.test.ts\"",
|
||||
"test:xvideos": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^xvideos$ test/lustpress.test.ts\"",
|
||||
"test:xhamster": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^xhamster$ test/lustpress.test.ts\"",
|
||||
"test:youporn": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^youporn$ test/lustpress.test.ts\"",
|
||||
"test:eporner": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^eporner$ test/lustpress.test.ts\"",
|
||||
"test:txxx": "start-server-and-test \"npm run start:dev\" 3000 \"node -r ts-node/register --test --test-name-pattern=^txxx$ test/lustpress.test.ts\""
|
||||
},
|
||||
"apidoc": {
|
||||
"title": "Lustpress API Documentation",
|
||||
"url": "http://localhost:3000",
|
||||
"sampleUrl": "http://localhost:3000",
|
||||
"name": "Lustpress"
|
||||
"start:dev": "bun --watch src/index.ts",
|
||||
"build": "bun build ./src/index.ts --outdir ./build --target bun",
|
||||
"start": "bun ./build/index.js",
|
||||
"start:prod": "bun run build && bun ./build/index.js",
|
||||
"test": "bun test test/lustpress.test.ts",
|
||||
"test:pornhub": "bun test -t pornhub test/lustpress.test.ts",
|
||||
"test:xnxx": "bun test -t xnxx test/lustpress.test.ts",
|
||||
"test:redtube": "bun test -t redtube test/lustpress.test.ts",
|
||||
"test:xvideos": "bun test -t xvideos test/lustpress.test.ts",
|
||||
"test:xhamster": "bun test -t xhamster test/lustpress.test.ts",
|
||||
"test:youporn": "bun test -t youporn test/lustpress.test.ts",
|
||||
"test:eporner": "bun test -t eporner test/lustpress.test.ts",
|
||||
"test:txxx": "bun test -t txxx test/lustpress.test.ts",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "sinkaroid",
|
||||
@@ -38,34 +28,22 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@elysiajs/cors": "^1.2.0",
|
||||
"@elysiajs/swagger": "^1.2.2",
|
||||
"@keyv/redis": "^5.1.6",
|
||||
"cheerio": "^1.2.0",
|
||||
"cors": "^2.8.6",
|
||||
"dotenv": "^17.3.1",
|
||||
"express": "^5.2.1",
|
||||
"express-rate-limit": "^8.3.1",
|
||||
"express-slow-down": "^3.1.0",
|
||||
"keyv": "^5.6.0",
|
||||
"phin": "^3.7.1",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"playwright": "^1.58.2"
|
||||
"elysia": "^1.2.24",
|
||||
"keyv": "^5.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^5.0.6",
|
||||
"@types/node": "^25.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.57.0",
|
||||
"@typescript-eslint/parser": "^8.57.0",
|
||||
"apidoc": "^1.2.0",
|
||||
"eslint": "^10.0.3",
|
||||
"rimraf": "^6.1.3",
|
||||
"start-server-and-test": "^2.1.5",
|
||||
"ts-node": "^10.9.2",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "5.9.3"
|
||||
"@types/bun": "^1.3.13",
|
||||
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
||||
"@typescript-eslint/parser": "^8.24.1",
|
||||
"eslint": "^9.20.1",
|
||||
"typescript": "^6.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
}
|
||||
"bun": ">=1.3.13"
|
||||
},
|
||||
"packageManager": "bun@1.3.13"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 107 KiB |
BIN
resources/project/images/nun.png
Normal file
BIN
resources/project/images/nun.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
330
src/LustPress.ts
330
src/LustPress.ts
@@ -1,203 +1,144 @@
|
||||
import { URL } from "node:url";
|
||||
import { chromium } from "playwright";
|
||||
import p, { IResponse } from "phin";
|
||||
import Keyv from "keyv";
|
||||
import KeyvRedis from "@keyv/redis";
|
||||
import pkg from "../package.json";
|
||||
import { solveChallenge } from "./utils/ph-solver";
|
||||
|
||||
const keyv = process.env.REDIS_URL
|
||||
? new Keyv({ store: new KeyvRedis(process.env.REDIS_URL) })
|
||||
: new Keyv();
|
||||
|
||||
keyv.on("error", err => console.log("Connection Error", err));
|
||||
const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE);
|
||||
keyv.on("error", (err) => console.log("Connection Error", err));
|
||||
const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE || "1");
|
||||
|
||||
const GEO_TIMEOUT_MS = 3000;
|
||||
let cachedLastLocation: string | null = null;
|
||||
let lastLocationTimestamp = 0;
|
||||
|
||||
function cachedLocationOrUnknown(): string {
|
||||
if (cachedLastLocation && Date.now() - lastLocationTimestamp < 3600_000) {
|
||||
return cachedLastLocation;
|
||||
}
|
||||
return "Unknown, Unknown";
|
||||
}
|
||||
|
||||
class LustPress {
|
||||
|
||||
url: string;
|
||||
useragent: string;
|
||||
private browserUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36";
|
||||
private cookieCache: { [domain: string]: string } = {};
|
||||
|
||||
|
||||
constructor() {
|
||||
this.url = "";
|
||||
this.useragent = `${pkg.name}/${pkg.version} Node.js/${process.versions.node}`;
|
||||
this.browserUA =
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36";
|
||||
this.useragent = process.env.USER_AGENT || `${pkg.name}/${pkg.version} Bun/${Bun.version}`;
|
||||
}
|
||||
|
||||
async getCookies(url: string): Promise<string> {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
async getPornhubCookies(): Promise<string> {
|
||||
const baseUrl = "https://www.pornhub.com/";
|
||||
const headers = {
|
||||
"User-Agent": this.browserUA,
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Connection": "keep-alive",
|
||||
"Upgrade-Insecure-Requests": "1"
|
||||
};
|
||||
|
||||
const context = await browser.newContext({
|
||||
userAgent: process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/${process.versions.node}`
|
||||
});
|
||||
console.log("Fetching Pornhub Index for cookies...");
|
||||
let res = await fetch(baseUrl, { headers, redirect: "follow" });
|
||||
|
||||
// Get cookies from set-cookie headers
|
||||
const initialCookies = res.headers.getSetCookie().map(c => c.split(";")[0]).join("; ");
|
||||
let text = await res.text();
|
||||
|
||||
const page = await context.newPage();
|
||||
await page.goto(url, { waitUntil: "domcontentloaded" });
|
||||
await page.waitForLoadState("networkidle");
|
||||
const cookies = await context.cookies();
|
||||
await browser.close();
|
||||
return cookies.map(c => `${c.name}=${c.value}`).join("; ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 isPornhub = /pornhub\.com/i.test(url);
|
||||
|
||||
let cookieHeader = "";
|
||||
|
||||
if (isPornhub) {
|
||||
const domain = new URL(url).hostname;
|
||||
|
||||
if (this.cookieCache[domain]) {
|
||||
console.log("Using cached cookie");
|
||||
cookieHeader = this.cookieCache[domain];
|
||||
} else {
|
||||
console.log("Solving challenge via playwright");
|
||||
cookieHeader = await this.getCookies(url);
|
||||
this.cookieCache[domain] = cookieHeader;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Fetching from ${isPornhub ? "phin (cookie)" : "phin"}`);
|
||||
|
||||
const res = await p({
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/${process.versions.node}`,
|
||||
...(cookieHeader && { "Cookie": cookieHeader })
|
||||
},
|
||||
followRedirects: true
|
||||
if (text.includes("leastFactor")) {
|
||||
console.log("Pornhub Challenge detected at Index. Solving...");
|
||||
const challengeCookie = await solveChallenge(text);
|
||||
const combinedCookies = [initialCookies, challengeCookie, "age_verified=1"].filter(Boolean).join("; ");
|
||||
|
||||
// Verify cookies
|
||||
console.log("Verifying cookies at Index...");
|
||||
res = await fetch(baseUrl, {
|
||||
headers: { ...headers, Cookie: combinedCookies },
|
||||
redirect: "follow"
|
||||
});
|
||||
|
||||
if (isPornhub && res.statusCode !== 200) {
|
||||
const domain = new URL(url).hostname;
|
||||
console.log("Cookie invalid, clearing cache and retrying via playwright");
|
||||
delete this.cookieCache[domain];
|
||||
const newCookie = await this.getCookies(url);
|
||||
this.cookieCache[domain] = newCookie;
|
||||
const retry = await p({
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/${process.versions.node}`,
|
||||
"Cookie": newCookie
|
||||
},
|
||||
followRedirects: true
|
||||
});
|
||||
return retry.body;
|
||||
text = await res.text();
|
||||
|
||||
if (!text.includes("leastFactor")) {
|
||||
console.log("Pornhub cookies verified!");
|
||||
return combinedCookies;
|
||||
} else {
|
||||
console.log("Warning: Challenge still present after solve attempt.");
|
||||
return combinedCookies;
|
||||
}
|
||||
|
||||
return res.body;
|
||||
|
||||
} else {
|
||||
|
||||
console.log("Fetching from source");
|
||||
url = url.replace(/\/\//g, "/");
|
||||
const isPornhub = /pornhub\.com/i.test(url);
|
||||
|
||||
let cookieHeader = "";
|
||||
|
||||
if (isPornhub) {
|
||||
const domain = new URL(url).hostname;
|
||||
|
||||
if (this.cookieCache[domain]) {
|
||||
console.log("Using cached cookie");
|
||||
cookieHeader = this.cookieCache[domain];
|
||||
} else {
|
||||
console.log("Solving challenge via playwright");
|
||||
cookieHeader = await this.getCookies(url);
|
||||
this.cookieCache[domain] = cookieHeader;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Fetching from ${isPornhub ? "phin (cookie)" : "phin"}`);
|
||||
|
||||
const res = await p({
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/16.9.1`,
|
||||
...(cookieHeader && { "Cookie": cookieHeader })
|
||||
},
|
||||
followRedirects: true
|
||||
});
|
||||
|
||||
if (isPornhub && res.statusCode !== 200) {
|
||||
const domain = new URL(url).hostname;
|
||||
console.log("Cookie invalid, clearing cache and retrying via playwright");
|
||||
delete this.cookieCache[domain];
|
||||
const newCookie = await this.getCookies(url);
|
||||
this.cookieCache[domain] = newCookie;
|
||||
const retry = await p({
|
||||
url: url,
|
||||
headers: {
|
||||
"User-Agent": process.env.USER_AGENT || `${pkg.name}/${pkg.version} Node.js/16.9.1`,
|
||||
"Cookie": newCookie
|
||||
},
|
||||
followRedirects: true
|
||||
});
|
||||
return retry.body;
|
||||
}
|
||||
|
||||
await keyv.set(url, res.body, ttl);
|
||||
|
||||
return res.body;
|
||||
}
|
||||
|
||||
return initialCookies || "age_verified=1";
|
||||
}
|
||||
|
||||
/**
|
||||
* remove html tag and bunch of space
|
||||
* @param str string to remove html tag
|
||||
* @returns string
|
||||
*/
|
||||
async fetchBody(url: string): Promise<Buffer> {
|
||||
const cached = await keyv.get(url);
|
||||
if (cached) {
|
||||
console.log(`[CACHE HIT] ${url}`);
|
||||
return cached;
|
||||
}
|
||||
|
||||
const isPornhub = /pornhub\.com/i.test(url);
|
||||
const domain = new URL(url).hostname;
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"User-Agent": isPornhub ? this.browserUA : this.useragent
|
||||
};
|
||||
|
||||
if (isPornhub) {
|
||||
if (!this.cookieCache[domain]) {
|
||||
this.cookieCache[domain] = await this.getPornhubCookies();
|
||||
}
|
||||
headers["Cookie"] = this.cookieCache[domain];
|
||||
headers["Referer"] = "https://www.pornhub.com/";
|
||||
headers["Accept"] = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8";
|
||||
headers["Accept-Language"] = "en-US,en;q=0.9";
|
||||
}
|
||||
|
||||
console.log(`[FETCH] ${url}`);
|
||||
let res = await fetch(url, { headers, redirect: "follow" });
|
||||
let arrayBuf = await res.arrayBuffer();
|
||||
let body = Buffer.from(arrayBuf);
|
||||
|
||||
if (isPornhub && body.toString("utf8").includes("leastFactor")) {
|
||||
console.log("Challenge detected at target URL. Re-authenticating...");
|
||||
this.cookieCache[domain] = await this.getPornhubCookies();
|
||||
res = await fetch(url, {
|
||||
headers: { ...headers, Cookie: this.cookieCache[domain] },
|
||||
redirect: "follow"
|
||||
});
|
||||
arrayBuf = await res.arrayBuffer();
|
||||
body = Buffer.from(arrayBuf);
|
||||
}
|
||||
|
||||
if (res.ok && !url.includes("/random")) {
|
||||
await keyv.set(url, body, ttl);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
removeHtmlTag(str: string): string {
|
||||
str = str.replace(/(\r\n|\n|\r)/gm, "");
|
||||
str = str.replace(/\s+/g, "");
|
||||
return str;
|
||||
return str.replace(/(\r\n|\n|\r)/gm, "").replace(/\s+/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, "");
|
||||
str = str.replace(/\s+/g, " ");
|
||||
return str.trim();
|
||||
return str.replace(/(\r\n|\n|\r|\t)/gm, "").replace(/\\/g, "").replace(/\s+/g, " ").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 formatter = new Intl.RelativeTimeFormat("en");
|
||||
const ranges: { [key: string]: number } = {
|
||||
years: 3600 * 24 * 365,
|
||||
months: 3600 * 24 * 30,
|
||||
@@ -205,56 +146,73 @@ class LustPress {
|
||||
days: 3600 * 24,
|
||||
hours: 3600,
|
||||
minutes: 60,
|
||||
seconds: 1
|
||||
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);
|
||||
return formatter.format(Math.round(delta), key as Intl.RelativeTimeFormatUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`
|
||||
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}`;
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), GEO_TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
// ip-api free tier often rejects HTTPS requests with 403;
|
||||
const raw = await fetch("https://ipwho.is/", {
|
||||
signal: controller.signal,
|
||||
});
|
||||
if (!raw.ok) {
|
||||
return cachedLocationOrUnknown();
|
||||
}
|
||||
const data = await raw.json() as {
|
||||
success?: boolean;
|
||||
country?: string;
|
||||
region?: string;
|
||||
};
|
||||
if (data.success === false) {
|
||||
return cachedLocationOrUnknown();
|
||||
}
|
||||
const country = data.country?.trim();
|
||||
const region = data.region?.trim();
|
||||
if (!country || !region) {
|
||||
return cachedLocationOrUnknown();
|
||||
}
|
||||
|
||||
const location = `${country}, ${region}`;
|
||||
cachedLastLocation = location;
|
||||
lastLocationTimestamp = Date.now();
|
||||
return location;
|
||||
} catch {
|
||||
return cachedLocationOrUnknown();
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
if (!controller.signal.aborted) {
|
||||
controller.abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const lust = new LustPress();
|
||||
export default LustPress;
|
||||
|
||||
@@ -1,45 +1,9 @@
|
||||
import { scrapeContent } from "../../scraper/eporner/epornerGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export async function getEporner(req: Request, res: Response) {
|
||||
export async function getEporner({ query }: { query: { id: string } }) {
|
||||
try {
|
||||
const id = req.query.id as string;
|
||||
if (!id) throw Error("Parameter id is required");
|
||||
|
||||
/**
|
||||
* @api {get} /eporner/get?id=:id Get eporner
|
||||
* @apiName Get eporner
|
||||
* @apiGroup eporner
|
||||
* @apiDescription Get a eporner video based on id
|
||||
*
|
||||
* @apiParam {String} id Video ID
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* HTTP/1.1 400 Bad Request
|
||||
*
|
||||
* @apiExample {curl} curl
|
||||
* curl -i https://lust.scathach.id/eporner/get?id=ibvqvezXzcs
|
||||
*
|
||||
* @apiExample {js} JS/TS
|
||||
* import axios from "axios"
|
||||
*
|
||||
* axios.get("https://lust.scathach.id/eporner/get?id=ibvqvezXzcs")
|
||||
* .then(res => console.log(res.data))
|
||||
* .catch(err => console.error(err))
|
||||
*
|
||||
* @apiExample {python} Python
|
||||
* import aiohttp
|
||||
* async with aiohttp.ClientSession() as session:
|
||||
* async with session.get("https://lust.scathach.id/eporner/get?id=ibvqvezXzcs") as resp:
|
||||
* print(await resp.json())
|
||||
*/
|
||||
// https://www.eporner.com/video-ibvqvezXzcs/ivy-seduces-her-dad-s-friend/
|
||||
// https://www.eporner.com/hd-porn/hgovoiPexQe/Risa-Tsukino/
|
||||
|
||||
const { id } = query;
|
||||
let path: string;
|
||||
|
||||
if (id.startsWith("video-")) {
|
||||
@@ -51,16 +15,10 @@ export async function getEporner(req: Request, res: Response) {
|
||||
const url = `${c.EPORNER}/${path}`;
|
||||
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
return res.json(data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +1,15 @@
|
||||
import { scrapeContent } from "../../scraper/eporner/epornerGetRelatedController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export async function relatedEporner(req: Request, res: Response) {
|
||||
export async function relatedEporner({ query }: { query: { id: string } }) {
|
||||
try {
|
||||
const id = req.query.id as string;
|
||||
if (!id) throw Error("Parameter id is required");
|
||||
|
||||
const { id } = query;
|
||||
const url = `${c.EPORNER}/video-${id}`;
|
||||
const data = await scrapeContent(url);
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
|
||||
return res.json(data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,72 +1,32 @@
|
||||
import { scrapeContent as searchScrape } from "../../scraper/eporner/epornerSearchController";
|
||||
import { scrapeContent as videoScrape } from "../../scraper/eporner/epornerGetController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export async function randomEporner(req: Request, res: Response) {
|
||||
/**
|
||||
* @api {get} /eporner/random Get random eporner
|
||||
* @apiName Get random eporner
|
||||
* @apiGroup eporner
|
||||
* @apiDescription Get a random eporner video
|
||||
*
|
||||
* @apiParam {String} id Video ID
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* HTTP/1.1 400 Bad Request
|
||||
*
|
||||
* @apiExample {curl} curl
|
||||
* curl -i https://lust.scathach.id/eporner/random
|
||||
*
|
||||
* @apiExample {js} JS/TS
|
||||
* import axios from "axios"
|
||||
*
|
||||
* axios.get("https://lust.scathach.id/eporner/random")
|
||||
* .then(res => console.log(res.data))
|
||||
* .catch(err => console.error(err))
|
||||
*
|
||||
* @apiExample {python} Python
|
||||
* import aiohttp
|
||||
* async with aiohttp.ClientSession() as session:
|
||||
* async with session.get("https://lust.scathach.id/eporner/random") as resp:
|
||||
* print(await resp.json())
|
||||
*/
|
||||
|
||||
// cat/all/SORT-top-weekly/
|
||||
|
||||
try {
|
||||
const weeklyUrl = `${c.EPORNER}/cat/all/SORT-top-weekly/`;
|
||||
const list = await searchScrape(weeklyUrl);
|
||||
|
||||
if (!list.data.length) {
|
||||
throw new Error("No weekly top videos found");
|
||||
}
|
||||
|
||||
const random = list.data[Math.floor(Math.random() * list.data.length)];
|
||||
|
||||
let path: string;
|
||||
|
||||
if (random.id.startsWith("video-")) {
|
||||
path = random.id;
|
||||
} else {
|
||||
path = `hd-porn/${random.id}`;
|
||||
}
|
||||
|
||||
const url = `${c.EPORNER}/${path}`;
|
||||
const data = await videoScrape(url);
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
|
||||
return res.json(data);
|
||||
} catch (err) {
|
||||
res.status(400).json(maybeError(false, (err as Error).message));
|
||||
}
|
||||
}
|
||||
import { scrapeContent as searchScrape } from "../../scraper/eporner/epornerSearchController";
|
||||
import { scrapeContent as videoScrape } from "../../scraper/eporner/epornerGetController";
|
||||
import c from "../../utils/options";
|
||||
|
||||
export async function randomEporner() {
|
||||
try {
|
||||
const weeklyUrl = `${c.EPORNER}/cat/all/SORT-top-weekly/`;
|
||||
const list = await searchScrape(weeklyUrl);
|
||||
|
||||
if (!list.data.length) {
|
||||
throw new Error("No weekly top videos found");
|
||||
}
|
||||
|
||||
const random = list.data[Math.floor(Math.random() * list.data.length)];
|
||||
|
||||
let path: string;
|
||||
|
||||
if (random.id.startsWith("video-")) {
|
||||
path = random.id;
|
||||
} else {
|
||||
path = `hd-porn/${random.id}`;
|
||||
}
|
||||
|
||||
const url = `${c.EPORNER}/${path}`;
|
||||
const data = await videoScrape(url);
|
||||
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,29 @@
|
||||
import { scrapeContent } from "../../scraper/eporner/epornerSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { Request, Response } from "express";
|
||||
|
||||
export async function searchEporner(req: Request, res: Response) {
|
||||
try {
|
||||
/**
|
||||
* @api {get} /eporner/search Search eporner videos
|
||||
* @apiName Search eporner
|
||||
* @apiGroup eporner
|
||||
* @apiDescription Search eporner videos
|
||||
* @apiParam {String} key Keyword to search
|
||||
* @apiParam {Number} [page=1] Page number
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* HTTP/1.1 400 Bad Request
|
||||
*
|
||||
* @apiExample {curl} curl
|
||||
* curl -i https://lust.scathach.id/eporner/search?key=milf
|
||||
* curl -i https://lust.scathach.id/eporner/search?key=milf&page=2
|
||||
*
|
||||
* @apiExample {js} JS/TS
|
||||
* import axios from "axios"
|
||||
*
|
||||
* axios.get("https://lust.scathach.id/eporner/search?key=milf")
|
||||
* .then(res => console.log(res.data))
|
||||
* .catch(err => console.error(err))
|
||||
*
|
||||
* @apiExample {python} Python
|
||||
* import aiohttp
|
||||
* async with aiohttp.ClientSession() as session:
|
||||
* async with session.get("https://lust.scathach.id/eporner/search?key=milf") as resp:
|
||||
* print(await resp.json())
|
||||
*/
|
||||
|
||||
// https://www.eporner.com/tag/milf/
|
||||
|
||||
const key = req.query.key as string;
|
||||
const page = Number(req.query.page || 1);
|
||||
|
||||
if (!key) throw Error("Parameter key is required");
|
||||
if (isNaN(page)) throw Error("Parameter page must be a number");
|
||||
|
||||
const slug = key
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, "-");
|
||||
|
||||
const url =
|
||||
page === 1
|
||||
? `${c.EPORNER}/tag/${slug}/`
|
||||
: `${c.EPORNER}/tag/${slug}/${page}/`;
|
||||
|
||||
const data = await scrapeContent(url);
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent")
|
||||
});
|
||||
|
||||
return res.json(data);
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/eporner/epornerSearchController";
|
||||
import c from "../../utils/options";
|
||||
|
||||
export async function searchEporner({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key } = query;
|
||||
const page = Number(query.page || 1);
|
||||
|
||||
const slug = key
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/\s+/g, "-");
|
||||
|
||||
const url =
|
||||
page === 1
|
||||
? `${c.EPORNER}/tag/${slug}/`
|
||||
: `${c.EPORNER}/tag/${slug}/${page}/`;
|
||||
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function getPornhub({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function relatedPornhub({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,50 +1,14 @@
|
||||
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) {
|
||||
export async function randomPornhub() {
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +1,29 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/pornhub/pornhubSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { spacer } from "../../utils/modifier";
|
||||
|
||||
const sorting = ["mr", "mv", "tr", "lg"];
|
||||
|
||||
export async function searchPornhub({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string; sort?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key, sort } = query;
|
||||
const page = query.page || "1";
|
||||
|
||||
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}`;
|
||||
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,15 @@
|
||||
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) {
|
||||
export async function getRedtube({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,54 +1,14 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/redtube/redtubeSearchController";
|
||||
import c from "../../utils/options";
|
||||
|
||||
export async function relatedRedtube({ query }: { query: { id: string } }) {
|
||||
try {
|
||||
const { id } = query;
|
||||
const url = `${c.REDTUBE}/${id}`;
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,25 @@
|
||||
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";
|
||||
import { lust } from "../../LustPress";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function randomRedtube(req: Request, res: Response) {
|
||||
export async function randomRedtube() {
|
||||
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();
|
||||
})
|
||||
.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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,21 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/redtube/redtubeSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { spacer } from "../../utils/modifier";
|
||||
|
||||
export async function searchRedtube({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key } = query;
|
||||
const page = query.page || "1";
|
||||
|
||||
const url = `${c.REDTUBE}/?search=${spacer(key)}&page=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
import { Request, Response } from "express";
|
||||
import LustPress from "../../LustPress";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { IVideoData } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
import { lust } from "../../LustPress";
|
||||
import { IVideoData, TxxxResponse } from "../../interfaces";
|
||||
|
||||
// Generate sharded API URL exactly like TXXX expects
|
||||
function getApiUrl(videoId: string): string {
|
||||
@@ -17,15 +12,13 @@ function getApiUrl(videoId: string): string {
|
||||
return `https://txxx.com/api/json/video/86400/${million}/${thousand}/${id}.json`;
|
||||
}
|
||||
|
||||
export async function getTxxx(req: Request, res: Response) {
|
||||
export async function getTxxx({ query }: { query: { id: string } }) {
|
||||
try {
|
||||
const id = String(req.query.id || "").trim();
|
||||
if (!id) throw new Error("Parameter id is required");
|
||||
|
||||
const { id } = query;
|
||||
const apiUrl = getApiUrl(id);
|
||||
|
||||
const buffer = await lust.fetchBody(apiUrl);
|
||||
const parsed = JSON.parse(buffer.toString("utf-8"));
|
||||
const parsed = JSON.parse(buffer.toString("utf-8")) as TxxxResponse;
|
||||
|
||||
if (!parsed?.video) {
|
||||
throw new Error("Invalid API response");
|
||||
@@ -34,12 +27,12 @@ export async function getTxxx(req: Request, res: Response) {
|
||||
const video = parsed.video;
|
||||
|
||||
const categories = Object.values(video.categories || {}).map(
|
||||
(c: any) => c.title,
|
||||
(c) => c.title
|
||||
);
|
||||
|
||||
const tags = Object.values(video.tags || {}).map((t: any) => t.title);
|
||||
const tags = Object.values(video.tags || {}).map((t) => t.title);
|
||||
|
||||
const models = Object.values(video.models || {}).map((m: any) => m.title);
|
||||
const models = Object.values(video.models || {}).map((m) => m.title);
|
||||
|
||||
const videoDir = video.dir || "";
|
||||
const videoId = video.video_id;
|
||||
@@ -63,20 +56,14 @@ export async function getTxxx(req: Request, res: Response) {
|
||||
tags: [...categories, ...tags],
|
||||
},
|
||||
source: `https://txxx.com/videos/${videoId}/${videoDir}/`,
|
||||
assets: [embed, video.thumbsrc].filter(Boolean),
|
||||
assets: [embed, video.thumbsrc].filter(Boolean) as string[],
|
||||
};
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
|
||||
return res.json(response);
|
||||
return response;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
return res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { Request, Response } from "express";
|
||||
import LustPress from "../../LustPress";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { ISearchVideoData } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
import { lust } from "../../LustPress";
|
||||
import { ISearchVideoData, TxxxRelatedResponse } from "../../interfaces";
|
||||
|
||||
function getRelatedApiUrl(videoId: string, page = 1, count = 50): string {
|
||||
const id = Number(videoId);
|
||||
@@ -19,10 +14,14 @@ function getRelatedApiUrl(videoId: string, page = 1, count = 50): string {
|
||||
);
|
||||
}
|
||||
|
||||
export async function relatedTxxx(req: Request, res: Response) {
|
||||
export async function relatedTxxx({
|
||||
query,
|
||||
}: {
|
||||
query: { id: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const id = String(req.query.id || "").trim();
|
||||
const page = Number(req.query.page || 1);
|
||||
const { id } = query;
|
||||
const page = Number(query.page || 1);
|
||||
|
||||
if (!id) throw new Error("Parameter id is required");
|
||||
if (Number.isNaN(page)) throw new Error("Parameter page must be a number");
|
||||
@@ -30,14 +29,14 @@ export async function relatedTxxx(req: Request, res: Response) {
|
||||
const apiUrl = getRelatedApiUrl(id, page);
|
||||
|
||||
const buffer = await lust.fetchBody(apiUrl);
|
||||
const rawData = JSON.parse(buffer.toString("utf-8"));
|
||||
const rawData = JSON.parse(buffer.toString("utf-8")) as TxxxRelatedResponse;
|
||||
|
||||
const videos = Array.isArray(rawData.videos) ? rawData.videos : [];
|
||||
|
||||
const data = videos.map((v: any) => ({
|
||||
id: v.video_id,
|
||||
const data = videos.map((v) => ({
|
||||
id: String(v.video_id),
|
||||
title: v.title,
|
||||
image: v.scr || v.thumb || null,
|
||||
image: v.scr || v.thumb || "",
|
||||
duration: v.duration || "None",
|
||||
views: v.video_viewed || "0",
|
||||
rating: v.rating || "0",
|
||||
@@ -46,24 +45,16 @@ export async function relatedTxxx(req: Request, res: Response) {
|
||||
video: `https://txxx.com/embed/${v.video_id}/`,
|
||||
}));
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
|
||||
return res.json({
|
||||
return {
|
||||
success: true,
|
||||
total_count: rawData.total_count || "0",
|
||||
total_count: String(rawData.total_count || "0"),
|
||||
pages: rawData.pages || 1,
|
||||
page,
|
||||
data,
|
||||
source: `https://txxx.com/videos/${id}/`,
|
||||
} as ISearchVideoData);
|
||||
} as unknown as ISearchVideoData;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
return res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
import { Request, Response } from "express";
|
||||
import LustPress from "../../LustPress";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { lust } from "../../LustPress";
|
||||
import { TxxxSearchResponse as TxxxResponse } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function randomTxxx(req: Request, res: Response) {
|
||||
export async function randomTxxx() {
|
||||
try {
|
||||
const apiUrl =
|
||||
"https://txxx.com/api/json/videos2/14400/str/most-popular/60/..1.all..day.json";
|
||||
"https://txxx.com/api/json/videos2/14400/str/most-popular/60/..1.all..day.json";
|
||||
|
||||
const buffer = await lust.fetchBody(apiUrl);
|
||||
const rawData = JSON.parse(buffer.toString("utf-8"));
|
||||
const rawData = JSON.parse(buffer.toString("utf-8")) as TxxxResponse;
|
||||
|
||||
const videos = Array.isArray(rawData.videos)
|
||||
? rawData.videos
|
||||
: [];
|
||||
const videos = Array.isArray(rawData.videos) ? rawData.videos : [];
|
||||
|
||||
if (videos.length === 0) {
|
||||
throw new Error("No videos returned from upstream");
|
||||
@@ -39,20 +33,15 @@ export async function randomTxxx(req: Request, res: Response) {
|
||||
embed: `https://txxx.com/embed/${v.video_id}/`,
|
||||
};
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
|
||||
return res.json({
|
||||
return {
|
||||
success: true,
|
||||
data,
|
||||
source: apiUrl,
|
||||
});
|
||||
};
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
return res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,40 +1,27 @@
|
||||
import { Request, Response } from "express";
|
||||
import LustPress from "../../LustPress";
|
||||
import { maybeError } from "../../utils/modifier";
|
||||
import { lust } from "../../LustPress";
|
||||
import { TxxxSearchResponse } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function searchTxxx(req: Request, res: Response) {
|
||||
export async function searchTxxx({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const key = String(req.query.key || "").trim();
|
||||
const page = Number(req.query.page || 1);
|
||||
|
||||
if (!key) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: "Parameter key is required",
|
||||
});
|
||||
}
|
||||
|
||||
if (Number.isNaN(page)) {
|
||||
return res.json({
|
||||
success: false,
|
||||
error: "Parameter page must be a number",
|
||||
});
|
||||
}
|
||||
const { key } = query;
|
||||
const page = Number(query.page || 1);
|
||||
|
||||
const apiUrl =
|
||||
"https://txxx.com/api/videos2.php" +
|
||||
`?params=259200/str/relevance/60/search..${page}.all..` +
|
||||
`&s=${encodeURIComponent(key)}`;
|
||||
"https://txxx.com/api/videos2.php" +
|
||||
`?params=259200/str/relevance/60/search..${page}.all..` +
|
||||
`&s=${encodeURIComponent(key)}`;
|
||||
|
||||
// Fetch from API directly
|
||||
const buffer = await lust.fetchBody(apiUrl);
|
||||
const rawData = JSON.parse(buffer.toString("utf-8"));
|
||||
const rawData = JSON.parse(buffer.toString("utf-8")) as TxxxSearchResponse;
|
||||
|
||||
const videos = Array.isArray(rawData.videos) ? rawData.videos : [];
|
||||
|
||||
const data = videos.map((v: any) => ({
|
||||
const data = videos.map((v) => ({
|
||||
video_id: v.video_id,
|
||||
title: v.title,
|
||||
dir: v.dir,
|
||||
@@ -49,15 +36,17 @@ export async function searchTxxx(req: Request, res: Response) {
|
||||
embed: `https://txxx.com/embed/${v.video_id}/`,
|
||||
}));
|
||||
|
||||
return res.json({
|
||||
return {
|
||||
success: true,
|
||||
total_count: String(rawData.total_count ?? videos.length),
|
||||
pages: Number(rawData.pages ?? 1),
|
||||
page,
|
||||
data,
|
||||
});
|
||||
};
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
return res.json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function getXhamster({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
const url = `${c.XHAMSTER}/videos/${id}`;
|
||||
const data = await scrapeContent(url);
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
return res.json(data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function relatedXhamster({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,42 +1,10 @@
|
||||
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";
|
||||
import { lust } from "../../LustPress";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function randomXhamster(req: Request, res: Response) {
|
||||
export async function randomXhamster() {
|
||||
try {
|
||||
/**
|
||||
* @api {get} /xhamster/random Get random xhamster video
|
||||
* @apiName Get random xhamster
|
||||
* @apiGroup xhamster
|
||||
* @apiDescription Get a random xhamster video from the list of newest videos.
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* HTTP/1.1 400 Bad Request
|
||||
*
|
||||
* @apiExample {curl} curl
|
||||
* curl -i https://lust.scathach.id/xhamster/random
|
||||
*
|
||||
* @apiExample {js} JS/TS
|
||||
* import axios from "axios"
|
||||
*
|
||||
* axios.get("https://lust.scathach.id/xhamster/random")
|
||||
* .then(res => console.log(res.data))
|
||||
* .catch(err => console.error(err))
|
||||
*
|
||||
* @apiExample {python} Python
|
||||
* import aiohttp
|
||||
* async with aiohttp.ClientSession() as session:
|
||||
* async with session.get("https://lust.scathach.id/xhamster/random") as resp:
|
||||
* print(await resp.json())
|
||||
*/
|
||||
|
||||
const resolve = await lust.fetchBody(`${c.XHAMSTER}/newest`);
|
||||
const $ = load(resolve);
|
||||
|
||||
@@ -54,17 +22,10 @@ export async function randomXhamster(req: Request, res: Response) {
|
||||
|
||||
const data = await scrapeContent(randomUrl);
|
||||
|
||||
logger.info({
|
||||
path: req.path,
|
||||
query: req.query,
|
||||
method: req.method,
|
||||
ip: req.ip,
|
||||
useragent: req.get("User-Agent"),
|
||||
});
|
||||
|
||||
return res.json(data);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,21 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/xhamster/xhamsterSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { spacer } from "../../utils/modifier";
|
||||
|
||||
export async function searchXhamster({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key } = query;
|
||||
const page = query.page || "1";
|
||||
|
||||
const url = `${c.XHAMSTER}/search/${spacer(key)}?page=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function getXnxx({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function relatedXnxx({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +1,27 @@
|
||||
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 { lust } from "../../LustPress";
|
||||
import { load } from "cheerio";
|
||||
import LustPress from "../../LustPress";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function randomXnxx(req: Request, res: Response) {
|
||||
export async function randomXnxx() {
|
||||
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 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();
|
||||
})
|
||||
.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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,20 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/xnxx/xnxxSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { spacer } from "../../utils/modifier";
|
||||
|
||||
export async function searchXnxx({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key } = query;
|
||||
const page = query.page || "0";
|
||||
const url = `${c.XNXX}/search/${spacer(key)}/${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function getXvideos({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function relatedXvideos({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
import { load } from "cheerio";
|
||||
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";
|
||||
import { lust } from "../../LustPress";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function randomXvideos(req: Request, res: Response) {
|
||||
export async function randomXvideos() {
|
||||
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")
|
||||
@@ -45,19 +14,13 @@ export async function randomXvideos(req: Request, res: Response) {
|
||||
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 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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,58 +1,21 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/xvideos/xvideosSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { spacer } from "../../utils/modifier";
|
||||
|
||||
export async function searchXvideos({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key } = query;
|
||||
const page = query.page || "0";
|
||||
|
||||
const url = `${c.XVIDEOS}/?k=${spacer(key)}&p=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function getYouporn({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,55 +1,15 @@
|
||||
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) {
|
||||
export async function relatedYouporn({ query }: { query: { id: string } }) {
|
||||
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 { id } = query;
|
||||
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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +1,23 @@
|
||||
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";
|
||||
import { lust } from "../../LustPress";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function randomYouporn(req: Request, res: Response) {
|
||||
export async function randomYouporn() {
|
||||
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();
|
||||
})
|
||||
.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);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
res.status(400).json(maybeError(false, e.message));
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +1,21 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
import { scrapeContent } from "../../scraper/youporn/youpornSearchController";
|
||||
import c from "../../utils/options";
|
||||
import { spacer } from "../../utils/modifier";
|
||||
|
||||
export async function searchYouporn({
|
||||
query,
|
||||
}: {
|
||||
query: { key: string; page?: string };
|
||||
}) {
|
||||
try {
|
||||
const { key } = query;
|
||||
const page = query.page || "1";
|
||||
|
||||
const url = `${c.YOUPORN}/search/?query=${spacer(key)}&page=${page}`;
|
||||
const data = await scrapeContent(url);
|
||||
return data;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
103
src/index.ts
103
src/index.ts
@@ -1,52 +1,69 @@
|
||||
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";
|
||||
import { Elysia } from "elysia";
|
||||
import { cors } from "@elysiajs/cors";
|
||||
import { swagger } from "@elysiajs/swagger";
|
||||
import { lust } from "./LustPress";
|
||||
import { scrapeRoutes } from "./router/endpoint";
|
||||
import pkg from "../package.json";
|
||||
|
||||
const lust = new LustPress();
|
||||
const app = express();
|
||||
|
||||
|
||||
app.get("/", slow, limiter, async (req, res) => {
|
||||
res.send({
|
||||
const app = new Elysia()
|
||||
.use(cors())
|
||||
.use(
|
||||
swagger({
|
||||
path: "/docs",
|
||||
documentation: {
|
||||
info: {
|
||||
title: "Lustpress API",
|
||||
version: pkg.version,
|
||||
description: pkg.description,
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
.get("/", async () => ({
|
||||
success: true,
|
||||
playground: "https://sinkaroid.github.io/lustpress",
|
||||
endpoint: "https://github.com/sinkaroid/lustpress/blob/master/README.md#routing",
|
||||
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")
|
||||
});
|
||||
});
|
||||
version: pkg.version,
|
||||
}))
|
||||
.onError(({ code, error, set }) => {
|
||||
if (code === "NOT_FOUND") {
|
||||
set.status = 404;
|
||||
return {
|
||||
success: false,
|
||||
message: (error as Error).message || "Not Found",
|
||||
};
|
||||
}
|
||||
if (code === "VALIDATION") {
|
||||
set.status = 400;
|
||||
return {
|
||||
success: false,
|
||||
message: JSON.parse((error as Error).message)[0]?.message || "Validation Error",
|
||||
};
|
||||
}
|
||||
if (code === "UNKNOWN") {
|
||||
set.status = 400;
|
||||
return {
|
||||
success: false,
|
||||
message: (error as Error).message,
|
||||
};
|
||||
}
|
||||
set.status = 500;
|
||||
return {
|
||||
success: false,
|
||||
message: (error as Error).message || "Internal Server Error",
|
||||
stack: (error as Error).stack,
|
||||
};
|
||||
})
|
||||
.use(scrapeRoutes) // reuse legacy error handling
|
||||
.listen(process.env.PORT || 3000);
|
||||
|
||||
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")
|
||||
});
|
||||
});
|
||||
console.log(
|
||||
`Lustpress is running at ${app.server?.hostname}:${app.server?.port}`
|
||||
);
|
||||
|
||||
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}`));
|
||||
export type App = typeof app;
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
// Global response types
|
||||
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;
|
||||
channel?: string;
|
||||
models: string[];
|
||||
tags: string[];
|
||||
};
|
||||
source: string;
|
||||
assets: string[];
|
||||
success: boolean;
|
||||
data: {
|
||||
title: string;
|
||||
id: string;
|
||||
image?: string;
|
||||
duration: string;
|
||||
views: string;
|
||||
rating?: string;
|
||||
uploaded: string;
|
||||
upvoted: string | null;
|
||||
downvoted: string | null;
|
||||
channel?: string;
|
||||
models: string[];
|
||||
tags: string[];
|
||||
};
|
||||
source: string;
|
||||
assets: string[];
|
||||
}
|
||||
|
||||
export interface ISearchVideoData {
|
||||
success: boolean;
|
||||
data: string[];
|
||||
source: string;
|
||||
success: boolean;
|
||||
data: string[];
|
||||
source: string;
|
||||
}
|
||||
|
||||
export interface ISearchItem {
|
||||
@@ -36,8 +37,159 @@ export interface ISearchItem {
|
||||
video?: string;
|
||||
}
|
||||
|
||||
|
||||
export interface MaybeError {
|
||||
message: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
// Pornhub
|
||||
export interface PornhubSearchItem {
|
||||
link: string;
|
||||
id: string;
|
||||
title: string;
|
||||
image: string;
|
||||
duration: string;
|
||||
views: string;
|
||||
video: string;
|
||||
}
|
||||
|
||||
// Xvideos
|
||||
export interface XvideosSearchItem {
|
||||
link: string;
|
||||
id: string;
|
||||
image: string;
|
||||
title: string;
|
||||
duration: string;
|
||||
rating: string | null;
|
||||
video: string;
|
||||
}
|
||||
|
||||
export interface XvideosRelatedRaw {
|
||||
u: string;
|
||||
t: string;
|
||||
i: string;
|
||||
d: string;
|
||||
n: string;
|
||||
r: string;
|
||||
id: string | number;
|
||||
}
|
||||
|
||||
// Xnxx
|
||||
export interface XnxxRelatedRaw {
|
||||
u: string;
|
||||
t: string;
|
||||
i: string;
|
||||
d: string;
|
||||
n: string;
|
||||
r: string;
|
||||
id: string | number;
|
||||
}
|
||||
|
||||
// Xhamster
|
||||
export interface XhamsterSearchItem {
|
||||
link: string;
|
||||
id: string;
|
||||
title: string;
|
||||
image: string;
|
||||
duration: string;
|
||||
views: string;
|
||||
video: string;
|
||||
}
|
||||
|
||||
export interface XhamsterTag {
|
||||
isTag?: boolean;
|
||||
isPornstar?: boolean;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface XhamsterInitials {
|
||||
ratingComponent?: {
|
||||
ratingModel?: {
|
||||
value?: number;
|
||||
likes?: number;
|
||||
dislikes?: number;
|
||||
};
|
||||
};
|
||||
videoTagsComponent?: {
|
||||
tags?: XhamsterTag[];
|
||||
};
|
||||
}
|
||||
|
||||
// Redtube
|
||||
export interface RedTubeSearchItem {
|
||||
link: string;
|
||||
id: string;
|
||||
title: string;
|
||||
image: string;
|
||||
duration: string;
|
||||
views: string;
|
||||
video: string;
|
||||
}
|
||||
|
||||
// Txxx
|
||||
export interface TxxxCategory {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface TxxxVideo {
|
||||
title?: string;
|
||||
video_id: string | number;
|
||||
thumbsrc?: string;
|
||||
thumb?: string;
|
||||
duration?: string;
|
||||
statistics?: {
|
||||
viewed?: string;
|
||||
rating?: string;
|
||||
likes?: number;
|
||||
dislikes?: number;
|
||||
};
|
||||
post_date?: string;
|
||||
channel?: { title?: string };
|
||||
categories?: Record<string, TxxxCategory>;
|
||||
tags?: Record<string, TxxxCategory>;
|
||||
models?: Record<string, TxxxCategory>;
|
||||
models_suggested?: string[];
|
||||
dir?: string;
|
||||
}
|
||||
|
||||
export interface TxxxResponse {
|
||||
video?: TxxxVideo;
|
||||
}
|
||||
|
||||
export interface TxxxSearchVideo {
|
||||
video_id: string | number;
|
||||
title: string;
|
||||
dir?: string;
|
||||
duration?: string;
|
||||
video_viewed?: string;
|
||||
rating?: string;
|
||||
post_date?: string;
|
||||
likes?: number;
|
||||
dislikes?: number;
|
||||
scr?: string;
|
||||
categories?: string;
|
||||
}
|
||||
|
||||
export interface TxxxSearchResponse {
|
||||
videos?: TxxxSearchVideo[];
|
||||
total_count?: string | number;
|
||||
pages?: number;
|
||||
}
|
||||
|
||||
export interface TxxxRelatedVideo {
|
||||
video_id: string | number;
|
||||
title: string;
|
||||
scr?: string;
|
||||
thumb?: string;
|
||||
duration?: string;
|
||||
video_viewed?: string;
|
||||
rating?: string;
|
||||
username?: string;
|
||||
display_name?: string;
|
||||
dir?: string;
|
||||
}
|
||||
|
||||
export interface TxxxRelatedResponse {
|
||||
videos?: TxxxRelatedVideo[];
|
||||
total_count?: string | number;
|
||||
pages?: number;
|
||||
}
|
||||
|
||||
@@ -1,93 +1,207 @@
|
||||
import cors from "cors";
|
||||
import { Router } from "express";
|
||||
import { slow, limiter } from "../utils/limit-options";
|
||||
|
||||
|
||||
// EPorner
|
||||
import { getEporner } from "../controller/eporner/epornerGet";
|
||||
import { searchEporner } from "../controller/eporner/epornerSearch";
|
||||
import { relatedEporner } from "../controller/eporner/epornerGetRelated";
|
||||
import { randomEporner } from "../controller/eporner/epornerRandom";
|
||||
|
||||
// TXXX
|
||||
import { getTxxx } from "../controller/txxx/txxxGet";
|
||||
import { searchTxxx } from "../controller/txxx/txxxSearch";
|
||||
import { relatedTxxx } from "../controller/txxx/txxxGetRelated";
|
||||
import { randomTxxx } from "../controller/txxx/txxxRandom";
|
||||
|
||||
// PornHub
|
||||
import { getPornhub } from "../controller/pornhub/pornhubGet";
|
||||
import { searchPornhub } from "../controller/pornhub/pornhubSearch";
|
||||
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);
|
||||
router.get("/eporner/get", cors(), slow, limiter, getEporner);
|
||||
router.get("/eporner/search", cors(), slow, limiter, searchEporner);
|
||||
router.get("/eporner/related", cors(), slow, limiter, relatedEporner);
|
||||
router.get("/eporner/random", cors(), slow, limiter, randomEporner);
|
||||
router.get("/txxx/get", cors(), slow, limiter, getTxxx);
|
||||
router.get("/txxx/search", cors(), slow, limiter, searchTxxx);
|
||||
router.get("/txxx/related", cors(), slow, limiter, relatedTxxx);
|
||||
router.get("/txxx/random", cors(), slow, limiter, randomTxxx);
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
export default scrapeRoutes;
|
||||
import { Elysia, t } from "elysia";
|
||||
|
||||
// EPorner
|
||||
import { getEporner } from "../controller/eporner/epornerGet";
|
||||
import { searchEporner } from "../controller/eporner/epornerSearch";
|
||||
import { relatedEporner } from "../controller/eporner/epornerGetRelated";
|
||||
import { randomEporner } from "../controller/eporner/epornerRandom";
|
||||
|
||||
// TXXX
|
||||
import { getTxxx } from "../controller/txxx/txxxGet";
|
||||
import { searchTxxx } from "../controller/txxx/txxxSearch";
|
||||
import { relatedTxxx } from "../controller/txxx/txxxGetRelated";
|
||||
import { randomTxxx } from "../controller/txxx/txxxRandom";
|
||||
|
||||
// PornHub
|
||||
import { getPornhub } from "../controller/pornhub/pornhubGet";
|
||||
import { searchPornhub } from "../controller/pornhub/pornhubSearch";
|
||||
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";
|
||||
|
||||
const queryId = {
|
||||
query: t.Object({ id: t.String() }),
|
||||
};
|
||||
|
||||
const querySearch = {
|
||||
query: t.Object({
|
||||
key: t.String(),
|
||||
page: t.Optional(t.String()),
|
||||
}),
|
||||
};
|
||||
|
||||
export const scrapeRoutes = (app: Elysia) =>
|
||||
app
|
||||
.group("/pornhub", (app) =>
|
||||
app
|
||||
.get("/get", getPornhub, {
|
||||
...queryId,
|
||||
detail: { summary: "Get Pornhub video data", tags: ["Pornhub"] },
|
||||
})
|
||||
.get("/search", searchPornhub, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search Pornhub videos", tags: ["Pornhub"] },
|
||||
})
|
||||
.get("/random", randomPornhub, {
|
||||
detail: { summary: "Get random Pornhub videos", tags: ["Pornhub"] },
|
||||
})
|
||||
.get("/related", relatedPornhub, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related Pornhub videos", tags: ["Pornhub"] },
|
||||
})
|
||||
)
|
||||
.group("/xnxx", (app) =>
|
||||
app
|
||||
.get("/get", getXnxx, {
|
||||
...queryId,
|
||||
detail: { summary: "Get XNXX video data", tags: ["XNXX"] },
|
||||
})
|
||||
.get("/search", searchXnxx, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search XNXX videos", tags: ["XNXX"] },
|
||||
})
|
||||
.get("/related", relatedXnxx, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related XNXX videos", tags: ["XNXX"] },
|
||||
})
|
||||
.get("/random", randomXnxx, {
|
||||
detail: { summary: "Get random XNXX videos", tags: ["XNXX"] },
|
||||
})
|
||||
)
|
||||
.group("/redtube", (app) =>
|
||||
app
|
||||
.get("/get", getRedtube, {
|
||||
...queryId,
|
||||
detail: { summary: "Get RedTube video data", tags: ["RedTube"] },
|
||||
})
|
||||
.get("/search", searchRedtube, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search RedTube videos", tags: ["RedTube"] },
|
||||
})
|
||||
.get("/related", relatedRedtube, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related RedTube videos", tags: ["RedTube"] },
|
||||
})
|
||||
.get("/random", randomRedtube, {
|
||||
detail: { summary: "Get random RedTube videos", tags: ["RedTube"] },
|
||||
})
|
||||
)
|
||||
.group("/xvideos", (app) =>
|
||||
app
|
||||
.get("/get", getXvideos, {
|
||||
...queryId,
|
||||
detail: { summary: "Get Xvideos video data", tags: ["Xvideos"] },
|
||||
})
|
||||
.get("/search", searchXvideos, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search Xvideos videos", tags: ["Xvideos"] },
|
||||
})
|
||||
.get("/random", randomXvideos, {
|
||||
detail: { summary: "Get random Xvideos videos", tags: ["Xvideos"] },
|
||||
})
|
||||
.get("/related", relatedXvideos, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related Xvideos videos", tags: ["Xvideos"] },
|
||||
})
|
||||
)
|
||||
.group("/xhamster", (app) =>
|
||||
app
|
||||
.get("/get", getXhamster, {
|
||||
...queryId,
|
||||
detail: { summary: "Get Xhamster video data", tags: ["Xhamster"] },
|
||||
})
|
||||
.get("/search", searchXhamster, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search Xhamster videos", tags: ["Xhamster"] },
|
||||
})
|
||||
.get("/random", randomXhamster, {
|
||||
detail: { summary: "Get random Xhamster videos", tags: ["Xhamster"] },
|
||||
})
|
||||
.get("/related", relatedXhamster, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related Xhamster videos", tags: ["Xhamster"] },
|
||||
})
|
||||
)
|
||||
.group("/youporn", (app) =>
|
||||
app
|
||||
.get("/get", getYouporn, {
|
||||
...queryId,
|
||||
detail: { summary: "Get YouPorn video data", tags: ["YouPorn"] },
|
||||
})
|
||||
.get("/search", searchYouporn, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search YouPorn videos", tags: ["YouPorn"] },
|
||||
})
|
||||
.get("/related", relatedYouporn, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related YouPorn videos", tags: ["YouPorn"] },
|
||||
})
|
||||
.get("/random", randomYouporn, {
|
||||
detail: { summary: "Get random YouPorn videos", tags: ["YouPorn"] },
|
||||
})
|
||||
)
|
||||
.group("/eporner", (app) =>
|
||||
app
|
||||
.get("/get", getEporner, {
|
||||
...queryId,
|
||||
detail: { summary: "Get EPorner video data", tags: ["EPorner"] },
|
||||
})
|
||||
.get("/search", searchEporner, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search EPorner videos", tags: ["EPorner"] },
|
||||
})
|
||||
.get("/related", relatedEporner, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related EPorner videos", tags: ["EPorner"] },
|
||||
})
|
||||
.get("/random", randomEporner, {
|
||||
detail: { summary: "Get random EPorner videos", tags: ["EPorner"] },
|
||||
})
|
||||
)
|
||||
.group("/txxx", (app) =>
|
||||
app
|
||||
.get("/get", getTxxx, {
|
||||
...queryId,
|
||||
detail: { summary: "Get TXXX video data", tags: ["TXXX"] },
|
||||
})
|
||||
.get("/search", searchTxxx, {
|
||||
...querySearch,
|
||||
detail: { summary: "Search TXXX videos", tags: ["TXXX"] },
|
||||
})
|
||||
.get("/related", relatedTxxx, {
|
||||
...queryId,
|
||||
detail: { summary: "Get related TXXX videos", tags: ["TXXX"] },
|
||||
})
|
||||
.get("/random", randomTxxx, {
|
||||
detail: { summary: "Get random TXXX videos", tags: ["TXXX"] },
|
||||
})
|
||||
);
|
||||
@@ -1,9 +1,7 @@
|
||||
import { load } from "cheerio";
|
||||
import LustPress from "../../LustPress";
|
||||
import { lust } from "../../LustPress";
|
||||
import { IVideoData } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
function calculateRatingFromStrings(
|
||||
upVote: string,
|
||||
downVote: string
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { load } from "cheerio";
|
||||
import LustPress from "../../LustPress";
|
||||
import { lust } from "../../LustPress";
|
||||
import { ISearchVideoData } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
const html = await lust.fetchBody(url);
|
||||
const $ = load(html);
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { load } from "cheerio";
|
||||
import LustPress from "../../LustPress";
|
||||
import { lust } from "../../LustPress";
|
||||
import { IVideoData } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const resolve = await lust.fetchBody(url);
|
||||
|
||||
@@ -1,59 +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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
import { load } from "cheerio";
|
||||
import { lust } from "../../LustPress";
|
||||
import c from "../../utils/options";
|
||||
import { ISearchVideoData, PornhubSearchItem } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await lust.fetchBody(url);
|
||||
const $ = load(res);
|
||||
|
||||
class PornhubSearch {
|
||||
search: PornhubSearchItem[];
|
||||
data: PornhubSearchItem[];
|
||||
constructor() {
|
||||
this.search = $("div.wrap")
|
||||
.map((i, el) => {
|
||||
const link = $(el).find("a").attr("href") || "";
|
||||
const id = link.split("=")[1] || "None";
|
||||
const title = $(el).find("a").attr("title") || "None";
|
||||
const image = $(el).find("img").attr("src") || "None";
|
||||
const duration = $(el).find("var.duration").text() || "None";
|
||||
const views = $(el).find("div.videoDetailsBlock").find("span.views").text() || "None";
|
||||
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) => {
|
||||
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 result: ISearchVideoData = {
|
||||
success: true,
|
||||
data: ph.data as unknown as string[],
|
||||
source: url,
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,72 +1,63 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
import { load } from "cheerio";
|
||||
import { lust } from "../../LustPress";
|
||||
import c from "../../utils/options";
|
||||
import { ISearchVideoData, RedTubeSearchItem } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await lust.fetchBody(url);
|
||||
const $ = load(res);
|
||||
|
||||
class RedTubeSearch {
|
||||
views: string[];
|
||||
search: RedTubeSearchItem[];
|
||||
data: RedTubeSearchItem[];
|
||||
constructor() {
|
||||
this.views = $("span.video_count")
|
||||
.map((i, el) => {
|
||||
return $(el).text();
|
||||
}).get();
|
||||
this.search = $("a.video_link")
|
||||
.map((i, el) => {
|
||||
const link = $(el).attr("href") || "";
|
||||
const id = link.split("/")[1] || "None";
|
||||
const title = $(el).find("img").attr("alt") || "None";
|
||||
const image = $(el).find("img").attr("data-src") || "None";
|
||||
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] || "None",
|
||||
video: `https://embed.redtube.com/?id=${id}`,
|
||||
};
|
||||
}).get();
|
||||
|
||||
this.data = this.search.filter((el) => {
|
||||
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 result: ISearchVideoData = {
|
||||
success: true,
|
||||
data: red.data as unknown as string[],
|
||||
source: url,
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
import { load } from "cheerio";
|
||||
import LustPress from "../../LustPress";
|
||||
import { IVideoData } from "../../interfaces";
|
||||
|
||||
const lust = new LustPress();
|
||||
import { lust } from "../../LustPress";
|
||||
import { IVideoData, XhamsterInitials } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
@@ -10,11 +8,11 @@ export async function scrapeContent(url: string) {
|
||||
const $ = load(buffer.toString("utf8"));
|
||||
|
||||
const raw = $("#initials-script").html();
|
||||
const initials = raw
|
||||
const initials = (raw
|
||||
? JSON.parse(
|
||||
raw.replace(/^window\.initials\s*=\s*/, "").replace(/;$/, ""),
|
||||
)
|
||||
: null;
|
||||
: null) as XhamsterInitials | null;
|
||||
|
||||
class Xhamster {
|
||||
link: string;
|
||||
@@ -71,13 +69,13 @@ export async function scrapeContent(url: string) {
|
||||
|
||||
this.tags =
|
||||
initials?.videoTagsComponent?.tags
|
||||
?.filter((t: any) => t.isTag)
|
||||
.map((t: any) => t.name) || [];
|
||||
?.filter((t) => t.isTag)
|
||||
.map((t) => t.name) || [];
|
||||
|
||||
this.models =
|
||||
initials?.videoTagsComponent?.tags
|
||||
?.filter((t: any) => t.isPornstar)
|
||||
.map((t: any) => t.name) || [];
|
||||
?.filter((t) => t.isPornstar)
|
||||
.map((t) => t.name) || [];
|
||||
|
||||
const embedId = this.link.split("-").pop()?.replace("/", "");
|
||||
this.video = embedId ? `https://xhamster.com/embed/${embedId}` : "None";
|
||||
@@ -109,3 +107,4 @@ export async function scrapeContent(url: string) {
|
||||
throw new Error(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +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);
|
||||
|
||||
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("/")[4],
|
||||
title: $(el).find("img").attr("alt"),
|
||||
image: $(el).find("img").attr("src"),
|
||||
duration: duration[i],
|
||||
views: views[i],
|
||||
video: `${c.XHAMSTER}/embed/${link?.split("-").pop()}`,
|
||||
};
|
||||
})
|
||||
.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);
|
||||
}
|
||||
}
|
||||
import { load } from "cheerio";
|
||||
import { lust } from "../../LustPress";
|
||||
import c from "../../utils/options";
|
||||
import { ISearchVideoData, XhamsterSearchItem } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await lust.fetchBody(url);
|
||||
const $ = load(res);
|
||||
|
||||
class XhamsterSearch {
|
||||
search: XhamsterSearchItem[];
|
||||
constructor() {
|
||||
const views = $("div.video-thumb-views")
|
||||
.map((i, el) => {
|
||||
return $(el).text();
|
||||
})
|
||||
.get();
|
||||
const duration = $("span[data-role='video-duration']")
|
||||
.map((i, el) => {
|
||||
return $(el).text();
|
||||
})
|
||||
.get();
|
||||
this.search = $("a.video-thumb__image-container")
|
||||
.map((i, el) => {
|
||||
const link = $(el).attr("href") || "";
|
||||
|
||||
return {
|
||||
link: link,
|
||||
id: link.split("/")[4] || "None",
|
||||
title: $(el).find("img").attr("alt") || "None",
|
||||
image: $(el).find("img").attr("src") || "None",
|
||||
duration: duration[i] || "None",
|
||||
views: views[i] || "None",
|
||||
video: `${c.XHAMSTER}/embed/${link.split("-").pop()}`,
|
||||
};
|
||||
})
|
||||
.get();
|
||||
}
|
||||
}
|
||||
|
||||
const xh = new XhamsterSearch();
|
||||
if (xh.search.length === 0) throw Error("No result found");
|
||||
const result: ISearchVideoData = {
|
||||
success: true,
|
||||
data: xh.search as unknown as string[],
|
||||
source: url,
|
||||
};
|
||||
return result;
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,56 +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[];
|
||||
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));
|
||||
//console.log(actualResult);
|
||||
const result = actualResult.map((el: any) => {
|
||||
return {
|
||||
link: `${c.XNXX}${el.u}`,
|
||||
id: el.u.slice(1, -1),
|
||||
title: el.t,
|
||||
image: el.i,
|
||||
duration: el.d,
|
||||
views: `${el.n}, ${el.r}`,
|
||||
video: `${c.XNXX}/embedframe/${el.id}`
|
||||
};
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
import { load } from "cheerio";
|
||||
import { lust } from "../../LustPress";
|
||||
import c from "../../utils/options";
|
||||
import { ISearchVideoData, XnxxRelatedRaw } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await lust.fetchBody(url);
|
||||
const $ = load(res);
|
||||
|
||||
class XnxxSearch {
|
||||
search: 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)) as XnxxRelatedRaw[];
|
||||
const result = actualResult.map((rel) => {
|
||||
return {
|
||||
link: `${c.XNXX}${rel.u}`,
|
||||
id: rel.u.slice(1, -1),
|
||||
title: rel.t,
|
||||
image: rel.i,
|
||||
duration: rel.d,
|
||||
views: `${rel.n}, ${rel.r}`,
|
||||
video: `${c.XNXX}/embedframe/${rel.id}`
|
||||
};
|
||||
});
|
||||
return result;
|
||||
}).get();
|
||||
}
|
||||
}
|
||||
|
||||
const x = new XnxxSearch();
|
||||
if (x.search.length === 0) throw Error("No result found");
|
||||
const result: ISearchVideoData = {
|
||||
success: true,
|
||||
data: x.search as unknown as string[],
|
||||
source: url,
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,53 +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 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.slice(1, -1),
|
||||
title: el.t,
|
||||
image: el.i,
|
||||
duration: el.d,
|
||||
views: `${el.n}, ${el.r}`,
|
||||
video: `${c.XVIDEOS}/embedframe/${el.id}`
|
||||
};
|
||||
});
|
||||
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);
|
||||
}
|
||||
}
|
||||
import { load } from "cheerio";
|
||||
import { lust } from "../../LustPress";
|
||||
import c from "../../utils/options";
|
||||
import { ISearchVideoData, XvideosRelatedRaw } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await lust.fetchBody(url);
|
||||
const $ = load(res);
|
||||
|
||||
class XvideosSearch {
|
||||
search: 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)) as XvideosRelatedRaw[];
|
||||
const result = actualResult.map((rel) => {
|
||||
return {
|
||||
link: `${c.XVIDEOS}${rel.u}`,
|
||||
id: rel.u.slice(1, -1),
|
||||
title: rel.t,
|
||||
image: rel.i,
|
||||
duration: rel.d,
|
||||
views: `${rel.n}, ${rel.r}`,
|
||||
video: `${c.XVIDEOS}/embedframe/${rel.id}`
|
||||
};
|
||||
});
|
||||
return result;
|
||||
}).get();
|
||||
}
|
||||
}
|
||||
|
||||
const x = new XvideosSearch();
|
||||
if (x.search.length === 0) throw Error("No result found");
|
||||
const result: ISearchVideoData = {
|
||||
success: true,
|
||||
data: x.search as unknown as string[],
|
||||
source: url,
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +1,64 @@
|
||||
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 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: `${c.XVIDEOS}/embedframe/${$(el).find("img").attr("data-videoid")}`
|
||||
};
|
||||
}).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);
|
||||
}
|
||||
}
|
||||
import { load } from "cheerio";
|
||||
import { lust } from "../../LustPress";
|
||||
import c from "../../utils/options";
|
||||
import { ISearchVideoData, XvideosSearchItem } from "../../interfaces";
|
||||
|
||||
export async function scrapeContent(url: string) {
|
||||
try {
|
||||
const res = await lust.fetchBody(url);
|
||||
const $ = load(res);
|
||||
|
||||
class XvideosSearch {
|
||||
search: XvideosSearchItem[];
|
||||
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) => {
|
||||
const href = $(el).find("a").attr("href") || "None";
|
||||
const videoId = $(el).find("img").attr("data-videoid");
|
||||
return {
|
||||
link: `${c.XVIDEOS}${href}`,
|
||||
id: href,
|
||||
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: `${c.XVIDEOS}/embedframe/${videoId}`
|
||||
};
|
||||
}).get();
|
||||
|
||||
this.search = this.search.filter((el) => {
|
||||
return !el.id.includes("THUMBNUM");
|
||||
});
|
||||
this.search = this.search.filter((el) => {
|
||||
return el.id.includes("/video");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const xv = new XvideosSearch();
|
||||
if (xv.search.length === 0) throw Error("No result found");
|
||||
const result: ISearchVideoData = {
|
||||
success: true,
|
||||
data: xv.search as unknown as string[],
|
||||
source: url,
|
||||
};
|
||||
return result;
|
||||
|
||||
} catch (err) {
|
||||
const e = err as Error;
|
||||
throw Error(e.message);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { load } from "cheerio";
|
||||
import LustPress from "../../LustPress";
|
||||
import { lust } 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);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
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 };
|
||||
@@ -1,8 +0,0 @@
|
||||
import pino from "pino";
|
||||
|
||||
export const logger = pino({
|
||||
level: "info",
|
||||
transport: {
|
||||
target: "pino-pretty"
|
||||
},
|
||||
});
|
||||
@@ -19,7 +19,7 @@ export function maybeError(success: boolean, message: string) {
|
||||
|
||||
export function timeAgo(input: Date) {
|
||||
const date = new Date(input);
|
||||
const formatter: any = new Intl.RelativeTimeFormat("en");
|
||||
const formatter = new Intl.RelativeTimeFormat("en");
|
||||
const ranges: { [key: string]: number } = {
|
||||
years: 3600 * 24 * 365,
|
||||
months: 3600 * 24 * 30,
|
||||
@@ -33,7 +33,7 @@ export function timeAgo(input: Date) {
|
||||
for (const key in ranges) {
|
||||
if (ranges[key] < Math.abs(secondsElapsed)) {
|
||||
const delta = secondsElapsed / ranges[key];
|
||||
return formatter.format(Math.round(delta), key);
|
||||
return formatter.format(Math.round(delta), key as Intl.RelativeTimeFormatUnit);
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/utils/ph-solver.ts
Normal file
83
src/utils/ph-solver.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* solve Pornhub JS challenge (leastFactor)
|
||||
* @param html challenge page html
|
||||
* @returns KEY cookie string
|
||||
*/
|
||||
export async function solveChallenge(html: string): Promise<string> {
|
||||
const cleanHtml = html.replace(/\/\*[\s\S]*?\*\//g, "");
|
||||
|
||||
const pMatch = cleanHtml.match(/var p=(\d+);/);
|
||||
const sMatch = cleanHtml.match(/var s=(\d+);/);
|
||||
const extraMatch = cleanHtml.match(
|
||||
/document\.cookie="KEY="\+n\+"\*"\+p\/n\+":"\+s\+":(\d+):1;path=\/;";/
|
||||
);
|
||||
|
||||
if (!pMatch || !sMatch || !extraMatch) {
|
||||
throw new Error("Challenge variables not found");
|
||||
}
|
||||
|
||||
let p = parseInt(pMatch[1]);
|
||||
const s = parseInt(sMatch[1]);
|
||||
const extra = extraMatch[1];
|
||||
|
||||
const goFuncMatch = cleanHtml.match(
|
||||
/function go\(\) \{([\s\S]+?)n=leastFactor\(p\);/
|
||||
);
|
||||
if (goFuncMatch) {
|
||||
const body = goFuncMatch[1].replace(/\s+/g, "");
|
||||
|
||||
// 1. Find and process all if-else blocks, then REMOVE them from body
|
||||
const ifElseRegex = /if\(\(s>>(\d+)\)&1\)p(\+|-)=(\d+)\*(\d+);elsep(\+|-)=(\d+)\*(\d+);/g;
|
||||
let match;
|
||||
let bodyWithoutIfs = body;
|
||||
|
||||
while ((match = ifElseRegex.exec(body)) !== null) {
|
||||
const shift = parseInt(match[1]);
|
||||
const condition = (s >> shift) & 1;
|
||||
if (condition) {
|
||||
const sign = match[2];
|
||||
const val = parseInt(match[3]) * parseInt(match[4]);
|
||||
p = sign === "+" ? p + val : p - val;
|
||||
} else {
|
||||
const sign = match[5];
|
||||
const val = parseInt(match[6]) * parseInt(match[7]);
|
||||
p = sign === "+" ? p + val : p - val;
|
||||
}
|
||||
// Remove the matched if-else block to avoid double counting
|
||||
bodyWithoutIfs = bodyWithoutIfs.replace(match[0], "");
|
||||
}
|
||||
|
||||
// 2. Now process remaining adjustments (p+=... or p-=...) in the cleaned body
|
||||
const adjRegex = /p(\+|-)=(\d+);/g;
|
||||
let adjMatch;
|
||||
while ((adjMatch = adjRegex.exec(bodyWithoutIfs)) !== null) {
|
||||
const sign = adjMatch[1];
|
||||
const val = parseInt(adjMatch[2]);
|
||||
p = sign === "+" ? p + val : p - val;
|
||||
}
|
||||
}
|
||||
|
||||
const n = leastFactor(p);
|
||||
return `KEY=${n}*${p / n}:${s}:${extra}:1`;
|
||||
}
|
||||
|
||||
function leastFactor(n: number): number {
|
||||
if (isNaN(n) || !isFinite(n)) return NaN;
|
||||
if (n === 0) return 0;
|
||||
if (n % 1 || n * n < 2) return 1;
|
||||
if (n % 2 === 0) return 2;
|
||||
if (n % 3 === 0) return 3;
|
||||
if (n % 5 === 0) return 5;
|
||||
const m = Math.sqrt(n);
|
||||
for (let i = 7; i <= m; i += 30) {
|
||||
if (n % i === 0) return i;
|
||||
if (n % (i + 4) === 0) return i + 4;
|
||||
if (n % (i + 6) === 0) return i + 6;
|
||||
if (n % (i + 10) === 0) return i + 10;
|
||||
if (n % (i + 12) === 0) return i + 12;
|
||||
if (n % (i + 16) === 0) return i + 16;
|
||||
if (n % (i + 22) === 0) return i + 22;
|
||||
if (n % (i + 24) === 0) return i + 24;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import test from "node:test";
|
||||
import { test } from "bun:test";
|
||||
import assert from "node:assert/strict";
|
||||
import p from "phin";
|
||||
|
||||
const port = process.env.PORT ?? 3000;
|
||||
|
||||
@@ -12,15 +11,11 @@ type ApiResponse = {
|
||||
};
|
||||
|
||||
async function run(path: string) {
|
||||
const res = await p({
|
||||
url: `http://localhost:${port}${path}`,
|
||||
parse: "json",
|
||||
timeout: 20000
|
||||
});
|
||||
const res = await fetch(`http://localhost:${port}${path}`);
|
||||
|
||||
assert.equal(res.statusCode, 200);
|
||||
assert.equal(res.status, 200);
|
||||
|
||||
const json = res.body as ApiResponse;
|
||||
const json = (await res.json()) as ApiResponse;
|
||||
|
||||
console.log(JSON.stringify(json, null, 2));
|
||||
|
||||
@@ -31,32 +26,32 @@ async function run(path: string) {
|
||||
|
||||
test("pornhub", async () => {
|
||||
await run("/pornhub/random");
|
||||
});
|
||||
}, 300000);
|
||||
|
||||
test("xnxx", async () => {
|
||||
await run("/xnxx/random");
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
test("redtube", async () => {
|
||||
await run("/redtube/random");
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
test("xvideos", async () => {
|
||||
await run("/xvideos/random");
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
test("xhamster", async () => {
|
||||
await run("/xhamster/random");
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
test("youporn", async () => {
|
||||
await run("/youporn/random");
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
test("eporner", async () => {
|
||||
await run("/eporner/random");
|
||||
});
|
||||
}, 120000);
|
||||
|
||||
test("txxx", async () => {
|
||||
await run("/txxx/random");
|
||||
});
|
||||
}, 120000);
|
||||
@@ -1,25 +1,24 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "./build",
|
||||
"allowJs": true,
|
||||
"lib": ["ESNext"],
|
||||
"module": "ESNext",
|
||||
"target": "ESNext",
|
||||
"baseUrl": "src",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "node",
|
||||
"module": "commonjs",
|
||||
"paths": {},
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"inlineSourceMap": true,
|
||||
"downlevelIteration": true,
|
||||
"newLine": "lf",
|
||||
"moduleResolution": "bundler",
|
||||
"moduleDetection": "force",
|
||||
"allowJs": true,
|
||||
"allowImportingTsExtensions": true,
|
||||
"noEmit": true,
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"declaration": true,
|
||||
"skipLibCheck": true
|
||||
"downlevelIteration": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowUnreachableCode": false,
|
||||
"baseUrl": "src",
|
||||
"paths": {}
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
|
||||
Reference in New Issue
Block a user