9 Commits

Author SHA1 Message Date
Indrawan I.
67be3acc6e fix: proper error handler (#23)
* re use legacy errors

* 8.3.6-alpha
2026-05-14 09:56:52 +07:00
SNEK WAIFU
e92cb6ca8a ee 2026-05-14 08:09:23 +07:00
SNEK WAIFU
85a45cf575 eeee 2026-05-14 08:09:04 +07:00
SNEK WAIFU
fe225e1090 8.3.4-alpha 2026-05-14 07:25:26 +07:00
SNEK WAIFU
6ed1567f0e need testing 2026-05-14 07:24:58 +07:00
Indrawan I.
67445ed648 fix: resolve cache ttl calculation (#22) 2026-05-14 06:38:19 +07:00
Indrawan I.
e2c59b032b docs: elysia changes & ph fixes (#21) 2026-05-14 05:36:33 +07:00
Indrawan I.
de73724e17 ci: add playground workflows (#20) 2026-05-14 05:15:15 +07:00
Indrawan I.
cd32689b47 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
2026-05-14 04:57:54 +07:00
16 changed files with 204 additions and 85 deletions

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:eporner
run: |
bun run start:dev &
sleep 3
bun run test:eporner

66
.github/workflows/playground.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Playground
on:
push:
branches: [ master ]
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Setup Bun
uses: oven-sh/setup-bun@4bc047ad259df6fc24a6c9b0f9a0cb08cf17fbe5 # v2.0.1
with:
bun-version: latest
- name: Install dependencies
run: bun install
- name: Generate Swagger JSON and HTML
run: |
mkdir public
bun run start:dev &
sleep 3
curl -s http://localhost:3000/docs/json > public/swagger.json
cat << 'EOF' > public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LustPress API Playground</title>
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
<style>body { margin: 0; padding: 0; } .swagger-ui .topbar { display: none; }</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js"></script>
<script src="https://unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js"></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: "./swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
plugins: [SwaggerUIBundle.plugins.DownloadUrl],
});
};
</script>
</body>
</html>
EOF
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
publish_branch: gh-pages

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:pornhub
run: |
bun run start:dev &
sleep 3
bun run test:pornhub

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:redtube
run: |
bun run start:dev &
sleep 3
bun run test:redtube

View File

@@ -30,4 +30,7 @@ jobs:
run: bun run build
- name: Check status code
run: bun run test
run: |
bun run start:dev &
sleep 3
bun run test

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:txxx
run: |
bun run start:dev &
sleep 3
bun run test:txxx

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ea73d27597364c9af683 # v4.2.2
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:xhamster
run: |
bun run start:dev &
sleep 3
bun run test:xhamster

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:xnxx
run: |
bun run start:dev &
sleep 3
bun run test:xnxx

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:xvideos
run: |
bun run start:dev &
sleep 3
bun run test:xvideos

View File

@@ -27,4 +27,7 @@ jobs:
run: bun install
- name: Run test
run: bun run test:youporn
run: |
bun run start:dev &
sleep 3
bun run test:youporn

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ p.ts
.idea
CHANGELOG.md
theme.zip
AGENTS.md
AGENTS.md
lustpress-express

View File

@@ -7,8 +7,8 @@
<a href="https://codeclimate.com/github/sinkaroid/lustpress/maintainability"><img src="https://api.codeclimate.com/v1/badges/29a2be78f853f9e3a4a3/maintainability" /></a>
</p>
Lustpress 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.
Lustpress was originally named Lust and Express (legacy name) and now runs on **Bun** + **Elysia**.
The motivation of this project is carry an actionable data related to pornhub and other r18 sites with gather, similar design pattern, endpoint bindings, and consistent structure in mind.
<a href="https://sinkaroid.github.io/lustpress">Playground</a> •
<a href="https://github.com/sinkaroid/lustpress/blob/master/CONTRIBUTING.md">Contributing</a> •
@@ -28,11 +28,11 @@ The motivation of this project is to bring you an actionable data related to por
- [Installation](#installation)
- [Docker](#docker)
- [Manual](#manual)
- [Tests case](#tests)
- [Tests](#tests)
- [Playground](https://sinkaroid.github.io/lustpress)
- [Routing](#playground)
- [Status response](#status-response)
- [Pornhub JS Challenge Solver](#pornhub-js-challenge-solver)
- [Ph Challenge Solver](#pornhub-js-challenge-solver)
- [CLosing remarks](https://github.com/sinkaroid/lustpress/blob/master/CLOSING_REMARKS.md)
- [Alternative links](https://github.com/sinkaroid/lustpress/blob/master/CLOSING_REMARKS.md#alternative-links)
- [Pronunciation](#Pronunciation)
@@ -99,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.2.0-alpha Bun/1.3.13"
USER_AGENT = "lustpress/8.2.3-alpha Bun/1.3.13"
```
### Docker
@@ -114,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.2.0-alpha Bun/1.3.13' \
-e USER_AGENT='lustpress/8.2.3-alpha Bun/1.3.13' \
ghcr.io/sinkaroid/lustpress:latest
```
@@ -129,7 +129,7 @@ docker run -d \
- Lustpress testing and hot reload
- `bun run start:dev`
## Running tests
## Tests
Run the following commands to execute tests for each supported source:
```bash

View File

@@ -29,6 +29,9 @@ module.exports = [
beforeAll: "readonly",
afterAll: "readonly",
it: "readonly",
AbortController: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
},
},
plugins: {

View File

@@ -1,6 +1,6 @@
{
"name": "lustpress",
"version": "8.2.0-alpha",
"version": "8.3.6-alpha",
"description": "RESTful and experimental API for PH and other R18 websites",
"main": "src/index.ts",
"scripts": {

View File

@@ -9,7 +9,7 @@ const keyv = process.env.REDIS_URL
: new Keyv();
keyv.on("error", (err) => console.log("Connection Error", err));
const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE);
const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE || "1");
const GEO_TIMEOUT_MS = 3000;
let cachedLastLocation: string | null = null;
@@ -25,12 +25,14 @@ function cachedLocationOrUnknown(): string {
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 browserUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36";
private cookieCache: { [domain: string]: string } = {};
constructor() {
this.url = "";
this.useragent = `${pkg.name}/${pkg.version} Bun/${Bun.version}`;
this.browserUA =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36";
this.useragent = process.env.USER_AGENT || `${pkg.name}/${pkg.version} Bun/${Bun.version}`;
}
async getPornhubCookies(): Promise<string> {
@@ -77,13 +79,16 @@ class LustPress {
async fetchBody(url: string): Promise<Buffer> {
const cached = await keyv.get(url);
if (cached) return cached;
if (cached) {
console.log(`[CACHE HIT] ${url}`);
return cached;
}
const isPornhub = /pornhub\.com/i.test(url);
const domain = new URL(url).hostname;
const headers: Record<string, string> = {
"User-Agent": isPornhub ? this.browserUA : this.useragent,
"User-Agent": isPornhub ? this.browserUA : this.useragent
};
if (isPornhub) {
@@ -96,21 +101,25 @@ class LustPress {
headers["Accept-Language"] = "en-US,en;q=0.9";
}
console.log(`[FETCH] ${url}`);
let res = await fetch(url, { headers, redirect: "follow" });
let text = await res.text();
let arrayBuf = await res.arrayBuffer();
let body = Buffer.from(arrayBuf);
if (isPornhub && text.includes("leastFactor")) {
if (isPornhub && body.toString("utf8").includes("leastFactor")) {
console.log("Challenge detected at target URL. Re-authenticating...");
this.cookieCache[domain] = await this.getPornhubCookies();
res = await fetch(url, {
headers: { ...headers, Cookie: this.cookieCache[domain] },
redirect: "follow"
});
text = await res.text();
arrayBuf = await res.arrayBuffer();
body = Buffer.from(arrayBuf);
}
const body = Buffer.from(text);
if (!url.includes("/random")) await keyv.set(url, body, ttl);
if (res.ok && !url.includes("/random")) {
await keyv.set(url, body, ttl);
}
return body;
}

View File

@@ -1,56 +1,69 @@
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;
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,
}))
.onError(({ code, error, set }) => {
if (code === "NOT_FOUND") {
set.status = 404;
return {
success: false,
message: (error as Error).message || "Not Found",
};
}
if (code === "VALIDATION") {
set.status = 400;
return {
success: false,
message: JSON.parse((error as Error).message)[0]?.message || "Validation Error",
};
}
if (code === "UNKNOWN") {
set.status = 400;
return {
success: false,
message: (error as Error).message,
};
}
set.status = 500;
return {
success: false,
message: (error as Error).message || "Internal Server Error",
stack: (error as Error).stack,
};
})
.use(scrapeRoutes) // reuse legacy error handling
.listen(process.env.PORT || 3000);
console.log(
`Lustpress is running at ${app.server?.hostname}:${app.server?.port}`
);
export type App = typeof app;