From cd32689b475f6e1f1937685e2ad90fafffc82f70 Mon Sep 17 00:00:00 2001 From: "Indrawan I." Date: Thu, 14 May 2026 04:57:54 +0700 Subject: [PATCH] feat(core): hard remake with bun and elysia (#19) * static files changes * remake modifiers * test case * some docs changes * deps and rule changes * edit proper interfaces * remake getServer * @elysiajs/swagger * remake tsconfig * ci changes use bun * docs changes * feat(core): hard remake with bun and elysia * chore(release): 8.2.0-alpha * auto release * ci * update global * chore(release): 8.2.1-alpha * ci --- .github/workflows/build.yml | 16 +- .github/workflows/docker.yml | 2 +- .github/workflows/eporner.yml | 23 +- .github/workflows/playground.yml | 37 -- .github/workflows/pornhub.yml | 23 +- .github/workflows/redtube.yml | 23 +- .github/workflows/release.yml | 56 +++ .github/workflows/status.yml | 17 +- .github/workflows/txxx.yml | 23 +- .github/workflows/xhamster.yml | 23 +- .github/workflows/xnxx.yml | 23 +- .github/workflows/xvideos.yml | 23 +- .github/workflows/youporn.yml | 23 +- .gitignore | 3 +- CLOSING_REMARKS.md | 2 +- Dockerfile | 27 +- README.md | 75 ++-- bun.lock | 365 ++++++++++++++++++ eslint.config.js | 11 + fly.toml | 39 -- package.json | 80 ++-- resources/project/images/bnnuy.png | Bin 109198 -> 0 bytes resources/project/images/nun.png | Bin 0 -> 34730 bytes src/LustPress.ts | 319 +++++++-------- src/controller/eporner/epornerGet.ts | 52 +-- src/controller/eporner/epornerGetRelated.ts | 23 +- src/controller/eporner/epornerRandom.ts | 104 ++--- src/controller/eporner/epornerSearch.ts | 101 ++--- src/controller/pornhub/pornhubGet.ts | 50 +-- src/controller/pornhub/pornhubGetRelated.ts | 50 +-- src/controller/pornhub/pornhubRandom.ts | 44 +-- src/controller/pornhub/pornhubSearch.ts | 95 ++--- src/controller/redtube/redtubeGet.ts | 51 +-- src/controller/redtube/redtubeGetRelated.ts | 68 +--- src/controller/redtube/redtubeRandom.ts | 56 +-- src/controller/redtube/redtubeSearch.ts | 79 +--- src/controller/txxx/txxxGet.ts | 39 +- src/controller/txxx/txxxGetRelated.ts | 43 +-- src/controller/txxx/txxxRandom.ts | 33 +- src/controller/txxx/txxxSearch.ts | 49 +-- src/controller/xhamster/xhamsterGet.ts | 50 +-- src/controller/xhamster/xhamsterGetRelated.ts | 50 +-- src/controller/xhamster/xhamsterRandom.ts | 49 +-- src/controller/xhamster/xhamsterSearch.ts | 79 +--- src/controller/xnxx/xnxxGet.ts | 50 +-- src/controller/xnxx/xnxxGetRelated.ts | 50 +-- src/controller/xnxx/xnxxRandom.ts | 58 +-- src/controller/xnxx/xnxxSearch.ts | 78 +--- src/controller/xvideos/xvideosGet.ts | 50 +-- src/controller/xvideos/xvideosGetRelated.ts | 50 +-- src/controller/xvideos/xvideosRandom.ts | 53 +-- src/controller/xvideos/xvideosSearch.ts | 79 +--- src/controller/youporn/youpornGet.ts | 50 +-- src/controller/youporn/youpornGetRelated.ts | 50 +-- src/controller/youporn/youpornRandom.ts | 51 +-- src/controller/youporn/youpornSearch.ts | 79 +--- src/index.ts | 108 +++--- src/interfaces.ts | 196 ++++++++-- src/router/endpoint.ts | 300 +++++++++----- src/scraper/eporner/epornerGetController.ts | 4 +- .../eporner/epornerGetRelatedController.ts | 4 +- src/scraper/pornhub/pornhubGetController.ts | 4 +- .../pornhub/pornhubSearchController.ts | 113 +++--- .../redtube/redtubeSearchController.ts | 135 +++---- src/scraper/xhamster/xhamsterGetController.ts | 19 +- .../xhamster/xhamsterSearchController.ts | 113 +++--- src/scraper/xnxx/xnxxGetRelatedController.ts | 105 +++-- .../xvideos/xvideosGetRelatedController.ts | 102 +++-- .../xvideos/xvideosSearchController.ts | 129 +++---- .../youporn/youpornSearchController.ts | 4 +- src/utils/limit-options.ts | 17 - src/utils/logger.ts | 8 - src/utils/modifier.ts | 4 +- src/utils/ph-solver.ts | 83 ++++ test/lustpress.test.ts | 29 +- tsconfig.json | 33 +- 76 files changed, 2025 insertions(+), 2581 deletions(-) delete mode 100644 .github/workflows/playground.yml create mode 100644 .github/workflows/release.yml create mode 100644 bun.lock delete mode 100644 fly.toml delete mode 100644 resources/project/images/bnnuy.png create mode 100644 resources/project/images/nun.png delete mode 100644 src/utils/limit-options.ts delete mode 100644 src/utils/logger.ts create mode 100644 src/utils/ph-solver.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 758651d..3daeb29 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 \ No newline at end of file + run: bun run build \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4ec9583..39539f8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -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 diff --git a/.github/workflows/eporner.yml b/.github/workflows/eporner.yml index d6e7370..f7ebf39 100644 --- a/.github/workflows/eporner.yml +++ b/.github/workflows/eporner.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:eporner \ No newline at end of file diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml deleted file mode 100644 index fb24b69..0000000 --- a/.github/workflows/playground.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Playground - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -permissions: - contents: write - -jobs: - build-and-deploy: - concurrency: ci-${{ github.ref }} - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 - with: - persist-credentials: false - - - name: Setup Node - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f - with: - node-version: 24 - - - name: Install dependencies - run: npm install - - - name: Generating docs - run: npm run build:apidoc - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@9d877eea73427180ae43cf98e8914934fe157a1a - with: - branch: gh-pages - folder: docs \ No newline at end of file diff --git a/.github/workflows/pornhub.yml b/.github/workflows/pornhub.yml index c5a10f4..30eebc8 100644 --- a/.github/workflows/pornhub.yml +++ b/.github/workflows/pornhub.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:pornhub \ No newline at end of file diff --git a/.github/workflows/redtube.yml b/.github/workflows/redtube.yml index 6d092c4..ee46d4f 100644 --- a/.github/workflows/redtube.yml +++ b/.github/workflows/redtube.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:redtube \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fb003ed --- /dev/null +++ b/.github/workflows/release.yml @@ -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." \ No newline at end of file diff --git a/.github/workflows/status.yml b/.github/workflows/status.yml index 7451d86..e90bd82 100644 --- a/.github/workflows/status.yml +++ b/.github/workflows/status.yml @@ -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 \ No newline at end of file + run: | + bun run start:dev & + sleep 3 + bun run test \ No newline at end of file diff --git a/.github/workflows/txxx.yml b/.github/workflows/txxx.yml index d628a09..ec61ae1 100644 --- a/.github/workflows/txxx.yml +++ b/.github/workflows/txxx.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:txxx \ No newline at end of file diff --git a/.github/workflows/xhamster.yml b/.github/workflows/xhamster.yml index 053c580..5226e8a 100644 --- a/.github/workflows/xhamster.yml +++ b/.github/workflows/xhamster.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:xhamster \ No newline at end of file diff --git a/.github/workflows/xnxx.yml b/.github/workflows/xnxx.yml index 55d56a9..2b42f2b 100644 --- a/.github/workflows/xnxx.yml +++ b/.github/workflows/xnxx.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:xnxx \ No newline at end of file diff --git a/.github/workflows/xvideos.yml b/.github/workflows/xvideos.yml index 5a675b7..3e77f14 100644 --- a/.github/workflows/xvideos.yml +++ b/.github/workflows/xvideos.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:xvideos \ No newline at end of file diff --git a/.github/workflows/youporn.yml b/.github/workflows/youporn.yml index 47391fb..4c4f3fb 100644 --- a/.github/workflows/youporn.yml +++ b/.github/workflows/youporn.yml @@ -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 \ No newline at end of file + - name: Run test + run: | + bun run start:dev & + sleep 3 + bun run test:youporn \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9dba34d..dc96a2b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ p.ts .env .idea CHANGELOG.md -theme.zip \ No newline at end of file +theme.zip +AGENTS.md \ No newline at end of file diff --git a/CLOSING_REMARKS.md b/CLOSING_REMARKS.md index 6705f39..52b2d17 100644 --- a/CLOSING_REMARKS.md +++ b/CLOSING_REMARKS.md @@ -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) diff --git a/Dockerfile b/Dockerfile index b565095..6a1d7fc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,17 +1,26 @@ -FROM node:22 - +FROM oven/bun:1.3.13-alpine AS base WORKDIR /srv/app -COPY package*.json ./ -RUN npm install - -# install browser + system deps -RUN npx playwright install --with-deps chromium +# 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 bun run build -RUN npm 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"] \ No newline at end of file +CMD ["bun", "run", "start"] \ No newline at end of file diff --git a/README.md b/README.md index 62d6317..4a42993 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@

-Lustpress stand for Lust and **Express**, rebuild from [Jandapress](https://github.com/sinkaroid/jandapress) with completely different approach. +Lustpress was originally named Lust and Express (legacy name) and now runs on **Bun** + **Elysia** a high-performance remake from the ground up using Bun. 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. Playground • @@ -17,7 +17,7 @@ The motivation of this project is to bring you an actionable data related to por --- - + - [Lustpress](#) - [The problem](#the-problem) @@ -32,11 +32,10 @@ The motivation of this project is to bring you an actionable data related to por - [Playground](https://sinkaroid.github.io/lustpress) - [Routing](#playground) - [Status response](#status-response) + - [Pornhub JS 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 - +
NOTE: NodeJS 22.x or higherNOTE: Bun 1.3.13 or higher
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 `` 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.0-alpha Bun/1.3.13" ``` ### Docker @@ -118,7 +114,7 @@ docker run -d \ -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.0-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 +## Running 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. \ No newline at end of file diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..78ea4a1 --- /dev/null +++ b/bun.lock @@ -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=="], + } +} diff --git a/eslint.config.js b/eslint.config.js index c3394fa..49a3242 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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: { diff --git a/fly.toml b/fly.toml deleted file mode 100644 index db24634..0000000 --- a/fly.toml +++ /dev/null @@ -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" diff --git a/package.json b/package.json index eb651a8..47e769b 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,24 @@ { "name": "lustpress", - "version": "8.1.3-alpha", + "version": "8.2.1-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" } \ No newline at end of file diff --git a/resources/project/images/bnnuy.png b/resources/project/images/bnnuy.png deleted file mode 100644 index 96a0184e8c7a12484f8b9d89e64ab028489f5bde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109198 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

GE)5TQ0b$4&{r3G;s2z{50j5_nMx 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 H^Y{M;WfzAo literal 0 HcmV?d00001 diff --git a/src/LustPress.ts b/src/LustPress.ts index 857c2b9..d38cc84 100644 --- a/src/LustPress.ts +++ b/src/LustPress.ts @@ -1,203 +1,135 @@ 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)); +keyv.on("error", (err) => console.log("Connection Error", err)); const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE); -class LustPress { +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/121.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.useragent = `${pkg.name}/${pkg.version} Bun/${Bun.version}`; } - async getCookies(url: string): Promise { - const browser = await chromium.launch({ headless: true }); + async getPornhubCookies(): Promise { + 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 { - - 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 { + const cached = await keyv.get(url); + if (cached) return cached; + + const isPornhub = /pornhub\.com/i.test(url); + const domain = new URL(url).hostname; + + const headers: Record = { + "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"; + } + + let res = await fetch(url, { headers, redirect: "follow" }); + let text = await res.text(); + + if (isPornhub && text.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" + }); + text = await res.text(); + } + + const body = Buffer.from(text); + if (!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 +137,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 > - */ async getServer(): Promise { - 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; diff --git a/src/controller/eporner/epornerGet.ts b/src/controller/eporner/epornerGet.ts index 6f78be9..414c399 100644 --- a/src/controller/eporner/epornerGet.ts +++ b/src/controller/eporner/epornerGet.ts @@ -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); } } + diff --git a/src/controller/eporner/epornerGetRelated.ts b/src/controller/eporner/epornerGetRelated.ts index b9ee25d..12b6621 100644 --- a/src/controller/eporner/epornerGetRelated.ts +++ b/src/controller/eporner/epornerGetRelated.ts @@ -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); } } + diff --git a/src/controller/eporner/epornerRandom.ts b/src/controller/eporner/epornerRandom.ts index 77bb927..80523be 100644 --- a/src/controller/eporner/epornerRandom.ts +++ b/src/controller/eporner/epornerRandom.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/eporner/epornerSearch.ts b/src/controller/eporner/epornerSearch.ts index 131a3b2..698e880 100644 --- a/src/controller/eporner/epornerSearch.ts +++ b/src/controller/eporner/epornerSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/pornhub/pornhubGet.ts b/src/controller/pornhub/pornhubGet.ts index f1546f5..17c7914 100644 --- a/src/controller/pornhub/pornhubGet.ts +++ b/src/controller/pornhub/pornhubGet.ts @@ -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); } } + diff --git a/src/controller/pornhub/pornhubGetRelated.ts b/src/controller/pornhub/pornhubGetRelated.ts index 92e9aac..59196d0 100644 --- a/src/controller/pornhub/pornhubGetRelated.ts +++ b/src/controller/pornhub/pornhubGetRelated.ts @@ -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); } } + diff --git a/src/controller/pornhub/pornhubRandom.ts b/src/controller/pornhub/pornhubRandom.ts index 2c6ef99..bffac30 100644 --- a/src/controller/pornhub/pornhubRandom.ts +++ b/src/controller/pornhub/pornhubRandom.ts @@ -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); } } + diff --git a/src/controller/pornhub/pornhubSearch.ts b/src/controller/pornhub/pornhubSearch.ts index a706428..8bf060b 100644 --- a/src/controller/pornhub/pornhubSearch.ts +++ b/src/controller/pornhub/pornhubSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/redtube/redtubeGet.ts b/src/controller/redtube/redtubeGet.ts index 61d47db..2b8e4c4 100644 --- a/src/controller/redtube/redtubeGet.ts +++ b/src/controller/redtube/redtubeGet.ts @@ -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); } } + diff --git a/src/controller/redtube/redtubeGetRelated.ts b/src/controller/redtube/redtubeGetRelated.ts index 8865a75..8a31697 100644 --- a/src/controller/redtube/redtubeGetRelated.ts +++ b/src/controller/redtube/redtubeGetRelated.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/redtube/redtubeRandom.ts b/src/controller/redtube/redtubeRandom.ts index 3dd6257..3517fe0 100644 --- a/src/controller/redtube/redtubeRandom.ts +++ b/src/controller/redtube/redtubeRandom.ts @@ -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); } } + diff --git a/src/controller/redtube/redtubeSearch.ts b/src/controller/redtube/redtubeSearch.ts index afaf7ca..a96f1a7 100644 --- a/src/controller/redtube/redtubeSearch.ts +++ b/src/controller/redtube/redtubeSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/txxx/txxxGet.ts b/src/controller/txxx/txxxGet.ts index 134d810..dbdf3f1 100644 --- a/src/controller/txxx/txxxGet.ts +++ b/src/controller/txxx/txxxGet.ts @@ -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); } } + + diff --git a/src/controller/txxx/txxxGetRelated.ts b/src/controller/txxx/txxxGetRelated.ts index a5fc00e..8f2e171 100644 --- a/src/controller/txxx/txxxGetRelated.ts +++ b/src/controller/txxx/txxxGetRelated.ts @@ -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); } } diff --git a/src/controller/txxx/txxxRandom.ts b/src/controller/txxx/txxxRandom.ts index 6c339b9..3c77493 100644 --- a/src/controller/txxx/txxxRandom.ts +++ b/src/controller/txxx/txxxRandom.ts @@ -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); } } + + diff --git a/src/controller/txxx/txxxSearch.ts b/src/controller/txxx/txxxSearch.ts index 6b4e8a9..746ecac 100644 --- a/src/controller/txxx/txxxSearch.ts +++ b/src/controller/txxx/txxxSearch.ts @@ -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); } } + + diff --git a/src/controller/xhamster/xhamsterGet.ts b/src/controller/xhamster/xhamsterGet.ts index a9159ec..a8a28a4 100644 --- a/src/controller/xhamster/xhamsterGet.ts +++ b/src/controller/xhamster/xhamsterGet.ts @@ -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); } } + diff --git a/src/controller/xhamster/xhamsterGetRelated.ts b/src/controller/xhamster/xhamsterGetRelated.ts index 7f88e44..c93d821 100644 --- a/src/controller/xhamster/xhamsterGetRelated.ts +++ b/src/controller/xhamster/xhamsterGetRelated.ts @@ -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); } } + diff --git a/src/controller/xhamster/xhamsterRandom.ts b/src/controller/xhamster/xhamsterRandom.ts index f72d324..7059d66 100644 --- a/src/controller/xhamster/xhamsterRandom.ts +++ b/src/controller/xhamster/xhamsterRandom.ts @@ -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); } } + diff --git a/src/controller/xhamster/xhamsterSearch.ts b/src/controller/xhamster/xhamsterSearch.ts index 5590546..b1469bd 100644 --- a/src/controller/xhamster/xhamsterSearch.ts +++ b/src/controller/xhamster/xhamsterSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/xnxx/xnxxGet.ts b/src/controller/xnxx/xnxxGet.ts index fa0a798..577a79c 100644 --- a/src/controller/xnxx/xnxxGet.ts +++ b/src/controller/xnxx/xnxxGet.ts @@ -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); } } + diff --git a/src/controller/xnxx/xnxxGetRelated.ts b/src/controller/xnxx/xnxxGetRelated.ts index 4fc270b..21aa170 100644 --- a/src/controller/xnxx/xnxxGetRelated.ts +++ b/src/controller/xnxx/xnxxGetRelated.ts @@ -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); } } + diff --git a/src/controller/xnxx/xnxxRandom.ts b/src/controller/xnxx/xnxxRandom.ts index 74a06cb..82976ea 100644 --- a/src/controller/xnxx/xnxxRandom.ts +++ b/src/controller/xnxx/xnxxRandom.ts @@ -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); } } + diff --git a/src/controller/xnxx/xnxxSearch.ts b/src/controller/xnxx/xnxxSearch.ts index 49416f1..423eeb5 100644 --- a/src/controller/xnxx/xnxxSearch.ts +++ b/src/controller/xnxx/xnxxSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/xvideos/xvideosGet.ts b/src/controller/xvideos/xvideosGet.ts index 80688b4..867a4af 100644 --- a/src/controller/xvideos/xvideosGet.ts +++ b/src/controller/xvideos/xvideosGet.ts @@ -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); } } + diff --git a/src/controller/xvideos/xvideosGetRelated.ts b/src/controller/xvideos/xvideosGetRelated.ts index 7e9400a..0581901 100644 --- a/src/controller/xvideos/xvideosGetRelated.ts +++ b/src/controller/xvideos/xvideosGetRelated.ts @@ -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); } } + diff --git a/src/controller/xvideos/xvideosRandom.ts b/src/controller/xvideos/xvideosRandom.ts index 8acc3db..6571d86 100644 --- a/src/controller/xvideos/xvideosRandom.ts +++ b/src/controller/xvideos/xvideosRandom.ts @@ -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); } } + diff --git a/src/controller/xvideos/xvideosSearch.ts b/src/controller/xvideos/xvideosSearch.ts index 728eb9b..4a8ef84 100644 --- a/src/controller/xvideos/xvideosSearch.ts +++ b/src/controller/xvideos/xvideosSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/controller/youporn/youpornGet.ts b/src/controller/youporn/youpornGet.ts index 92e331d..e778869 100644 --- a/src/controller/youporn/youpornGet.ts +++ b/src/controller/youporn/youpornGet.ts @@ -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); } } + diff --git a/src/controller/youporn/youpornGetRelated.ts b/src/controller/youporn/youpornGetRelated.ts index 007f205..82727af 100644 --- a/src/controller/youporn/youpornGetRelated.ts +++ b/src/controller/youporn/youpornGetRelated.ts @@ -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); } } + diff --git a/src/controller/youporn/youpornRandom.ts b/src/controller/youporn/youpornRandom.ts index e72e68a..83f25e4 100644 --- a/src/controller/youporn/youpornRandom.ts +++ b/src/controller/youporn/youpornRandom.ts @@ -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); } } diff --git a/src/controller/youporn/youpornSearch.ts b/src/controller/youporn/youpornSearch.ts index e0d3014..2ea67a3 100644 --- a/src/controller/youporn/youpornSearch.ts +++ b/src/controller/youporn/youpornSearch.ts @@ -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)); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 74193cf..0f9c97f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,52 +1,56 @@ -import "dotenv/config"; -import LustPress from "./LustPress"; -import express from "express"; -import { Request, Response, NextFunction } from "express"; -import scrapeRoutes from "./router/endpoint"; -import { slow, limiter } from "./utils/limit-options"; -import { logger } from "./utils/logger"; -import * as pkg from "../package.json"; - -const lust = new LustPress(); -const app = express(); - - -app.get("/", slow, limiter, async (req, res) => { - res.send({ - success: true, - playground: "https://sinkaroid.github.io/lustpress", - endpoint: "https://github.com/sinkaroid/lustpress/blob/master/README.md#routing", - date: new Date().toLocaleString(), - rss: lust.currentProccess().rss, - heap: lust.currentProccess().heap, - server: await lust.getServer(), - version: `${pkg.version}`, - }); - logger.info({ - path: req.path, - method: req.method, - ip: req.ip, - useragent: req.get("User-Agent") - }); -}); - -app.use(scrapeRoutes()); -app.use((req: Request, res: Response, next: NextFunction) => { - res.status(404); - next(Error(`The page not found in path ${req.url} and method ${req.method}`)); - logger.error({ - path: req.url, - method: req.method, - ip: req.ip, - useragent: req.get("User-Agent") - }); -}); - -app.use((error: any, res: Response) => { - res.status(500).json({ - message: error.message, - stack: error.stack - }); -}); - -app.listen(process.env.PORT || 3000, () => console.log(`${pkg.name} is running on port ${process.env.PORT || 3000}`)); \ No newline at end of file +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 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", + date: new Date().toLocaleString(), + rss: lust.currentProccess().rss, + heap: lust.currentProccess().heap, + server: await lust.getServer(), + version: pkg.version, + })) + .use(scrapeRoutes) + .onError(({ code, error, set }) => { + console.log("Error occurred:", error); + if (code === "NOT_FOUND") { + set.status = 404; + return { + success: false, + message: (error as Error).message || "Not Found", + }; + } + set.status = 500; + return { + success: false, + message: (error as Error).message || "Internal Server Error", + stack: (error as Error).stack, + }; + }) + .listen(process.env.PORT || 3000); + +console.log( + `Lustpress is running at ${app.server?.hostname}:${app.server?.port}` +); + +export type App = typeof app; \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index 49c35f9..4287dba 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -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; + tags?: Record; + models?: Record; + 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; +} diff --git a/src/router/endpoint.ts b/src/router/endpoint.ts index 379ccc5..b07340d 100644 --- a/src/router/endpoint.ts +++ b/src/router/endpoint.ts @@ -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; \ No newline at end of file +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"] }, + }) + ); \ No newline at end of file diff --git a/src/scraper/eporner/epornerGetController.ts b/src/scraper/eporner/epornerGetController.ts index 47f3bd1..278a357 100644 --- a/src/scraper/eporner/epornerGetController.ts +++ b/src/scraper/eporner/epornerGetController.ts @@ -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 diff --git a/src/scraper/eporner/epornerGetRelatedController.ts b/src/scraper/eporner/epornerGetRelatedController.ts index 6d55c7a..dadde65 100644 --- a/src/scraper/eporner/epornerGetRelatedController.ts +++ b/src/scraper/eporner/epornerGetRelatedController.ts @@ -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); diff --git a/src/scraper/pornhub/pornhubGetController.ts b/src/scraper/pornhub/pornhubGetController.ts index 0d3a091..a7402ce 100644 --- a/src/scraper/pornhub/pornhubGetController.ts +++ b/src/scraper/pornhub/pornhubGetController.ts @@ -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); diff --git a/src/scraper/pornhub/pornhubSearchController.ts b/src/scraper/pornhub/pornhubSearchController.ts index a95a045..d688082 100644 --- a/src/scraper/pornhub/pornhubSearchController.ts +++ b/src/scraper/pornhub/pornhubSearchController.ts @@ -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); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/scraper/redtube/redtubeSearchController.ts b/src/scraper/redtube/redtubeSearchController.ts index f05c677..44b6ab9 100644 --- a/src/scraper/redtube/redtubeSearchController.ts +++ b/src/scraper/redtube/redtubeSearchController.ts @@ -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); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/scraper/xhamster/xhamsterGetController.ts b/src/scraper/xhamster/xhamsterGetController.ts index 2680602..5231e46 100644 --- a/src/scraper/xhamster/xhamsterGetController.ts +++ b/src/scraper/xhamster/xhamsterGetController.ts @@ -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); } } + diff --git a/src/scraper/xhamster/xhamsterSearchController.ts b/src/scraper/xhamster/xhamsterSearchController.ts index 8ff2dea..45fd9fe 100644 --- a/src/scraper/xhamster/xhamsterSearchController.ts +++ b/src/scraper/xhamster/xhamsterSearchController.ts @@ -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); - } -} \ No newline at end of file +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); + } +} \ No newline at end of file diff --git a/src/scraper/xnxx/xnxxGetRelatedController.ts b/src/scraper/xnxx/xnxxGetRelatedController.ts index cf8a1a2..9fd8705 100644 --- a/src/scraper/xnxx/xnxxGetRelatedController.ts +++ b/src/scraper/xnxx/xnxxGetRelatedController.ts @@ -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

get