Compare commits
14 Commits
master
...
v8.2.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a091e89e8a | ||
|
|
b1d6add110 | ||
|
|
f04bbead08 | ||
|
|
07080d4a6a | ||
|
|
4735defbb4 | ||
|
|
2cd8e1ed29 | ||
|
|
fdf634dce7 | ||
|
|
423c955a18 | ||
|
|
ad81dbfcd0 | ||
|
|
a925544508 | ||
|
|
3a13143d8c | ||
|
|
2df31521d2 | ||
|
|
3cc0c3317f | ||
|
|
11b1228c5d |
5
.github/workflows/eporner.yml
vendored
5
.github/workflows/eporner.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:eporner
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:eporner
|
|
||||||
66
.github/workflows/playground.yml
vendored
66
.github/workflows/playground.yml
vendored
@@ -1,66 +0,0 @@
|
|||||||
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
|
|
||||||
5
.github/workflows/pornhub.yml
vendored
5
.github/workflows/pornhub.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:pornhub
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:pornhub
|
|
||||||
5
.github/workflows/redtube.yml
vendored
5
.github/workflows/redtube.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:redtube
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:redtube
|
|
||||||
5
.github/workflows/status.yml
vendored
5
.github/workflows/status.yml
vendored
@@ -30,7 +30,4 @@ jobs:
|
|||||||
run: bun run build
|
run: bun run build
|
||||||
|
|
||||||
- name: Check status code
|
- name: Check status code
|
||||||
run: |
|
run: bun run test
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test
|
|
||||||
5
.github/workflows/txxx.yml
vendored
5
.github/workflows/txxx.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:txxx
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:txxx
|
|
||||||
7
.github/workflows/xhamster.yml
vendored
7
.github/workflows/xhamster.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ea73d27597364c9af683 # v4.2.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:xhamster
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:xhamster
|
|
||||||
5
.github/workflows/xnxx.yml
vendored
5
.github/workflows/xnxx.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:xnxx
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:xnxx
|
|
||||||
5
.github/workflows/xvideos.yml
vendored
5
.github/workflows/xvideos.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:xvideos
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:xvideos
|
|
||||||
5
.github/workflows/youporn.yml
vendored
5
.github/workflows/youporn.yml
vendored
@@ -27,7 +27,4 @@ jobs:
|
|||||||
run: bun install
|
run: bun install
|
||||||
|
|
||||||
- name: Run test
|
- name: Run test
|
||||||
run: |
|
run: bun run test:youporn
|
||||||
bun run start:dev &
|
|
||||||
sleep 3
|
|
||||||
bun run test:youporn
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -10,5 +10,4 @@ p.ts
|
|||||||
.idea
|
.idea
|
||||||
CHANGELOG.md
|
CHANGELOG.md
|
||||||
theme.zip
|
theme.zip
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
lustpress-express
|
|
||||||
14
README.md
14
README.md
@@ -7,8 +7,8 @@
|
|||||||
<a href="https://codeclimate.com/github/sinkaroid/lustpress/maintainability"><img src="https://api.codeclimate.com/v1/badges/29a2be78f853f9e3a4a3/maintainability" /></a>
|
<a href="https://codeclimate.com/github/sinkaroid/lustpress/maintainability"><img src="https://api.codeclimate.com/v1/badges/29a2be78f853f9e3a4a3/maintainability" /></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
Lustpress was originally named Lust and Express (legacy name) and now runs on **Bun** + **Elysia**.
|
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 carry an actionable data related to pornhub and other r18 sites with gather, similar design pattern, endpoint bindings, and consistent structure in mind.
|
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.
|
||||||
|
|
||||||
<a href="https://sinkaroid.github.io/lustpress">Playground</a> •
|
<a href="https://sinkaroid.github.io/lustpress">Playground</a> •
|
||||||
<a href="https://github.com/sinkaroid/lustpress/blob/master/CONTRIBUTING.md">Contributing</a> •
|
<a href="https://github.com/sinkaroid/lustpress/blob/master/CONTRIBUTING.md">Contributing</a> •
|
||||||
@@ -28,11 +28,11 @@ The motivation of this project is carry an actionable data related to pornhub an
|
|||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Docker](#docker)
|
- [Docker](#docker)
|
||||||
- [Manual](#manual)
|
- [Manual](#manual)
|
||||||
- [Tests](#tests)
|
- [Tests case](#tests)
|
||||||
- [Playground](https://sinkaroid.github.io/lustpress)
|
- [Playground](https://sinkaroid.github.io/lustpress)
|
||||||
- [Routing](#playground)
|
- [Routing](#playground)
|
||||||
- [Status response](#status-response)
|
- [Status response](#status-response)
|
||||||
- [Ph Challenge Solver](#pornhub-js-challenge-solver)
|
- [Pornhub JS Challenge Solver](#pornhub-js-challenge-solver)
|
||||||
- [CLosing remarks](https://github.com/sinkaroid/lustpress/blob/master/CLOSING_REMARKS.md)
|
- [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)
|
- [Alternative links](https://github.com/sinkaroid/lustpress/blob/master/CLOSING_REMARKS.md#alternative-links)
|
||||||
- [Pronunciation](#Pronunciation)
|
- [Pronunciation](#Pronunciation)
|
||||||
@@ -99,7 +99,7 @@ REDIS_URL = redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.clo
|
|||||||
EXPIRE_CACHE = 1
|
EXPIRE_CACHE = 1
|
||||||
|
|
||||||
# you must identify your origin, if not set it will use default
|
# you must identify your origin, if not set it will use default
|
||||||
USER_AGENT = "lustpress/8.2.3-alpha Bun/1.3.13"
|
USER_AGENT = "lustpress/8.2.0-alpha Bun/1.3.13"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
@@ -114,7 +114,7 @@ docker run -d \
|
|||||||
-p 3028:3000 \
|
-p 3028:3000 \
|
||||||
-e REDIS_URL='redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.cloud.redislabs.com:1337' \
|
-e REDIS_URL='redis://default:somenicepassword@redis-666.c10.us-east-6-6.ec666.cloud.redislabs.com:1337' \
|
||||||
-e EXPIRE_CACHE='1' \
|
-e EXPIRE_CACHE='1' \
|
||||||
-e USER_AGENT='lustpress/8.2.3-alpha Bun/1.3.13' \
|
-e USER_AGENT='lustpress/8.2.0-alpha Bun/1.3.13' \
|
||||||
ghcr.io/sinkaroid/lustpress:latest
|
ghcr.io/sinkaroid/lustpress:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ docker run -d \
|
|||||||
- Lustpress testing and hot reload
|
- Lustpress testing and hot reload
|
||||||
- `bun run start:dev`
|
- `bun run start:dev`
|
||||||
|
|
||||||
## Tests
|
## Running tests
|
||||||
Run the following commands to execute tests for each supported source:
|
Run the following commands to execute tests for each supported source:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -29,9 +29,6 @@ module.exports = [
|
|||||||
beforeAll: "readonly",
|
beforeAll: "readonly",
|
||||||
afterAll: "readonly",
|
afterAll: "readonly",
|
||||||
it: "readonly",
|
it: "readonly",
|
||||||
AbortController: "readonly",
|
|
||||||
setTimeout: "readonly",
|
|
||||||
clearTimeout: "readonly",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lustpress",
|
"name": "lustpress",
|
||||||
"version": "8.3.6-alpha",
|
"version": "8.2.0-alpha",
|
||||||
"description": "RESTful and experimental API for PH and other R18 websites",
|
"description": "RESTful and experimental API for PH and other R18 websites",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const keyv = process.env.REDIS_URL
|
|||||||
: new Keyv();
|
: 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 || "1");
|
const ttl = 1000 * 60 * 60 * Number(process.env.EXPIRE_CACHE);
|
||||||
|
|
||||||
const GEO_TIMEOUT_MS = 3000;
|
const GEO_TIMEOUT_MS = 3000;
|
||||||
let cachedLastLocation: string | null = null;
|
let cachedLastLocation: string | null = null;
|
||||||
@@ -25,14 +25,12 @@ function cachedLocationOrUnknown(): string {
|
|||||||
class LustPress {
|
class LustPress {
|
||||||
url: string;
|
url: string;
|
||||||
useragent: string;
|
useragent: string;
|
||||||
private browserUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36";
|
private 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 } = {};
|
private cookieCache: { [domain: string]: string } = {};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.url = "";
|
this.url = "";
|
||||||
this.browserUA =
|
this.useragent = `${pkg.name}/${pkg.version} Bun/${Bun.version}`;
|
||||||
"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> {
|
async getPornhubCookies(): Promise<string> {
|
||||||
@@ -79,16 +77,13 @@ class LustPress {
|
|||||||
|
|
||||||
async fetchBody(url: string): Promise<Buffer> {
|
async fetchBody(url: string): Promise<Buffer> {
|
||||||
const cached = await keyv.get(url);
|
const cached = await keyv.get(url);
|
||||||
if (cached) {
|
if (cached) return cached;
|
||||||
console.log(`[CACHE HIT] ${url}`);
|
|
||||||
return cached;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isPornhub = /pornhub\.com/i.test(url);
|
const isPornhub = /pornhub\.com/i.test(url);
|
||||||
const domain = new URL(url).hostname;
|
const domain = new URL(url).hostname;
|
||||||
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
"User-Agent": isPornhub ? this.browserUA : this.useragent
|
"User-Agent": isPornhub ? this.browserUA : this.useragent,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isPornhub) {
|
if (isPornhub) {
|
||||||
@@ -101,25 +96,21 @@ class LustPress {
|
|||||||
headers["Accept-Language"] = "en-US,en;q=0.9";
|
headers["Accept-Language"] = "en-US,en;q=0.9";
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[FETCH] ${url}`);
|
|
||||||
let res = await fetch(url, { headers, redirect: "follow" });
|
let res = await fetch(url, { headers, redirect: "follow" });
|
||||||
let arrayBuf = await res.arrayBuffer();
|
let text = await res.text();
|
||||||
let body = Buffer.from(arrayBuf);
|
|
||||||
|
|
||||||
if (isPornhub && body.toString("utf8").includes("leastFactor")) {
|
if (isPornhub && text.includes("leastFactor")) {
|
||||||
console.log("Challenge detected at target URL. Re-authenticating...");
|
console.log("Challenge detected at target URL. Re-authenticating...");
|
||||||
this.cookieCache[domain] = await this.getPornhubCookies();
|
this.cookieCache[domain] = await this.getPornhubCookies();
|
||||||
res = await fetch(url, {
|
res = await fetch(url, {
|
||||||
headers: { ...headers, Cookie: this.cookieCache[domain] },
|
headers: { ...headers, Cookie: this.cookieCache[domain] },
|
||||||
redirect: "follow"
|
redirect: "follow"
|
||||||
});
|
});
|
||||||
arrayBuf = await res.arrayBuffer();
|
text = await res.text();
|
||||||
body = Buffer.from(arrayBuf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.ok && !url.includes("/random")) {
|
const body = Buffer.from(text);
|
||||||
await keyv.set(url, body, ttl);
|
if (!url.includes("/random")) await keyv.set(url, body, ttl);
|
||||||
}
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
125
src/index.ts
125
src/index.ts
@@ -1,69 +1,56 @@
|
|||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import { cors } from "@elysiajs/cors";
|
import { cors } from "@elysiajs/cors";
|
||||||
import { swagger } from "@elysiajs/swagger";
|
import { swagger } from "@elysiajs/swagger";
|
||||||
import { lust } from "./LustPress";
|
import { lust } from "./LustPress";
|
||||||
import { scrapeRoutes } from "./router/endpoint";
|
import { scrapeRoutes } from "./router/endpoint";
|
||||||
import pkg from "../package.json";
|
import pkg from "../package.json";
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(cors())
|
.use(cors())
|
||||||
.use(
|
.use(
|
||||||
swagger({
|
swagger({
|
||||||
path: "/docs",
|
path: "/docs",
|
||||||
documentation: {
|
documentation: {
|
||||||
info: {
|
info: {
|
||||||
title: "Lustpress API",
|
title: "Lustpress API",
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
description: pkg.description,
|
description: pkg.description,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.get("/", async () => ({
|
.get("/", async () => ({
|
||||||
success: true,
|
success: true,
|
||||||
playground: "https://sinkaroid.github.io/lustpress",
|
playground: "https://sinkaroid.github.io/lustpress",
|
||||||
endpoint:
|
endpoint:
|
||||||
"https://github.com/sinkaroid/lustpress/blob/master/README.md#routing",
|
"https://github.com/sinkaroid/lustpress/blob/master/README.md#routing",
|
||||||
date: new Date().toLocaleString(),
|
date: new Date().toLocaleString(),
|
||||||
rss: lust.currentProccess().rss,
|
rss: lust.currentProccess().rss,
|
||||||
heap: lust.currentProccess().heap,
|
heap: lust.currentProccess().heap,
|
||||||
server: await lust.getServer(),
|
server: await lust.getServer(),
|
||||||
version: pkg.version,
|
version: pkg.version,
|
||||||
}))
|
}))
|
||||||
.onError(({ code, error, set }) => {
|
.use(scrapeRoutes)
|
||||||
if (code === "NOT_FOUND") {
|
.onError(({ code, error, set }) => {
|
||||||
set.status = 404;
|
console.log("Error occurred:", error);
|
||||||
return {
|
if (code === "NOT_FOUND") {
|
||||||
success: false,
|
set.status = 404;
|
||||||
message: (error as Error).message || "Not Found",
|
return {
|
||||||
};
|
success: false,
|
||||||
}
|
message: (error as Error).message || "Not Found",
|
||||||
if (code === "VALIDATION") {
|
};
|
||||||
set.status = 400;
|
}
|
||||||
return {
|
set.status = 500;
|
||||||
success: false,
|
return {
|
||||||
message: JSON.parse((error as Error).message)[0]?.message || "Validation Error",
|
success: false,
|
||||||
};
|
message: (error as Error).message || "Internal Server Error",
|
||||||
}
|
stack: (error as Error).stack,
|
||||||
if (code === "UNKNOWN") {
|
};
|
||||||
set.status = 400;
|
})
|
||||||
return {
|
.listen(process.env.PORT || 3000);
|
||||||
success: false,
|
|
||||||
message: (error as Error).message,
|
console.log(
|
||||||
};
|
`Lustpress is running at ${app.server?.hostname}:${app.server?.port}`
|
||||||
}
|
);
|
||||||
set.status = 500;
|
|
||||||
return {
|
export type App = typeof app;
|
||||||
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;
|
|
||||||
Reference in New Issue
Block a user